目录
IO流
IO流的分类
IO流的体系
字节流:
1、Filelnputstream(文件字节输入流)
2、FileOutputStream(文件字节输出流)
字节流非常适合做一切文件的复制操作
复制案例:
try-catch-finally 和 try-with-resource
字符流
1、FileReader(文件字符输入流)
2、FileWriter(文件字符输出流)
字节流、字符流的使用场景小结:
缓冲流
BufferedReader(字符缓冲输入流)
BufferedWriter(字符缓冲输出流)
不同编码读取出现乱码的问题
转换流
InputStreamReader(字符输入转换流)
OutputStreamWriter字符输出转换流
打印流
PrintStream/PrintWriter(打印流)
Printstream提供的打印数据的方案
PrintWriter提供的打印数据的方案
PrintStream和PrintWriter的区别
打印流的一种应用:输出语句的重定向
数据流
DataOutputstream(数据输出流)
Datalnputstream(数据输入流)
序列化流
Objectoutputstream(对象字节输出流)
Objectlnputstream(对象字节输入流)
IO流
- 读写数据的输入输出流
- I指Input,称为输入流:负责把数据读到内存中去
- O指Output,称为输出流:负责写数据出去
IO流的分类
IO流总体看来分为四大流
- 字节输入流:以内存为基准,来自磁盘文件/网络中的数据以字节的形式读入到内存中去的流
- 字节输出流:以内存为基准,把内存中的数据以字节写出到磁盘文件或者网络中去的流
- 字符输入流:以内存为基准,来自磁盘文件/网络中的数据以字符的形式读入到内存中去的流
- 字符输出流:以内存为基准,把内存中的数据以字符写出到磁盘文件或者网络介质中去的流
IO流的体系
字节流:
1、Filelnputstream(文件字节输入流)
- 作用:以内存为基准,可以把磁盘文件中的数据以字节的形式读入到内存中去
代码演示:
abc.txt内容
一个一个字节读 和 使用循环一个一个字节读:
import java.io.*;public class IoTest1 {public static void main(String[] args) throws IOException {//1、创建文件字节输入流管道,与源文件接通//InputStream fileInputStream = new FileInputStream(new File("file_io\\src\\com\\zeyu\\abc.txt")); new File可省略InputStream fileInputStream = new FileInputStream("file_io\\src\\com\\zeyu\\abc.txt");//2、读取文件字节数据//public int read() 每次读取一个字节返回,如果没有数据了,返回-1
// int b1 = fileInputStream.read();
// System.out.println((char)b1);
//
// int b2 = fileInputStream.read();
// System.out.println((char)b2);//使用循环读int b;while((b = fileInputStream.read()) != -1){System.out.print((char)b);}//读取数据的性能很差!//读取汉字输出会乱码!!无法避免的!!//流使用完毕之后,必须关闭!释放系统资源!fileInputStream.close();}
}
运行结果:
为什么会乱码?
因为中文字符在utf-8里面占三个字节,而每次读取一个字节输出显然是解码不出中文的,只会读取中文字符三个字节中的一个字节
用字节数组读 和 结合循环用字节数组读:
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;public class IoTest2 {public static void main(String[] args) throws IOException {InputStream fileInputStream = new FileInputStream("file_io\\src\\com\\zeyu\\abc.txt");//读取文件中的字节数据,每次读取多个字节// public int read(byte b[])throws IOException//每次读取多个字节到字节数组中去,返回读取的字节数量,读取完毕会返回-1
// byte[] buffer = new byte[3];
// int len = fileInputStream.read(buffer); //返回读取了多少个字节
// System.out.println(new String(buffer));
// System.out.println(len);
//
// int len2 = fileInputStream.read(buffer);
// //注意:读取多少,倒出多少
// System.out.println(new String(buffer,0,len2)); //倒出0到len2的字节
// System.out.println(len2);
//
// int len3 = fileInputStream.read(buffer);
// System.out.println(len3); //-1//使用循环改造int len;byte[] buffer = new byte[3];while((len = fileInputStream.read(buffer)) != -1){System.out.print(new String(buffer, 0, len));} //性能得到了明显的提升//这种方案也不能避免中文乱码问题fileInputStream.close();}
}
运行结果:
即使每次读取三个字节,依旧无法避免乱码的可能,因为若读取的字符串排列为前面俩个英文接一个中文,就会读取到前面俩个英文的(英文数字在utf-8中占一个字节)字节和中文的其中一个字节
一次性读取全部字节:
package com.zeyu.Io;import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;public class IoTest3 {public static void main(String[] args) throws IOException {//一次性读取完文件的全部字节InputStream is = new FileInputStream("file_io\\src\\com\\zeyu\\abc2.txt");//准备一个字节数组,大小与文件的大小一样大
// File f = new File("file_io\\src\\com\\zeyu\\abc2.txt");
// long size = f.length();
// byte[] buffer = new byte[(int)size];
//
// int len = is.read(buffer);
//
// System.out.println(new String(buffer));
// System.out.println(len);
// System.out.println(size);byte[] buffer = is.readAllBytes(); //readAllBytes() 直接把文件的全部字节读取到一个字节数组中返回System.out.println(new String(buffer));}
}
运行结果:
一次读取完文件的所有字节,当然就不会出现乱码问题了,但是文件过大依旧可能出现内存溢出问题
2、FileOutputStream(文件字节输出流)
- 作用:以内存为基准,把内存中的数据以字节的形式写出到文件中去
代码演示:
import java.io.FileOutputStream;
import java.io.IOException;public class IoTest4 {public static void main(String[] args) throws IOException {//创建一个字节输出流管道与目标文件接通FileOutputStream os = new FileOutputStream("file_io\\src\\com\\zeyu\\abc3.txt");//写字节出去os.write(97); //97 为英文字母a的ASCII码os.write('b');//os.write('泽'); 会乱码,中文在utf-8中占3个字节,默认只能写出去一个字节byte[] bytes = "重返蓝鲸岛abc".getBytes();os.write(bytes); //直接写入字节数组就没有问题os.write("\r\n".getBytes()); //\r\n 换行os.write(bytes,0,15); //写入指定长度os.close();FileOutputStream os2 = new FileOutputStream("file_io\\src\\com\\zeyu\\abc3.txt",true); //后面写true,追加数据,不覆盖os2.write(bytes);os2.close();}
}
abc3.txt内容:
字节流非常适合做一切文件的复制操作
- 任何文件的底层都是字节,字节流做复制,是一字不漏的转移完全部字节,只要复制后的文件格式一致就没问题!
复制案例:
将c盘的一张图片复制到d盘
package com.zeyu.exercise;import java.io.*;public class copyFile {public static void main(String[] args) throws Exception {InputStream is = new FileInputStream("C:\\Users\\dzy\\Pictures\\Camera Roll\\六花.jpg"); //原位置OutputStream os = new FileOutputStream("D:\\File\\六花\\六花.jpg"); //目标位置byte[] buffer = new byte[1024]; //每次1kbint len; //记录读出多少while((len = is.read(buffer)) != -1){os.write(buffer,0,len); //取多少倒多少}is.close();os.close();}
}
try-catch-finally 和 try-with-resource
如果在使用完IO流之后不关闭(close),可能会导致资源泄漏和引起文件锁定等问题。所以要在使用完后关闭流
假如在执行close语句之前有语句出现异常,那么自然也不会执行close语句,也就不会关闭流了
例如:
运行结果:
可以看到System.out.println(10/0) 这句语句显然是有问题的,而代码执行到这句语句时就直接终止了,后面的语句都没有执行,也就没有关闭流。鉴于这种情况,我们可以用try-catch-finally 或 try-with-resource来解决:
try-catch-finally:
package com.zeyu.Io;import java.io.FileOutputStream;
import java.io.IOException;public class try_catch_finally {public static void main(String[] args) {FileOutputStream os = null;try {//创建一个字节输出流管道与目标文件接通os = new FileOutputStream("file_io\\src\\com\\zeyu\\abc3.txt");//写字节出去os.write(97);os.write('b');//os.write('泽'); 会乱码,中文在utf-8中占3个字节,默认只能写出去一个字节byte[] bytes = "重返蓝鲸岛abc".getBytes();os.write(bytes); //直接写入字节数组就没有问题os.write("\r\n".getBytes()); //\r\n 换行os.write(bytes,0,15); //写入指定长度} catch (IOException e) {e.printStackTrace();} finally {//释放资源的操作try {if(os != null) os.close();} catch (IOException e) {e.printStackTrace();}}}
}
在try-catch-finally中无论代码是否报错,它都会在结束之前执行finally里面的语句,除非在其它地方使用System.exit()关闭了jvm,所以使用try-catch-finally方式是肯定会释放资源执行close的
注意:finally中不要写return语句,不然它先执行finally的return从而忽略真正的return语句
try-with-resource:
package com.zeyu.Io;import java.io.FileOutputStream;
import java.io.IOException;public class try_with_resource {public static void main(String[] args) {try( //自动关闭在此创建的资源对象,调用其close方法//创建一个字节输出流管道与目标文件接通FileOutputStream os = new FileOutputStream("file_io\\src\\com\\zeyu\\abc3.txt");//注意:这里只能放资源对象//资源对象就是会实现AutoCloseable接口的对象) {//写字节出去os.write(97);os.write('b');//os.write('泽'); 会乱码,中文在utf-8中占3个字节,默认只能写出去一个字节byte[] bytes = "重返蓝鲸岛abc".getBytes();os.write(bytes); //直接写入字节数组就没有问题os.write("\r\n".getBytes()); //\r\n 换行os.write(bytes,0,15); //写入指定长度os.close();} catch (IOException e) {e.printStackTrace();}}
}
显然,这是一种比try-catch-finally更方便的方法,只要在try后面加个括号,里面写上需要释放的资源的定义语句,那么它就会自动释放其中的资源
字符流
1、FileReader(文件字符输入流)
- 作用:以内存为基准,可以把文件中的数据以字符的形式读入到内存中去
代码演示:
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.Reader;public class IO_charTest1 {public static void main(String[] args) {try ( // 1、创建一个文件字符输入流管道与源文件接通Reader r = new FileReader("file_io\\src\\com\\zeyu\\z.txt");){//读取文本文件内容//每次读取一个字符(性能较差,每次读取都要进行系统调用)
// int c;
// while((c = r.read()) != -1){
// System.out.print((char)c);
// }//每次读取多个字符//性能不错(每次读取多个,进行系统调用的次数就少了)int len;char[] buffer = new char[3];while((len = r.read(buffer)) != -1){System.out.print(new String(buffer,0,len));}} catch (Exception e) {e.printStackTrace();}}
}
运行结果:
2、FileWriter(文件字符输出流)
- 作用:以内存为基准,把内存中的数据以字符的形式写出到文件中去
字特输出流使用时的注意事项
- 字符输出流写出数据后,必须刷新流,或者关闭流,写出去的数据才能生效
代码演示:
import java.io.FileWriter;
import java.io.Writer;public class IO_charTest2 {public static void main(String[] args) {try ( // 0、创建一个文件字符输出流管道与目标文件接通。Writer w = new FileWriter("file_io\\src\\com\\zeyu\\z2.txt"/*,true*/); //后面加true写入由覆盖变为追加){// 1、public void write(int c):写一个字符出去w.write('l');// 2、public void write(string c)写一个字符串出去w.write("ove");w.write("\r\n"); //换行// 3、public void write(string c,int pos ,int len):写字符串的一部分出去w.write("hate you",5,3);w.write("\r\n");// 4、public void write(char[] buffer):写一个字符数组出去char[] buffer = {'当','花','叶','呢','喃','之','时',',','请','呼','唤','我','的','名','字'};w.write(buffer);w.write("\r\n");//5、public void write(char[] buffer ,int pos ,int len):写字符数组的一部分出去w.write(buffer,8,7);w.flush();}catch (Exception e){e.printStackTrace();}}
}
运行结果:
字节流、字符流的使用场景小结:
- 字节流适合做一切文件数据的拷贝(音视频,文本);字节流不适合读取中文内容输出
- 字符流适合做文本文件的操作(读,写)
缓冲流
- 对原始流进行包装,以提高原始流读写数据的性能的流
- 提高字节流读写数据的性能
- 原理:字节缓冲输入流自带了8KB缓冲池;字节缓冲输出流也自带了8KB缓冲池
构造器:
将字节流包装成字节缓冲流,再使用字节缓冲流做操作效率会更快
代码演示:
package com.zeyu.IO_byte_buffer;import java.io.*;public class IO_byte_bufferTest1 {public static void main(String[] args) {try (OutputStream os = new FileOutputStream("file_io\\src\\com\\zeyu\\b.txt");OutputStream bos = new BufferedOutputStream(os/*,8192*2*/); //字节缓冲输出流,可以在后面自定义缓冲池大小InputStream is = new FileInputStream("file_io\\src\\com\\zeyu\\b.txt");InputStream bis = new BufferedInputStream(is/*,8192*/); //字节缓冲输入流,可以在后面自定义缓冲池大小){bos.write("由此开启那飘零于时间里的故事".getBytes());bos.flush();byte[] buffer = new byte[1024];int len;while((len = bis.read(buffer)) != -1){System.out.println(new String(buffer,0,len));}} catch (Exception e) {e.printStackTrace();}}
}
运行结果:
BufferedReader(字符缓冲输入流)
- 作用:自带8K(8192)的字符缓冲池,可以提高字符输入流读取字符数据的性能
- 字符缓冲输入流新增的功能:按照行读取字符
代码演示:
package com.zeyu.IO_char;import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.Reader;public class IO_charTest1 {public static void main(String[] args) {try ( // 1、创建一个文件字符输入流管道与源文件接通Reader r = new FileReader("file_io\\src\\com\\zeyu\\z.txt");){//读取文本文件内容//每次读取一个字符(性能较差,每次读取都要进行系统调用)
// int c;
// while((c = r.read()) != -1){
// System.out.print((char)c);
// }//每次读取多个字符//性能不错(每次读取多个,进行系统调用的次数就少了)int len;char[] buffer = new char[3];while((len = r.read(buffer)) != -1){System.out.print(new String(buffer,0,len));}} catch (Exception e) {e.printStackTrace();}}
}
运行结果:
BufferedWriter(字符缓冲输出流)
- 作用:自带8K的字符缓冲池,可以提高字符输出流写字符数据的性能
- 字符缓冲输出流新增的功能:换行
代码演示:
package com.zeyu.IO_char;import java.io.FileWriter;
import java.io.Writer;public class IO_charTest2 {public static void main(String[] args) {try ( // 0、创建一个文件字符输出流管道与目标文件接通。Writer w = new FileWriter("file_io\\src\\com\\zeyu\\z2.txt"/*,true*/); //后面加true写入由覆盖变为追加){// 1、public void write(int c):写一个字符出去w.write('l');// 2、public void write(string c)写一个字符串出去w.write("ove");w.write("\r\n"); //换行// 3、public void write(string c,int pos ,int len):写字符串的一部分出去w.write("hate you",5,3);w.write("\r\n");// 4、public void write(char[] buffer):写一个字符数组出去char[] buffer = {'当','花','叶','呢','喃','之','时',',','请','呼','唤','我','的','名','字'};w.write(buffer);w.write("\r\n");//5、public void write(char[] buffer ,int pos ,int len):写字符数组的一部分出去w.write(buffer,8,7);w.flush();}catch (Exception e){e.printStackTrace();}}
}
运行结果:
不同编码读取出现乱码的问题
- 如果代码编码和被读取的文本文件的编码是一致的,使用字符流读取文本文件时不会出现乱码!
- 如果代码编码和被读取的文本文件的编码是不一致的,使用字符流读取文本文件时就会出现乱码!
转换流
InputStreamReader(字符输入转换流)
- 解决不同编码时,字符流读取文本内容乱码的问题
- 解决思路:先获取文件的原始字节流,再将其按真实的字符集编码转成字符输入流,这样字符输入流中的字符就不乱码了
代码演示:
c.txt为GBK编码
import java.io.*;public class IO_transFromTest1 {public static void main(String[] args) {try (//1、得到文件的原始字节流(GBK的字节流形式)InputStream is = new FileInputStream("file_io\\src\\com\\zeyu\\c.txt");//2、把原始的字节输入流按照指定的字符集编码转换成字符输入流Reader isr = new InputStreamReader(is,"GBK");//把字符输入流包装成缓冲字符输入流BufferedReader br = new BufferedReader(isr);){String line;while((line = br.readLine()) != null){System.out.println(line);}} catch (Exception e) {e.printStackTrace();}}
}
OutputStreamWriter字符输出转换流
- 作用:可以控制写出去的字符使用什么字符集编码
- 解决思路:获取字节输出流,再按照指定的字符集编码将其转换成字符输出流,以后写出去的字符就会用该字符集编码了
代码演示:
package com.zeyu.transfrom;import java.io.*;public class IO_transFromTest2 {public static void main(String[] args) {try (//1、得到文件的原始字节流(GBK的字节流形式)OutputStream os = new FileOutputStream("file_io\\src\\com\\zeyu\\c2.txt");//2、把原始的字节输入流按照指定的字符集编码转换成字符输入流Writer osr = new OutputStreamWriter(os,"GBK");//把字符输入流包装成缓冲字符输入流BufferedWriter bw = new BufferedWriter(osr);){osr.write("I’m a little used to calling outside your name\n" +"我有些习惯在外面大喊你的名字");} catch (Exception e) {e.printStackTrace();}}
}
运行结果:
c2.txt为GBK编码
也可调用String提供的getBytes方法控制写出去的字符使用什么字符集编码
打印流
PrintStream/PrintWriter(打印流)
- 作用:打印流可以实现更方便、更高效的打印数据出去,能实现打印啥出去就是啥出去
Printstream提供的打印数据的方案
PrintWriter提供的打印数据的方案
演示代码:
package com.zeyu.print;import java.io.PrintStream;public class IO_printTest1 {public static void main(String[] args) {try (PrintStream ps = new PrintStream("file_io\\src\\com\\zeyu\\print.txt"/*, Charset.forName("GBK")*/); //指定字符集编码写入
// PrintWriter ps = new PrintWriter("file_io\\src\\com\\zeyu\\print.txt"/*, Charset.forName("GBK")*/);//打印与PrintStream基本一致,PrintStream支持写字节数据,PrintWriter支持写字符数据
// PrintWriter ps = new PrintWriter(new FileWriter("file_io\\src\\com\\zeyu\\print.txt",true)); //追加写法){ps.println("如果下摆湿掉了的话");ps.println("等待干就行了");ps.println("水滴飞溅 发出声音");ps.println("是你教会了我 不再去害怕");ps.println("紧握住你的手 不放开的话");ps.println(97);ps.println(false);ps.println(3.14);ps.write(97); //'a'} catch (Exception e) {e.printStackTrace();}}
}
运行结果:
PrintStream和PrintWriter的区别
- 打印数据的功能上是一模一样的:都是使用方便,性能高效(核心优势)
- PrintStream继承自字节输出流OutputStream,因此支持写字节数据的方法
- PrintWriter继承自字符输出流Writer,因此支持写字符数据出去
打印流的一种应用:输出语句的重定向
- 可以把输出语句的打印位置改到某个文件中去
代码演示:
package com.zeyu.print;import java.io.PrintStream;public class IO_printTest2 {public static void main(String[] args) {System.out.println("如果下摆湿掉了的话");System.out.println("等待干就行了");try (PrintStream ps = new PrintStream("file_io\\src\\com\\zeyu\\print2.txt");){System.setOut(ps); //把系统默认的打印流对象改成自己的打印流对象System.out.println("水滴飞溅 发出声音");System.out.println("是你教会了我 不再去害怕");System.out.println("紧握住你的手 不放开的话");} catch (Exception e) {e.printStackTrace();}}
}
运行结果:
数据流
DataOutputstream(数据输出流)
- 允许把数据和其类型一并写出去
代码演示:
import java.io.DataOutputStream;
import java.io.FileOutputStream;public class IO_DataOutputTest1 {public static void main(String[] args) {try ( //创建一个数据输出流包装低级的字节输出流DataOutputStream dos = new DataOutputStream(new FileOutputStream("file_io\\src\\com\\zeyu\\data.txt"));){dos.writeBoolean(true);dos.writeInt(777);dos.writeDouble(3.14);dos.writeUTF("缠住吻住春风吹住我吗");} catch (Exception e) {e.printStackTrace();}}
}
运行结果:
Datalnputstream(数据输入流)
- 用于读取数据输出流写出去的数据
代码演示:
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;public class IO_DataInputTest2 {public static void main(String[] args) {try ( //创建一个数据输入流包装低级的字节输入流DataInputStream dis = new DataInputStream(new FileInputStream("file_io\\src\\com\\zeyu\\data.txt"));){//读取的类型顺序要和写入的类型顺序一一对应,不然会出bugSystem.out.println(dis.readBoolean());System.out.println(dis.readInt());System.out.println(dis.readDouble());System.out.println(dis.readUTF());} catch (Exception e) {e.printStackTrace();}}
}
运行结果:
序列化流
- 对象序列化:把Java对象写入到文件中去
- 对象反序列化:把文件里的Java对象读出来
Objectoutputstream(对象字节输出流)
- 可以把Java对象进行序列化:把Java对象存入到文件中去
注意:对象如果要参与序列化,必须实现序列化接口(java.io.Serializable)
代码演示:
package com.zeyu.object;import java.io.*;public class IO_objectOutputTest1 {public static void main(String[] args) {try ( //2、创建一个对象字节输出流包装原始的字节输出流ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("file_io\\src\\com\\zeyu\\Object.txt"));){//1、创建一个java对象User u1 = new User("admin","小白","123456",22);//3、序列化对象到文件中oos.writeObject(u1);} catch (Exception e) {e.printStackTrace();}}
}
运行结果:
Objectlnputstream(对象字节输入流)
- 可以把Java对象进行反序列化:把存储在文件中的Java对象读入到内存中来
代码演示:
package com.zeyu.object;import java.io.FileInputStream;
import java.io.ObjectInputStream;public class IO_ObjectInputTest2 {public static void main(String[] args) {try (//创建一个对象字节输入流管道,包装低级的字节输入流与源文件接通ObjectInputStream ois = new ObjectInputStream(new FileInputStream("file_io\\src\\com\\zeyu\\Object.txt"));){User u = (User) ois.readObject();System.out.println(u);} catch (Exception e) {e.printStackTrace();}}
}
运行结果:
如果想让对象某个成员变量不参与序列化,可以使用transient
如果想要一次性序列化多个对象,可以用集合包装这些对象,然后直接序列化集合对象,ArrayList集合已经实现了序列化接口!