IO流(二)
1.缓冲流
1.1缓冲流概述
缓冲流概述
缓冲流也称为高效流、或者高级流。之前学习的字节流可以称为原始流。
作用:缓冲流自带缓冲区,可以提高原始字节流、字符流读写数据的性能。
缓冲流体系如下图所示。
1.2字节缓冲流
字节缓冲流性能优化原理:
字节缓冲输入流自带8KB缓冲池,直接从缓冲池读取数据,所以性能较好。
字节缓冲输出流自带8KB缓冲池,数据直接写入缓冲池中,写数据性能极高。
字节缓冲流
字节缓冲输入流:BufferedInputStream,提高字节输入流读取数据的性能,读写功能上并无变化。
字节缓冲输出流:BufferedOutputStream,提高字节输出流读取数据的性能,读写功能上并无变化。
字节缓冲流构造器
方法名 |
说明 |
public BufferedInputStream(InputStream is) |
可以把低级的字节输入流包装成一个高级的缓冲字节输入流管道, 从而提高字节输入流读数据的性能 |
public BufferedOutputStream(OutputStream os) |
可以把低级的字节输出流包装成一个高级的缓冲字节输出流,从而提高写数据的性能 |
示例代码如下:
publicstaticvoidmain(String[] args) { try ( // 其中自动调用close()方法// 1.创建一个字节输入流管道与原文件接通InputStreamis=newFileInputStream("day11_io_app2\\src\\2055376.jpg"); // 把原始的文件字节输入流包装成缓冲字节输入流InputStreambis=newBufferedInputStream(is); // 2.创建一个字节输出流管道与目标文件接通OutputStreamos=newFileOutputStream("day11_io_app2\\src\\Copy2055376.jpg"); // 一定要加上目标文件名// 把原始的文件字节输出流包装成缓冲字节输出流OutputStreambos=newBufferedOutputStream(os); ) { // 3.定义一个字节数组,用于文件复制byte[] buffer=newbyte[1024]; // 每次读取1kb的数据// 不能用此种方式,每次循环is.read(buffer)输入流执行了两次,需要定义变量来记录读取长度/* while (is.read(buffer) != -1) {os.write(buffer, 0, is.read(buffer)); // 读多少倒多少}*/// 定义变量记录读取的字节数intlen; while ((len=bis.read(buffer)) !=-1) { bos.write(buffer, 0, len); // 读多少倒多少 } System.out.println("复制结束"); } catch (Exceptione) { e.printStackTrace(); } }
注:字节缓冲流的功能如何调用?
public BufferedOutputStream(OutputStream os)
public BufferedInputStream(InputStream is)
功能上并无很大变化,性能提升了。
建议使用字节缓冲输入流、字节缓冲输出流,结合字节数组的方式,性能最优。
1.3字符缓冲流
字符缓冲输入流:BufferedReader
作用:提高字符输入流读取数据的性能,除此之外多了按照行读取数据的功能。
字符缓冲输入流构造器
方法名 |
说明 |
public BufferedReader (Reader r) |
可以把低级的字符输入流包装成一个高级的缓冲字符输入流管道,从而提高字符输入流读数据的性能 |
字符缓冲输入流新增功能
方法名 |
说明 |
public String readLine() |
读取一行数据返回,将读取的字符串返回, 如果读取没有完毕,无行可读返回null |
示例代码如下:
publicstaticvoidmain(String[] args) { try ( Readerr=newFileReader("day11_io_app2\\src\\data.txt"); // 把文件字符输入流包装成缓冲字符输入流BufferedReaderbr=newBufferedReader(r); ) { /* char[] buffer = new char[3]; // 每次读取三个字符int len; // 定义变量记录读取字符数while ((len = br.read(buffer)) != -1) {// 将数组内容以字符串形式拼接起来System.out.print(new String(buffer, 0, len)); // 读取多少拼接多少}*/// 字符缓冲输入流新增功能// public String readLine() 读取一行数据返回,将读取的字符串返回,如果读取没有完毕,无行可读返回null/*System.out.println(br.readLine()); // 我abc我12dsadasd撒大声地所多sdggSystem.out.println(br.readLine()); // abc我12dsadasd撒大声地所多sdggabc我12dsadasd撒大声地所多s的dggsfSDAS是RQ示范法123// 若是空行,返回空,空不是null,无行可读返回nullSystem.out.println(br.readLine()); // null*/Stringline; // 定义读取字符的返回值while ((line=br.readLine()) !=null) { System.out.println(line); // println而不是print,读取一行,不算换行符,需要手动添加换行/*我abc我12dsadasd撒大声地所多sdggabc我12dsadasd撒大声地所多sdggabc我12dsadasd撒大声地所多s的dggsfSDAS是RQ示范法123*/ } } catch (IOExceptione) { e.printStackTrace(); } }
字符缓冲输出流:BufferedWriter
作用:提高字符输出流写取数据的性能,除此之外多了换行功能。
字符缓冲输出流构造器
方法名 |
说明 |
public BufferedWriter(Writer w) |
可以把低级的字符输出流包装成一个高级的缓冲字符输出流管道,从而提高字符输出流写数据的性能 |
字符缓冲输出流新增方法
方法名 |
说明 |
public void newLine() |
换行操作 |
示例代码如下:
publicstaticvoidmain(String[] args) { // 创建一个文件字符输出流管道与目标文件接通try ( // Writer w = new FileWriter("day11_io_app2\src\data_out.txt")Writerw=newFileWriter("day11_io_app2\\src\\data_out.txt", true); // 不清空原文件数据,在其基础上添加数据// 把文件字符输出流包装成缓冲字符输出流BufferedWriterbw=newBufferedWriter(w); ) { // 字符缓冲输出流新增方法//public void newLine() 换行操作bw.newLine(); // 同bw.write("\r\n");bw.write("\r\n"); // 换行// 1.void write(int c) 写一个字符bw.write(97); bw.write('a'); bw.write('中'); // 2.void write(char[] buffer) 写入一个字符数组char[] chars="abc我爱你北京".toCharArray(); bw.write(chars); // 3.void write(char[] buffer, int pos, int len) 写入字符数组的一部分bw.write(chars, 1, 5); // len代表字符数// 4.void write(String str) 写一个字符串bw.write("123我爱你中国"); // 5.void write(String str, int pos, int len) 写一个字符串的一部分bw.write("123我爱你中国", 3, 5); } catch (IOExceptione) { e.printStackTrace(); } }
注:字符缓冲流的功能如何调用?
public BufferedReader(Reader r)
性能提升了,多了readLine()按照行读取的功能。
public BufferedWriter(Writer w)
性能提升了,多了newLine()换行的功能。
修改变量名快捷键:选中该变量->Shift + F6
案例:拷贝文章,恢复顺序
需求:把《出师表》的文章顺序进行恢复到一个新文件中。
分析:
① 定义一个缓存字符输入流管道与源文件接通。
② 定义一个List集合存储读取的每行数据。
③ 定义一个循环按照行读取数据,存入到List集合中去。
④ 对List集合中的每行数据按照首字符编号升序排序。
⑤ 定义一个缓存字符输出管道与目标文件接通。
⑥ 遍历List集合中的每个元素,用缓冲输出管道写出并换行。
示例代码如下:
publicstaticvoidmain(String[] args) { /*需求:把《出师表》的文章顺序进行恢复到一个新文件中。分析:① 定义一个缓存字符输入流管道与源文件接通。② 定义一个List集合存储读取的每行数据。③ 定义一个循环按照行读取数据,存入到List集合中去。④ 对List集合中的每行数据按照首字符编号升序排序。⑤ 定义一个缓存字符输出管道与目标文件接通。⑥ 遍历List集合中的每个元素,用缓冲输出管道写出并换行。*/try ( // 创建一个文件字符输入流管道与源文件接通// Reader r = new FileReader("day11_io_app2\\src\\csb.txt");// 把文件字符输入流包装成缓冲字符输入流// BufferedReader br = new BufferedReader(r);BufferedReaderbr=newBufferedReader(newFileReader("day11_io_app2\\src\\csb.txt")); // 创建一个字符缓冲输出流管道与目标文件接通BufferedWriterbw=newBufferedWriter(newFileWriter("day11_io_app2\\src\\NewCsb.txt")); ) { // 定义一个List集合存储每行内容List<String>data=newArrayList<>(); // 定义循环,按照行读取文章,并将其存储到数组中Stringline; // 定义读取字符的返回值while ((line=br.readLine()) !=null) { data.add(line); // 将读取到的该行数据存储到集合中去 } System.out.println(data); // 排序// 自定义排序规则List<String>sortList=newArrayList<>(); Collections.addAll(sortList, "一", "一", "二", "三", "四", "五", "陆", "柒", "八", "九", "十", "十一"); Collections.sort(data, newComparator<String>() { publicintcompare(Stringo1, Stringo2) { // 根据序号的索引排序,索引越大代表序号越大// 将序号子串取出(序号是开始到"."之前的部分,左闭右开),比较序号returnsortList.indexOf(o1.substring(0, o1.indexOf("."))) -sortList.indexOf(o2.substring(0, o2.indexOf("."))); } }); System.out.println(data); // 变量集合中的每行文章,写出到新文件中,并且需要换行for (Stringdatum : data) { bw.write(datum); // 写每行(集合中每个元素)的数据bw.newLine(); // 换行 } } catch (IOExceptione) { e.printStackTrace(); } }
2.转换流
问题提出:字符流直接读取文本内容
必须文件和代码编码一致才不会乱码。
如果文件和代码编码不一致,读取将出现乱码。
解决办法:使用字符输入转换流
①提取文件(GBK)的原始字节流,原始字节不会存在问题。
②把字节流以指定编码转换成字符输入流,这样字符输入流中的字符就不会发生乱码了。
字符转换流体系如下图所示。
字符输入转换流:InputStreamReader,把原始的字节流按照指定编码转换成字符输入流。
字符输入转换流构造器
方法名 |
说明 |
public InputStreamReader(InputStream is) |
把原始的字节流按照代码默认编码转换成字符输入流。几乎不用, 与默认的FileReader一样 |
public InputStreamReader(InputStream is ,String charset) |
把原始的字节流按照指定字符集编码转换成代码默认编码的字符输入流, 这样字符流中的字符就不乱码了 |
示例代码如下:
publicstaticvoidmain(String[] args) { try ( // 代码UTF-8 文件GBK 直接读取 会出现乱码BufferedReaderbr=newBufferedReader(newFileReader("D:\\360Downloads\\GBKtxt.txt")) ) { Stringline; while ((line=br.readLine()) !=null) { System.out.println(line); // abc�Ұ����й� 乱码 } } catch (IOExceptione) { e.printStackTrace(); } try ( // 代码UTF-8 文件GBK 使用字符输入转换流解决// 1.提取GBK文件的原始字节流InputStreamis=newFileInputStream("D:\\360Downloads\\GBKtxt.txt"); // Reader isr = new InputStreamReader(is); // 以默认的UTF-8转换为字符流,仍然会乱码,与直接使用FileReader效果相同// 2.指定原始字节流的字符集格式,转换为代码默认字符集的字符输入流Readerisr=newInputStreamReader(is, "GBK"); // 以GBK字符集的形式读取字节,转换为字符// 包装为缓冲字符输入流BufferedReaderbr=newBufferedReader(isr); ) { Stringline; while ((line=br.readLine()) !=null) { System.out.println(line); // abc我爱你中国 不乱码 } } catch (IOExceptione) { e.printStackTrace(); } }
如何控制写出去的字符使用的编码?
使用字符输出转换流实现。
字符输出转换流:OutputStreamWriter,把字节输出流按照指定编码转换成字符输出流。
字符输出转换流构造器
方法名 |
说明 |
public OutputStreamWriter(OutputStream os) |
把原始的字节输出流按照代码默认编码转换成字符输出流。几乎不用。 与默认的FileWriter一样 |
public OutputStreamWriter(OutputStream os,String charset) |
把代码默认字符集的字节输出流转换成指定字符集编码字符输出流 |
示例代码如下:
publicstaticvoidmain(String[] args) { try ( // 1.定义一个字节输出流OutputStreamos=newFileOutputStream("day11_io_app2\\src\\outputStreamWriter.txt"); // Writer osw = new OutputStreamWriter(os); // 以代码默认的UTF-8的字符集写出,与直接使用FileWriter效果相同// 2.将代码默认编码格式的字节输出流转换为指定编码格式的字符输出流Writerosw=newOutputStreamWriter(os, "GBK"); // 读取字符,以GBK字符集的形式转换为字节并输出// 包装为缓冲字符输出流BufferedWriterbw=newBufferedWriter(osw); ) { bw.write("我爱你中国"); } catch (IOExceptione) { e.printStackTrace(); } }
3.序列化对象
对象字节流体系如下图所示。
对象序列化
使用到的流是对象字节输出流:ObjectOutputStream。
作用:以内存为基准,把内存中的对象存储到磁盘文件中去,称为对象序列化。
对象字节输出流构造器
方法名 |
说明 |
public ObjectOutputStream(OutputStream out) |
把低级字节输出流包装成高级的对象字节输出流 |
ObjectOutputStream序列化方法
方法名 |
说明 |
public final void writeObject(Object obj) |
把对象写出去到对象序列化流的文件中去 |
示例代码如下:
Student类
publicclassStudentimplementsSerializable { // 声明序列化版本号// 序列化的版本号必须0与反序列化版本号一致privatestaticfinallongserialVersionUID=-3155696393394656215L; privateStringname; privateStringloginName; privateStringloginPassword; privateintage; publicStudent() { } publicStudent(Stringname, StringloginName, StringloginPassword, intage) { this.name=name; this.loginName=loginName; // transient修饰的成员变量不参与序列化privatetransientStringloginPassword; this.age=age; } // setter、getter方法publicStringtoString() { return"Student{"+"name='"+name+'\''+", loginName='"+loginName+'\''+", loginPassword='"+loginPassword+'\''+", age="+age+'}'; } }
测试类
publicclassObjectOutputStreamDemo1 { publicstaticvoidmain(String[] args) { // 1.创建学生对象Studentstu=newStudent("张三", "2022", "123", 20); try ( // 2.对象序列化:使用对象字节输出流包装文件字节输出流管道ObjectOutputStreamoos=newObjectOutputStream(newFileOutputStream("day11_io_app2\\src\\obj.txt")); ) { // 3.调用序列化方法oos.writeObject(stu); } catch (IOExceptione) { e.printStackTrace(); } } }
�� sr #com.itheima.d5_serializable.Student�4�ٌ��) I ageL loginNamet _x0012_Ljava/lang/String;L nameq ~ xp t 2022t 张三
注:实现序列化时,必须要求该对象实现序列化接口(如public class Student implements Serializable)。
实体类中transient修饰的成员变量不参与序列化。
保存对象信息的文件obj.txt查看是乱码是正常现象,目的不是为了查看,而是可以保存对象信息以便以后获取使用。
对象反序列化
使用到的流是对象字节输入流:ObjectInputStream。
作用:以内存为基准,把存储到磁盘文件中去的对象数据恢复成内存中的对象,称为对象反序列化。
对象字节输入流构造器
方法名 |
说明 |
public ObjectInputStream(InputStream out) |
把低级字节输入流包装成高级的对象字节输入流 |
ObjectOutputStream序列化方法
方法名 |
说明 |
public Object readObject() |
把存储到磁盘文件中的对象数据恢复成内存中的对象返回 |
示例代码如下:
publicclassObjectInputStreamDemo2 { publicstaticvoidmain(String[] args) { try ( // 2.对象反序列化:使用对象字节输入流包装文件字节输入流管道ObjectInputStreamois=newObjectInputStream(newFileInputStream("day11_io_app2\\src\\obj.txt")) ) { // 调用反序列化方法Studentstu= (Student) ois.readObject(); System.out.println(stu); } catch (Exceptione) { e.printStackTrace(); } } }
程序运行结果如下:
Student{name='张三', loginName='2022', loginPassword='null', age=20}
注:若出现序列化异常Exception in thread “main” java.io.InvalidClassException: local class incompatible: stream classdesc serialVersionUID = -3155696393394656215, local class serialVersionUID = -2071565876962058344
解决办法:在实体类中统一serialVersionUID,即序列化的版本号必须与反序列化版本号一致。
实体类中transient修饰的成员变量不参与序列化,因此不序列化时信息不存入文件中,获取时相应值为null。
4.打印流
打印流体系如下图所示。
打印流
作用:打印流可以实现方便、高效的打印数据到文件中去。打印流一般是指:PrintStream,PrintWriter两个类。可以实现打印什么数据就是什么数据,例如打印整数97写出去就是97,打印boolean的true,写出去就是true。
PrintStream构造器
方法名 |
说明 |
public PrintStream(OutputStream os) |
打印流直接通向字节输出流管道 |
public PrintStream(File f) |
打印流直接通向文件对象 |
public PrintStream(String filepath) |
打印流直接通向文件路径 |
PrintStream常用API
方法名 |
说明 |
public void print(Xxx xx) |
打印任意类型的数据出去 |
PrintWriter构造器
方法名 |
说明 |
public PrintWriter(OutputStream os) |
打印流直接通向字节输出流管道 |
public PrintWriter (Writer w) |
打印流直接通向字符输出流管道 |
public PrintWriter (File f) |
打印流直接通向文件对象 |
public PrintWriter (String filepath) |
打印流直接通向文件路径 |
PrintWriter常用API
方法名 |
说明 |
public void print(Xxx xx) |
打印任意类型的数据出去 |
示例代码如下:
publicstaticvoidmain(String[] args) { try ( // 创建一个打印流对象// PrintStream ps = new PrintStream(new FileOutputStream("day11_io_app2\\src\\ps.txt", true)); // 追加数据,在低级管道后加truePrintStreamps=newPrintStream("day11_io_app2\\src\\ps.txt"); PrintWriterpw=newPrintWriter("day11_io_app2\\src\\pw.txt"); ) { ps.println(97); ps.println('a'); ps.println("张三"); ps.println(true); pw.println(97); pw.println('a'); pw.println("张三"); pw.println(true); } catch (Exceptione) { e.printStackTrace(); } }
PrintStream和PrintWriter的异同点:
打印数据功能上是一模一样的,都是使用方便,性能高效(核心优势)。
PrintStream继承自字节输出流OutputStream,支持写字节数据的方法。
PrintWriter继承自字符输出流Writer,支持写字符数据出去。
输出语句的重定向
输出语句重定向属于打印流的一种应用,可以把输出语句的打印位置改到文件。
代码格式如下:
PrintStream ps = new PrintStream("文件地址");
System.setOut(ps);
示例代码如下:
publicstaticvoidmain(String[] args) throwsFileNotFoundException { // 默认打印在控制台System.out.println("抽刀断水水更流"); // 改变输出语句位置(重定向)PrintStreamps=newPrintStream("day11_io_app2\\src\\print.txt"); System.setOut(ps); // 将系统默认打印流System.out更改为自定义的打印流ps// 此时系统默认打印位置由控制台更改为ps中的指定文件System.out.println("举杯消愁愁更愁"); }
5.补充知识:Properties
Properties属性集对象
其实就是一个Map集合,但是我们一般不会当集合使用,因为HashMap更好用。
Properties核心作用
Properties代表的是一个属性文件,可以把自己对象中的键值对信息存入到一个属性文件中去。
属性文件:后缀是.properties结尾的文件,里面的内容都是 key=value,后续做系统配置信息的。
Properties和IO流结合的方法
方法名 |
说明 |
void load (InputStream inStream) |
从输入字节流读取属性列表(键和元素对) |
void load (Reader reader) |
从输入字符流读取属性列表(键和元素对) |
void store (OutputStream out, String comments) |
将此属性列表(键和元素对)写入此Properties表中,以适合于使用load(InputStream)方法的格式写入输出字节流 |
void store (Writer writer, String comments) |
将此属性列表(键和元素对)写入此Properties表中,以适合使用load(Reader)方法的格式写入输出字符流 |
public Object setProperty(String key, String value) |
保存键值对(相当于Map集合中的put) |
public String getProperty(String key) |
使用此属性列表中指定的键搜索属性值 (相当于Map集合中的get) |
public Set<String> stringPropertyNames() |
所有键的名称的集合 (相当于Map集合中的keySet()) |
示例代码如下:
publicclassPropertiesDemo1 { publicstaticvoidmain(String[] args) throwsException { Propertiesproperties=newProperties(); // public Object setProperty(String key, String value) 保存键值对(相当于Map集合中的put)properties.setProperty("admin", "123456"); properties.setProperty("张三", "zs123"); properties.setProperty("武松", "dachongxiuzou"); System.out.println(properties); // {admin=123456, 武松=dachongxiuzou, 张三=zs123}/*** 参数一:保存管道,字符输出流管道* 参数二:提醒与注释,可不写("")*/properties.store(newFileWriter("day11_io_app2\\src\\users.properties"), "Cleveland, this is for you"); } } publicclassPropertiesDemo2 { publicstaticvoidmain(String[] args) throwsException { Propertiesproperties=newProperties(); System.out.println(properties); // {}// 加载属性文件中的键值对数据到属性对象properties中去properties.load(newFileReader("day11_io_app2\\src\\users.properties")); System.out.println(properties); // {admin=123456, 武松=dachongxiuzou, 张三=zs123}// public String getProperty(String key) 使用此属性列表中指定的键搜索属性值(相当于Map集合中的get)Stringrs=properties.getProperty("admin"); // 通过键来获取值的信息System.out.println(rs); // 123456// public Set<String> stringPropertyNames() 所有键的名称的集合 (相当于Map集合中的keySet())Set<String>keySet=properties.stringPropertyNames(); System.out.println(keySet); // [admin, 武松, 张三] } }
6.补充知识:IO框架
commons-io概述
commons-io是apache开源基金组织提供的一组有关IO操作的类库,可以提高IO功能开发的效率。
commons-io工具包提供了很多有关io操作的类,有两个主要的类FileUtils和IOUtils。
IOUtils常用API
方法名 |
说明 |
int copy(InputStream inputStream, OutputStream outputStream) |
复制文件 |
FileUtils常用API
方法名 |
说明 |
String readFileToString(File file, String encoding) |
读取文件中的数据, 返回指定格式的字符串 |
void copyFileToDirectory(File srcFile, File destDir) |
复制文件到文件夹 |
void copyFile(File srcFile, File destFile) |
复制文件 |
void copyDirectoryToDirectory(File srcDir, File destDir) |
复制文件夹 |
void deleteDirectory(File directory) |
删除文件夹 |
示例代码如下:
publicstaticvoidmain(String[] args) throwsException { // IOUtils常用API// int copy(InputStream inputStream, OutputStream outputStream) 复制文件IOUtils.copy(newFileInputStream("day11_io_app2\\src\\data.txt"), newFileOutputStream("day11_io_app2\\src\\copyData.txt")); // FileUtils常用API// String readFileToString(File file, String encoding) 读取文件中的数据,返回指定格式的字符串System.out.println(FileUtils.readFileToString(newFile("day11_io_app2\\src\\data.txt"), "UTF-8")); // void copyFile(File srcFile, File destFile) 复制文件FileUtils.copyFile(newFile("day11_io_app2\\src\\data.txt"), newFile("day11_io_app2\\src\\newData2.txt")); // void copyFileToDirectory(File srcFile, File destDir) 复制文件到文件夹FileUtils.copyFileToDirectory(newFile("day11_io_app2\\src\\data.txt"), newFile("D:\\360Downloads")); // void copyDirectoryToDirectory(File srcDir, File destDir) 复制文件夹FileUtils.copyDirectoryToDirectory(newFile("D:\\360Downloads\\abc"), newFile("D:\\360Downloads\\new")); // void deleteDirectory(File directory) 删除文件夹FileUtils.deleteDirectory(newFile("D:\\360Downloads\\new")); }