字符集、IO流、释放资源新的格式、文件夹拷贝的案例
一,字符集
1,标准ASCII字符集
ASCII(American Standard Code for Information Interchange): 美国信息交换标准代码,包括了英文、符号等。
标准ASCII使用1个字节存储一个字符,首尾是0,总共可表示128个字符,对美国佬来说完全够用。
由于这个ASCLL码表的局限性很大,每个国家就在ASCll表的基础上,扩展了自己的码表。中国的码表叫做GBK;
汉字编码字符集,包含了2万多个汉字等字符,GBK中一个中文字符编码成两个字节的形式存储。
注意:GBK兼容了ASCII字符集。
GBK规定:汉字的第一个个字节的第一位必须为 1
比如:我a你 的编码格式
由此也就可以判断:这个数字字母还是文字,这样就可以很好的解码
- 如果读取到第1位是0,就认为是一个字母,此时往后读1个字节。
- 如果读取到第1位是1,就认为是一个汉字,此时往后读2个字节。
每个国家在ASCLL码表的基础上都产生了自己的编码,这对于自己的国家是好的,但是对于国家之间是不友好的。所以国际组织就制定了能够容纳世界上所有文字和符号的字符集。
2,Unicode字符集(统一码,也叫万国码)
🏫 我们熟知的UTF-8和Unicode字符集什么关系呢?
“UTF-8”和“Unicode”之间的关系是包含关系,因为“UTF-8”是“Unicode”的实现方式之一,它规定了字符如何在计算机中存储、传输等,而其他实现方式还包括“UTF-16”和“UTF-32”。
🏫 那么UTF-8和UTF-32有什么关系呢?
UTF-32 用四个字节表示一个字符,十分的奢侈,中间有很多的空间浪费,使得数据占用存储空间很大,通信的效率变低。
UTF-8是Unicode字符集的一种编码方案,采取可变长编码方案,共分四个长度区:1个字节,2个字节,3个字节,4个字节
英文字符、数字等只占1个字节(兼容标准ASCII编码),汉字字符占用3个字节。
总结:
ASCII字符集:只有英文、数字、符号等,占1个字节。
GBK字符集:汉字占2个字节,英文、数字占1个字节。
UTF-8字符集:汉字占3个字节,英文、数字占1个字节。
🎏 注意事项:
注意1: 字符编码时使用的字符集,和解码时使用的字符集必须一致,否则会出现乱码
注意2: 英文,数字一般不会乱码,因为很多字符集都兼容了ASCII编码。
3,字符集的编码和解码操作:
首先要明白什么叫做编码,什么叫做解码。很重要
编码: 把字符按照指定的字符集编码成字节 即: 从文字变成数字
解码: 把字节按照指定的字符集编码成字符 即: 从数字变成文字
4,Java代码完成对字符的编码
String提供了如下方法 | 说明 |
---|---|
byte[] getBytes() | 使用平台的默认字符集将该 String编码为一系列字节,将结果存储到新的字节数组中 |
byte[] getBytes(String charsetName) | 使用指定的字符集将该 String编码为一系列字节,将结果存储到新的字节数组中 |
5,Java代码完成对字符的解码
String提供了如下方法 | 说明 |
---|---|
String(byte[] bytes) | 通过使用平台的默认字符集解码指定的字节数组来构造新的 String |
String(byte[] bytes, String charsetName) | 通过指定的字符集解码指定的字节数组来构造新的 String |
示例代码:
// 1、编码
String data = "a我b";
byte[] bytes = data.getBytes(); // 默认是按照平台字符集(UTF-8)进行编码的。
System.out.println(Arrays.toString(bytes));
// 按照指定字符集进行编码。
byte[] bytes1 = data.getBytes("GBK");
System.out.println(Arrays.toString(bytes1));
// 2、解码
String s1 = new String(bytes); // 按照平台默认编码(UTF-8)解码
System.out.println(s1);
String s2 = new String(bytes1, "GBK");
System.out.println(s2);
二,IO流
前面学习过File类。但File只能操作文件,不能操作文件中的内容。我们也学习了字符集,不同的字符集存字符数据的原理是不一样的。有了前面两个知识的基础,接下来我们再学习IO流,就可以对文件中的数据进行操作了。
IO流的作用:就是可以对文件或者网络中的数据进行读、写的操作。如下图所示
- 把数据从磁盘、网络中读取到程序中来,用到的是输入流。
- 把程序中的数据写入磁盘、网络中,用到的是输出流。
- 简单记:输入流(读数据)、输出流(写数据)
IO流在Java中有很多种,不同的流来干不同的事情。Java把各种流用不同的类来表示,这些流的继承体系如下图所示:
IO流分为两大派系:
1.字节流:字节流又分为字节输入流、字节输出流
2.字符流:字符流由分为字符输入流、字符输出流
1,总结流的四大类:
-
字节输入流:以内存为基准,来自磁盘文件/网络中的数据以字节的形式读入到内存中去的流
-
字节输出流:以内存为基准,把内存中的数据以字节写出到磁盘文件或者网络中去的流。
-
字符输入流:以内存为基准,来自磁盘文件/网络中的数据以字符的形式读入到内存中去的流。
-
字符输出流:以内存为基准,把内存中的数据以字符写出到磁盘文件或者网络介质中去的流。
2,FileInputStream读取一个字节
因为InputStream是抽象类,我们用的是它的子类,叫FileInputStream。
需要用到的方法如下所示:有构造方法、成员方法
构造器 | 说明 |
---|---|
public FileInputStream(File file) | 创建字节输入流管道与源文件接通 |
public FileInputStream(String pathname) | 创建字节输入流管道与源文件接通 |
方法名称 | 说明 |
---|---|
public int read() | 每次读取一个字节返回,如果发现没有数据可读会返回-1. |
注意事項: 使用FileInputStream每次读取一个字节,读取性能较差,并且读取汉字输出会乱码。
步骤:
第一步:创建FileInputStream文件字节输入流管道,与源文件接通。
第二步:调用read()方法开始读取文件的字节数据。
第三步:调用close()方法释放资源
// 1、创建文件字节输入流管道,与源文件接通。 想对路径
InputStream is = new FileInputStream(("javEE-day09\\src\\hello.txt"));
// 2、开始读取文件的字节数据。
// public int read():每次读取一个字节返回,如果没有数据了,返回-1.
int b; // 用于记住读取的字节。
while ((b = is.read()) != -1){
System.out.print((char) b);
}
//3、流使用完毕之后,必须关闭!释放系统资源!
is.close();
有一个问题:由于一个中文在UTF-8编码方案中是占3个字节,采用一次读取一个字节的方式,读一个字节就相当于读了1/3个汉字,此时将这个字节转换为字符,是会有乱码的。
3,使用FileInputStream读取多个字节的数据:
步骤:
第一步:创建FileInputStream文件字节输入流管道,与源文件接通。
第二步:调用read(byte[] bytes)方法开始读取文件的字节数据。
第三步:调用close()方法释放资源
// 1、创建一个字节输入流对象代表字节输入流管道与源文件接通。
InputStream is = new FileInputStream("javEE-day09\\src\\hello.txt");
// 2、开始读取文件中的字节数据:每次读取多个字节。
// public int read(byte b[]) throws IOException
// 每次读取多个字节到字节数组中去,返回读取的字节数量,读取完毕会返回-1.
// 3、使用循环改造。
byte[] buffer = new byte[3];
int len; // 记住每次读取了多少个字节。 abc 66
while ((len = is.read(buffer)) != -1){
// 注意:读取多少,解码出多少。
String rs = new String(buffer, 0 , len);
System.out.print(rs);
}
// 性能得到了明显的提升!!
// 这种方案也不能避免读取汉字输出乱码的问题!!
is.close(); // 关闭流
注意:read(byte[] bytes)它的返回值,表示当前这一次读取的字节个数。
还需要注意一个问题:采用一次读取多个字节的方式,也是可能有乱码的。因为也有可能读取到半个汉字的情况。
4,使用FileInputStream读取全部字节
前面我们到的读取方式,不管是一次读取一个字节,还是一次读取多个字节,都有可能有乱码。那么接下来我们介绍一种,不出现乱码的读取方式。
方式一: 我们可以一次性读取文件中的全部字节,然后把全部字节转换为一个字符串,就不会有乱码了。
方法名称 | 说明 |
---|---|
public int read(byte[] buffer) | 每次用一个字节数组去读取数据,返回字节数组读取了多少个字节, 如果发现没有数据可读会返回-1. |
// 1、一次性读取完文件的全部字节到一个字节数组中去。
// 创建一个字节输入流管道与源文件接通
InputStream is = new FileInputStream("javEE-day09\\src\\hello.txt");
// 2、准备一个字节数组,大小与文件的大小正好一样大。
File f = new File("file-io-app\\src\\itheima03.txt");
long size = f.length();
byte[] buffer = new byte[(int) size];
int len = is.read(buffer);
System.out.println(new String(buffer));
//3、关闭流
is.close();
方式二: Java官方为InputStream提供了如下方法,可以直接把文件的全部字节读取到一个字节数组中返回。
方法名称 | 说明 |
---|---|
public byte[] readAllBytes() throws IOException | 直接将当前字节输入流对应的文件对象的字节数据装到一个字节数组返回 |
// 1、一次性读取完文件的全部字节到一个字节数组中去。
// 创建一个字节输入流管道与源文件接通
InputStream is = new FileInputStream("javEE-day09\\src\\hello.txt");
//2、调用方法读取所有字节,返回一个存储所有字节的字节数组。
byte[] buffer = is.readAllBytes();
System.out.println(new String(buffer));
//3、关闭流
is.close();
最后,还是要注意一个问题:一次读取所有字节虽然可以解决乱码问题,但是文件不能过大,如果文件过大,可能导致内存溢出。
5,FileOutputStream写字节
往文件中写数据需要用到 OutputStream下面的一个子类FileOutputStream。写输入的流程如下图所示:
使用FileOutputStream往文件中写数据的步骤如下:
第一步:创建FileOutputStream文件字节输出流管道,与目标文件接通。
第二步:调用wirte()方法往文件中写数据
第三步:调用close()方法释放资源
代码如下:
// 1、创建一个字节输出流管道与目标文件接通。
// 覆盖管道:覆盖之前的数据
// OutputStream os =
// new FileOutputStream("file-io-app/src/itheima04out.txt");
// 追加数据的管道
OutputStream os =
new FileOutputStream("javEE-day09\\src\\hello.txt", true);
// 2、开始写字节数据出去了
os.write(97); // 97就是一个字节,代表a
os.write('b'); // 'b'也是一个字节
// os.write('磊'); // [ooo] 默认只能写出去一个字节
byte[] bytes = "我爱你中国abc".getBytes();
os.write(bytes);
os.write(bytes, 0, 15);
// 换行符
os.write("\r\n".getBytes());
os.close(); // 关闭流
6,字节流复制文件
上面学习了输出流,输入流。现在我们就可以用这两种流配合起来使用,做一个文件复制的综合案例。
比如:我们要复制一张图片,从磁盘E:\\a_learning\\test
的一个位置,复制到E:\\a_learning\\testfolder
位置。
复制文件的思路如下图所示:
1.需要创建一个FileInputStream流与源文件接通,创建FileOutputStream与目标文件接通
2.然后创建一个数组,使用FileInputStream每次读取一个字节数组的数据,存如数组中
3.然后再使用FileOutputStream把字节数组中的有效元素,写入到目标文件中
public static void main(String[] args) throws Exception {
// 1、创建一个字节输入流管道与源文件接通
InputStream is = new FileInputStream("E:\\a_learning\\test");
// 2、创建一个字节输出流管道与目标文件接通。
OutputStream os = new FileOutputStream("E:\\a_learning\\testfolder");
// 3、创建一个字节数组,负责转移字节数据。
byte[] buffer = new byte[1024]; // 1KB.
// 4、从字节输入流中读取字节数据,写出去到字节输出流中。读多少写出去多少。
int len; // 记住每次读取了多少个字节。
while ((len = is.read(buffer)) != -1){
os.write(buffer, 0, len);
}
os.close();
is.close();
System.out.println("复制完成!!");
}
三,释放资源的方式
比如在刚才的代码中:当程序中有错误的时候,虚拟机就会停止程序,以至于下面的方法没法执行,导致资源的浪费,这也是之前我们手机或电脑卡的话,重启一下就好很多,就是有一些资源被某个程序所占用从而导致的这个结果。
1,JDK7以前的资源释放
在JDK7版本以前,我们可以使用try...catch...finally语句来处理。格式如下
try{
//有可能产生异常的代码
}catch(异常类 e){
//处理异常的代码
}finally{
//释放资源的代码
//finally里面的代码有一个特点,不管异常是否发生,finally里面的代码都会执行。
}
下面通过jdk8之前的方法对方法做一个改造:
public static void main(String[] args) {
InputStream is = null;
OutputStream os = null;
try {
System.out.println(10 / 0);
// 1、创建一个字节输入流管道与源文件接通
is = new FileInputStream("E:\\a_learning\\test");
// 2、创建一个字节输出流管道与目标文件接通。
os = new FileOutputStream("E:\\a_learning\\testfolder");
// 异常点
System.out.println(10 / 0);
// 3、创建一个字节数组,负责转移字节数据。
byte[] buffer = new byte[1024]; // 1KB.
// 4、从字节输入流中读取字节数据,写出去到字节输出流中。读多少写出去多少。
int len; // 记住每次读取了多少个字节。
while ((len = is.read(buffer)) != -1){
os.write(buffer, 0, len);
}
System.out.println("复制完成!!");
} catch (IOException e) {
e.printStackTrace();
} finally {
// 释放资源的操作
try {
if(os != null) os.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if(is != null) is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
通过代码我们会发现关闭资源极其的麻烦。我们不仅仅要提高变量is 和 os 的优先级,而且还要写大量的try catch代码。尤其是在finally中。业务代码也就了了几行但是异常代码那~~~么多。所以在JDK之后就做一个改进。
2,JDK7以后的资源释放
try(资源对象1; 资源对象2;){
使用资源的代码
}catch(异常类 e){
处理异常的代码
}
//注意:注意到没有,这里没有释放资源的代码。它会自动是否资源
public static void main(String[] args) {
try (
// 1、创建一个字节输入流管道与源文件接通
is = new FileInputStream("E:\\a_learning\\test");
// 2、创建一个字节输出流管道与目标文件接通。
os = new FileOutputStream("E:\\a_learning\\testfolder");
){
// 3、创建一个字节数组,负责转移字节数据。
byte[] buffer = new byte[1024]; // 1KB.
// 4、从字节输入流中读取字节数据,写出去到字节输出流中。读多少写出去多少。
int len; // 记住每次读取了多少个字节。
while ((len = is.read(buffer)) != -1){
os.write(buffer, 0, len);
}
System.out.println(conn);
System.out.println("复制完成!!");
} catch (Exception e) {
e.printStackTrace();
}
}
就十分的方便,所以一定要掌握!
四,学了递归和File类以及IO之后结合的一个小案例
需求:拷贝一个指定文件夹中的所有的内容,到另一个指定文件夹中
public class CopyFolder {
public static void main(String[] args) {
copyFolder(new File("E:\\javaEE-code\\javaEE-day08"),new File("E:\\test"));
}
/**
* 拷贝文件夹
*
* @param startFolder 文件夹路径
* @param endFolder 拷贝的位置
*/
public static void copyFolder(File startFolder, File endFolder) {
// 如果是一个文件,那么就创建文件夹直接拷贝
if (startFolder.isFile()) {
// 创建拷贝的文件文件夹
endFolder.mkdirs();
// 调用方法拷贝文件
copyFile(startFolder, new File(endFolder, startFolder.getName()));
} else if (startFolder.isDirectory()) {
// 说明y是一个文件夹,需要在d文件夹下,创建出一个y文件夹
File file1 = new File(endFolder, startFolder.getName());
file1.mkdirs();
// 遍历原始文件夹
File[] files = startFolder.listFiles();
for (File file : files) {
// 判断 file是文件还是文件夹
if (file.isDirectory()) {
// 可以使用 copyDir方法,处理文件夹
copyFolder(file, file1);
} else {
// 完成文件复制
copyFile(file, new File(file1, file.getName()));
}
}
}else{
System.out.println("权限不够,或者文件不存");
}
}
/**
* 拷贝文件
*
* @param f1 需要拷贝的文件
* @param f2 拷贝之后的文件
*/
public static void copyFile(File f1, File f2) {
try (
// 1: 输入流;读原文件
FileInputStream fin = new FileInputStream(f1);
// 2: 输出流;写文件
FileOutputStream fout = new FileOutputStream(f2)
) {
//3: 准备一个字节数组,用于每次循环的时候,装读取到的字节数据,同时准备一个变量,用于保存每次循环的时候,读取到的字节数据的个数
int count;
byte[] arr = new byte[8192];
// 4: 循环读写
while ((count = fin.read(arr)) != -1) {
// 将arr中的count个字节数据,写到目的地即可
fout.write(arr, 0, count);
}
System.out.println(f1.getName() + "复制完成");
} catch (IOException e) {
e.printStackTrace();
}
}
}