Java --> IO流(字符集)
- 字符集:
1 import java.util.Arrays;
2
3 public class Test {
4 public static void main(String[] args) throws Exception {
5 //1、编码:把文字转换成指定的字节
6 String name = "abc阿1百2川";
7 byte[] bytes = name.getBytes(); //以当前代码默认字符集进行编码(UTF-8)
8 System.out.println(bytes.length); //一个英文字符占1个字节,一个中文字符占3个字节
9 System.out.println(Arrays.toString(bytes));
10
11 System.out.println("---------------");
12 //以指定形式(GBK)转换成字节数组
13 byte[] bytes1 = name.getBytes("GBK"); //一个英文字符占1个字节,一个中文字符占2个字节
14 System.out.println(bytes1.length);
15 System.out.println(Arrays.toString(bytes));
16
17 //2、解码:把字节转换成对应的中文形式(注:编码前和编码后的字符集要一致)
18 String str = new String(bytes); //以当前代码默认字符集进行解码(UTF-8)<编码、解码前后字符集一致>
19 System.out.println(str);
20
21 System.out.println("-----------------");
22 String str1 = new String(bytes1); //以当前代码默认字符集进行解码(UTF-8)<编码(GBK)、解码(UTF-8)前后字符集不一致>
23 System.out.println(str1);
24 System.out.println("---乱码原因:GBK以两个字节存储的数据会被UTF-8强行解析成三个字节表示的数据---");
25 //改正:编码使用GBK、解码也要使用GBK
26 String str2 = new String(bytes1, "GBK");
27 System.out.println(str2);
28 }
29 }
- 文件字节输入流(FileInputStream):每次读取一个字节(适合读英文,读取中文会发生乱码)
- 作用:以内存为基准,把磁盘文件中的数据以字节的形式读取到内存中去
1 import java.io.File;
2 import java.io.FileInputStream;
3 import java.io.InputStream;
4
5 public class FileInputStreamDemo1 {
6 public static void main(String[] args) throws Exception{
7 //创建文件字节输入流管道与源文件对象接通
8 //InputStream inputStream = new FileInputStream(new File("file-io-app/src/data.txt")); //多态
9 //简化写法:可以直接定位到文件
10 InputStream inputStream = new FileInputStream("file-io-app/src/data.txt"); //多态
11
12 //读取一个字节返回
13 // int read = inputStream.read();
14 // System.out.print(read + " "); //97
15 // System.out.println((char)read); //a
16 //
17 // int read1 = inputStream.read();
18 // System.out.print(read1 + " "); //98
19 // System.out.println((char)read1); //b
20 //
21 // int read2 = inputStream.read();
22 // System.out.print(read2 + " "); //99
23 // System.out.println((char)read2); //c
24 //
25 // int read3 = inputStream.read();
26 // System.out.print("读取不到返回:" + read3); // -1
27
28 //循环:弊端 --> 读取中文时会出现乱码:按照3个字节来读取<会截断原有的字节数据>
29 int read = 0;
30 while ((read = inputStream.read()) != -1){
31 System.out.print((char) read);
32 }
33 }
34 }
- 文件字节输入流(FileInputStream):每次读取一个字节数组
1 import java.io.FileInputStream;
2 import java.io.InputStream;
3
4 //使用文件字节输入流每次读取一个字节数组的形式
5 public class FileInputStreamDemo2 {
6 public static void main(String[] args) throws Exception{
7 //创建文件字节输入流管道与源文件接通
8 InputStream inputStream = new FileInputStream("file-io-app/src/data02.txt");
9 //定义字节数组,用于读取字节数组
10 byte[] bytes = new byte[3]; //声明字节数组大小,即每次读取的字节数
11
12 // int read = inputStream.read(bytes);
13 // System.out.println(read);
14 // //解码(UTF-8)
15 // String str = new String(bytes);
16 // System.out.println(str);
17 //
18 // int len1 = inputStream.read(bytes);
19 // System.out.println(len1);
20 // //解码(UTF-8)
21 // String str1 = new String(bytes);
22 // System.out.println(str1);
23 //
24 // int len2 = inputStream.read(bytes);
25 // System.out.println(len2);
26 // //解码(UTF-8)
27 // String str2 = new String(bytes);
28 // System.out.println(str2);
29 //
30 // //读取几个倒几个
31 // int len3 = inputStream.read(bytes);
32 // System.out.println(len3);
33 // //解码(UTF-8)
34 // String str3 = new String(bytes,0,len3);
35 // System.out.println(str3);
36 //
37 // int len4 = inputStream.read(bytes);
38 // System.out.println(len4);
39
40 //改进:使用循环,每次读取一个字节数组
41 //优点:性能提升(相较于一个一个字节而言,使用字节数组更快)、做复制时没问题
42 //弊端:中英文混合时会发生乱码
43 int length = 0;
44 while ((length = inputStream.read(bytes,0,3)) != -1){
45 String str = new String(bytes,0,length);
46 System.out.print(str);
47 }
48 }
49 }
示例代码运行结果:
示例代码之所以没有出现乱码的原因:abc刚好够一个字节数组,而一个中文又占3个字节,最后两个字符12读取多少个截取多少个。
- 文件字节输入流:一次读完全部的字节
如何使用字节输入流读取中文内容不乱码?
定义一个与文件一样大的字节数组,一次性读完文件的所有字节。不过这样做也有相应的问题:如果文件过大,字节数组可能引起内存溢出。
- 方式一:自己定义一个与文件一样大的字节数组
- 方式二:官方提供了readAllBytes()的API 【JDK9开始】
1 public class FileInputStreamDemo3 {
2 public static void main(String[] args) throws Exception{
3 File file = new File("file-io-app/src/data03.txt");
4 //创建文件字节输入流管道与源文件接通
5 InputStream inputStream = new FileInputStream(file);
6 //定义字节数组,用于读取字节数组
7 int length = (int)file.length();
8 byte[] bytes = new byte[length]; //声明字节数组大小,即每次读取的字节数
9 int charNumber = inputStream.read(bytes);
10 String str = new String(bytes,0,length);
11 System.out.println("读取了:" + charNumber + "个字节");
12 System.out.println(str);
13
14 //不过自JDK9开始,Java也提供了可以一次性读完所有文件内容的API
15 // byte[] bytes = inputStream.readAllBytes();
16 // System.out.println(new String(bytes));
17 }
18 }
示例代码运行结果:
- 文件字节输出流:写字节数据到文件
1 import java.io.FileOutputStream;
2
3 public class FileOutputStreamDemo1 {
4 public static void main(String[] args) throws Exception {
5 //1、创建文件字节输出流管道与目标文件接通
6 //覆盖管道:先清空之前的管道,在写入新的数据
7 FileOutputStream outputStream = new FileOutputStream("file-io-app/src/out04.txt");
8 //如果想追加管道,只需在构造器后多加一个参数true
9 //FileOutputStream outputStream = new FileOutputStream("file-io-app/src/out04.txt",true);
10 //2、写数据出去
11 outputStream.write('a');
12 outputStream.write(98); //对应字符的ASCII码值
13 outputStream.write('c');
14 outputStream.write("\r\n".getBytes()); //换行
15 //写入中文会发生乱码的原因:默认只能写一个字节,而一个中文占3个字节,写入中文时会强行地把中文字符的第一个字节截断
16 //outputStream.write('人');
17 //以GBK编码的形式写入出现乱码的原因:编码用的时GBk,解码时用的时UTF-8
18 // byte[] bytes = "闻道有先后".getBytes("GBK");
19 // outputStream.write(bytes);
20
21 //3、写一个字节数组的一部分出去
22 byte[] bytes = {'a',97,98,99}; //只写前三个
23 outputStream.write(bytes,0,3);
24
25 //刷新数据【写数据必须刷新数据】
26 outputStream.flush(); //刷新后流还可以继续用
27 outputStream.close(); //关闭后流就不可以用了,且关闭之前会自动刷新流
28 }
29 }
data04.txt文件内容:
- 使用字节输入、输出流做文件的拷贝
1 import java.io.*;
2
3 //使用字节流完成文件的复制
4 public class CopyDemo5 {
5 public static void main(String[] args) {
6 //创建字节输入流管道与原视频接通
7 try {
8 InputStream inputStream = new FileInputStream("D:\\tencent\\QQ\\MobileFile\\class_for_183.mp4");
9 //创建字节输出流管道与目标文件接通
10 OutputStream outputStream = new FileOutputStream("D:\\tencent\\QQ\\MobileFile\\new_class_for_183.mp4");
11 //定义一个字节数组转移数据
12 byte[] bytes = new byte[1024];
13 int len = 0; //len 用于记录每次读取到的数据个数(读多少倒多少,防止最后的一组数据装不满)
14 while ((len = inputStream.read(bytes)) != -1 ){
15 outputStream.write(bytes,0,len);
16 }
17 System.out.println("复制完成");
18 //关闭流
19 inputStream.close();
20 outputStream.close();
21 } catch (Exception e) {
22 e.printStackTrace();
23 }
24 }
25 }
因为任何文件的底层都是字节,拷贝则是对这些字节进行转移,只要前后文件格式、编码一致就没有任何问题。
- 资源释放的两种方式(第一种):try-catch-finally
因此,对于上述代码建议更改为下列模式的写法:
1 import java.io.*;
2
3 public class TryCatFinallyDemo1 {
4 public static void main(String[] args) {
5 //把对象定义到上边
6 InputStream inputStream = null;
7 OutputStream outputStream = null;
8 //创建字节输入流管道与原视频接通
9 try {
10 //以防出现在代码块上边出现异常导致两个管道都为null ,如:此处出现 System.out.prinln(10 / 0);
11 inputStream = new FileInputStream("D:\\tencent\\QQ\\MobileFile\\class_for_183.mp4");
12 //创建字节输出流管道与目标文件接通
13 outputStream = new FileOutputStream("D:\\tencent\\QQ\\MobileFile\\new_class_for_183.mp4");
14 //定义一个字节数组转移数据
15 byte[] bytes = new byte[1024];
16 int len = 0; //len 用于记录每次读取到的数据个数(读多少倒多少,防止最后的一组数据装不满)
17 while ((len = inputStream.read(bytes)) != -1 ){
18 outputStream.write(bytes,0,len);
19 }
20 System.out.println("复制完成");
21
22
23 } catch (Exception e) {
24 e.printStackTrace();
25 }finally {
26 //关闭流
27 try {
28 if (inputStream != null) inputStream.close();
29 } catch (IOException e) {
30 e.printStackTrace();
31 }
32 try {
33 if (outputStream != null) outputStream.close();
34 } catch (IOException e) {
35 e.printStackTrace();
36 }
37 }
38 }
39 }
System.exit(0) ; //try代码块里边干掉虚拟机,finall代码块就不会执行了
- 资源释放的两种方式(方式二):try-with-resource
正如上述代码所示(27~36行):如果要关闭两个管道的流,就需要写两个try - catch代码块,写法较为繁琐。
1 import java.io.*;
2
3 public class TryWithResourceDemo1 {
4 public static void main(String[] args) {
5 //创建字节输入流管道与原视频接通
6 try(
7 //这里边防止资源对象,用完后会自动关闭(即使出现异常)
8 //以防出现在代码块上边出现异常导致两个管道都为null ,如:此处出现 System.out.prinln(10 / 0);
9 InputStream inputStream = new FileInputStream("D:\\tencent\\QQ\\MobileFile\\dest_video.mp4");
10 //创建字节输出流管道与目标文件接通
11 OutputStream outputStream = new FileOutputStream("D:\\tencent\\QQ\\MobileFile\\dest_video.mp4");
12
13 Myconnection myconnection = new Myconnection()
14 //int age = 9; //这样的代码防止在这里是不被允许的,只能放置资源,而所谓的资源,就是实现了AutoCloseable接口
15 ) {
16 //定义一个字节数组转移数据
17 byte[] bytes = new byte[1024];
18 int len = 0; //len 用于记录每次读取到的数据个数(读多少倒多少,防止最后的一组数据装不满)
19 while ((len = inputStream.read(bytes)) != -1 ){
20 outputStream.write(bytes,0,len);
21 }
22 System.out.println("复制完成");
23 } catch (Exception e) {
24 e.printStackTrace();
25 }
26 }
27 }
28
29 class Myconnection implements AutoCloseable{
30 //重写接口里边的close方法
31 @Override
32 public void close() throws Exception {
33 //此方法被调用了就说明是下了AutoCloseable的接口就会自动调用此处的close方法,也就是释放资源
34 System.out.println("资源被成功释放!");
35 }
36 }
JDK9提供的释放资源的方式:
这样做就很尴尬了,虽说把资源管道定义在了try代码块外边,但是外边的管道又需要把异常抛出,那么既如此,而下边的try-catch代码块就是用来捕获异常的,就起不到作用了~
- 文件字符输入流、文件字符输出流
- 一个一个字符进行读取
1 import java.io.FileReader;
2 import java.io.Reader;
3
4 public class FileReaderDemo1 {
5 public static void main(String[] args) throws Exception{
6 //每次读取一个字符
7 //1、创建一个字符输入流管道与源文件对象接通
8 Reader reader = new FileReader("file-io-app\\src\\data05.txt");
9
10 //2、每次读取一个字符
11 // int len1= reader.read(); //返回的是对应字符的编号,没有可读的字符时返回-1
12 // System.out.println("读取到字符编号:" + len1); //49
13 // System.out.println("读取到的字符:" + (char) len1); //1
14 // System.out.println("------------------------");
15 // int len2 = reader.read();
16 // System.out.println("读取到字符编号:" + len2); //46
17 // System.out.println("读取到的字符:" + (char) len2); //.
18 // System.out.println("------------------------");
19 // int len3 = reader.read();
20 // System.out.println("读取到字符编号:" + len3); //32
21 // System.out.println("读取到的字符:" + (char) len3); //空格
22
23 //使用循环的方式读取
24 int len = 0;
25 while ((len = reader.read()) != -1){
26 System.out.print( (char) len) ;
27 }
28 }
29 }
data05.txt文件内容:
示例程序运行结果:
注:在此,需要指出的一个小小的点是 -- 有人可能会问,char类型的数据不是底层占2个字节吗,而在Unicode编码中,中文字符是占3个字节的,为什么可以用char来读取中文字符呢?需要阐明的是:这个地方使用的是字符输入流,也就是说 int len = reader.read(),其中 len 作为read 的返回值,源码中是这样描述的:The number of characters read, or -1 if the end of the stream has been reached
即:读取到的字符数,如果到流的末尾了则返回 -1 。也就是说,字符流读取数据是以字符为最小单位进行读取的,读取到的是字符(所代表的Unicode编码值),只是最终结果把该字符的编码值强制转换成了实际所代表值。
- 字符数组进行读取
1 import java.io.FileReader;
2 import java.io.Reader;
3
4 //一个一个字符数组读取
5 public class FileReaderDemo2 {
6 public static void main(String[] args) throws Exception{
7 //1、创建文件字节输入流管道与源文件接通
8 Reader reader = new FileReader("file-io-app/src/data06.txt");
9
10 //2、声明循环,每次读取一个字符数组的数据
11 char[] buffer = new char[1024]; //每次读取1k的数据
12
13 //3、定义一个len记录每次读取到的字符个数
14 int len = 0;
15
16 //4、循环读取
17 while ((len = reader.read(buffer)) != -1){
18 String str = new String(buffer,0,len);
19 System.out.print(str);
20 }
21 }
22 }
data06.txt文档内容(这里为了省事就直接把FileReaderDemo1中的代码粘贴进去了)就不在此展示了,直接展示程序运行结果:
- 文件字符输出流
1 import java.io.FileWriter;
2 import java.io.Writer;
3
4 public class FileWriteDemo3 {
5 public static void main(String[] args) throws Exception{
6 //覆盖管道:每次运行都会清空之前的数据(输出流如果不存在out08.txt会自动创建)
7 Writer writer = new FileWriter("file-io-app/src/out08.txt");
8 //追加管道:每次运行都是追加之前的数据,对源数据不会造成影响
9 //Writer writer = new FileWriter("file-io-app/src/out08.txt", true);
10
11 //1、写一个字符
12 writer.write(97);
13 writer.write(98);
14 writer.write('鹏');
15 writer.write("\r\n"); //换行
16
17 //2、写一个字符串
18 writer.write("闻道有先后,术业有专攻");
19 writer.write("\r\n");
20
21 //3、写一个字符数组出去
22 //char[] chars = {'J','a','v','a'};
23 char[] chars = "Java".toCharArray();
24 writer.write(chars);
25 writer.write("\r\n");
26
27 //4、写字符串数组的一部分出去
28
29 /**
30 * off : 开始写入字符的偏移量,(可以理解为字符出的下标)
31 * len : 写入的字符个数
32 */
33
34 writer.write("一分耕耘一分收获",0,4);
35 writer.write("\r\n");
36
37 //5、写字符数组的一部分
38 char[] chars1 = "一二三四五六七八九十".toCharArray();
39 writer.write(chars1,2,5); // 三四五六七
40 writer.write("\r\n");
41
42 //刷新流
43 //writer.flush(); //刷新后流还可以继续使用
44 //关闭流
45 writer.close(); //关闭(会自动刷新流)后流就不可以使用了
46 }
47 }
示例程序运行后 out08.txt 的文档内容: