高级流
流连接示意图
缓冲流
java.io.BufferedOutputStream和BufferedInputStream.
缓冲流是一对高级流,作用是提高读写数据的效率.
缓冲流内部有一个字节数组,默认长度是8K.缓冲流读写数据时一定是将数据的读写方式转换为块读写来保证读写效率.
使用缓冲流完成文件复制操作
package io; import java.io.*; /** * java将流分为节点流与处理流两类 * 节点流:也称为低级流,是真实连接程序与另一端的"管道",负责实际读写数据的流。 * 读写一定是建立在节点流的基础上进行的。 * 节点流好比家里的"自来水管"。连接我们的家庭与自来水厂,负责搬运水。 * 处理流:也称为高级流,不能独立存在,必须连接在其他流上,目的是当数据经过当前流时 * 对其进行某种加工处理,简化我们对数据的同等操作。 * 高级流好比家里常见的对水做加工的设备,比如"净水器","热水器"。 * 有了它们我们就不必再自己对水进行加工了。 * 实际开发中我们经常会串联一组高级流最终连接到低级流上,在读写操作时以流水线式的加工 * 完成复杂IO操作。这个过程也称为"流的连接"。 * * 缓冲流,是一对高级流,作用是加快读写效率。 * java.io.BufferedInputStream和java.io.BufferedOutputStream * */ public class CopyDemo3 { public static void main(String[] args) throws IOException { FileInputStream fis = new FileInputStream("ppt.pptx"); BufferedInputStream bis = new BufferedInputStream(fis); FileOutputStream fos = new FileOutputStream("ppt_cp.pptx"); BufferedOutputStream bos = new BufferedOutputStream(fos); int d; long start = System.currentTimeMillis(); while((d = bis.read())!=-1){//使用缓冲流读取字节 bos.write(d);//使用缓冲流写出字节 } long end = System.currentTimeMillis(); System.out.println("耗时:"+(end-start)+"ms"); bis.close();//关闭流时只需要关闭高级流即可,它会自动关闭它连接的流 bos.close(); } }
缓冲输出流写出数据时的缓冲区问题
通过缓冲流写出的数据会被临时存入缓冲流内部的字节数组,直到数组存满数据才会真实写出一次
package io; import java.io.BufferedOutputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; /** * 缓冲输出流写出数据的缓冲区问题 */ public class BOS_FlushDemo { public static void main(String[] args) throws IOException { FileOutputStream fos = new FileOutputStream("bos.txt"); BufferedOutputStream bos = new BufferedOutputStream(fos); String line = "奥里给!"; byte[] data = line.getBytes(StandardCharsets.UTF_8); bos.write(data); System.out.println("写出完毕!"); /* 缓冲流的flush方法用于强制将缓冲区中已经缓存的数据一次性写出。 注:该方法实际上实在字节输出流的超类OutputStream上定义的,并非只有缓冲 输出流有这个方法。但是实际上只有缓冲输出流的该方法有实际意义,其他的流实现 该方法的目的仅仅是为了在流连接过程中传递flush动作给缓冲输出流。 */ bos.flush();//冲 bos.close(); } }
对象流
java.io.ObjectOutputStream和ObjectInputSteam
对象流是一对高级流,在流连接中的作用是进行对象的序列化与反序列化。
对象序列化:将一个java对象按照其结构转换为一组字节的过程
对象反序列化:将一组字节还原为java对象(前提是这组字节是一个对象序列化得到的字节)
对象序列化的流连接操作原理图:
package io; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; /** * 对象流(是一对高级流) * java.io.ObjectInputStream和ObjectOutputStream * 对象流在流连接中的作用是进行对象的序列化与反序列化 * 其中对象输出流负责对象序列化。对象输入流负责对象反序列化 * * 所谓对象序列化: * 将写出的对象按照其结构转换为一组字节的过程。 */ public class OOSDemo { public static void main(String[] args) throws IOException { String name = "苍老师"; int age = 55; String gender = "男"; String[] otherInfo = {"摄影技术一流","喜欢拍片儿","是一名技术老师"}; //将该Person对象写入文件person.obj中 Person p = new Person(name,age,gender,otherInfo); FileOutputStream fos = new FileOutputStream("person.obj"); ObjectOutputStream oos = new ObjectOutputStream(fos); /* 对象输出流提供了一个直接写出对象的方法(进行对象序列化的操作) void writeObject(Object obj) 序列化时可能出现异常: java.io.NotSerializableException: io.Person 注:冒号后面的io.Person是指序列化的就是这个类的实例出现的错误 原因: 对象输出流在进行序列化对象时,要求该对象所属的类必须实现接口:java.io.Serializable接口 并且该类中所有引用类型属性也必须实现该接口,否则会抛出上述异常。 */ oos.writeObject(p); System.out.println("写出完毕!"); oos.close(); } }
对象反序列化
package io; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.ObjectInputStream; /** * 使用对象输入流完成对象的反序列化 */ public class OISDemo { public static void main(String[] args) throws IOException, ClassNotFoundException { //从person.obj文件中将对象反序列化回来 FileInputStream fis = new FileInputStream("person.obj"); ObjectInputStream ois = new ObjectInputStream(fis); /* Object readObject() 该方法会进行对象的反序列化,如果对象流通过其连接的流读取的字节分析并非 是一个java对象时,会抛出异常:ClassNotFoundException */ Person p = (Person)ois.readObject(); System.out.println(p); } }
需要进行序列化的类必须实现接口:java.io.Serializable 实现序列化接口后最好主动定义序列化版本号这个常量。 这样一来对象序列化时就不会根据类的结构生成一个版本号,而是使用该固定值。 那么反序列化时,只要还原的对象和当前类的版本号一致就可以进行还原。
transient关键字可以修饰属性,用于在进行对象序列化时忽略不必要的属性,达到对象瘦身的目的
package io; import java.io.Serializable; import java.util.Arrays; /** * 使用当前类实例测试对象流的读写操作 */ public class Person implements Serializable { public static final long serialVersionUID = 1L; private String name;//姓名 private int age;//年龄 private String gender;//性别 private String[] otherInfo;//其他信息 public Person(String name, int age, String gender, String[] otherInfo) { this.name = name; this.age = age; this.gender = gender; this.otherInfo = otherInfo; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getGender() { return gender; } public void setGender(String gender) { this.gender = gender; } public String[] getOtherInfo() { return otherInfo; } public void setOtherInfo(String[] otherInfo) { this.otherInfo = otherInfo; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + ", gender='" + gender + '\'' + ", otherInfo=" + Arrays.toString(otherInfo) + '}'; } }
字符流
-
java将流按照读写单位划分为字节流与字符流.
-
java.io.InputStream和OutputStream是所有字节流的超类
-
而java.io.Reader和Writer则是所有字符流的超类,它们和字节流的超类是平级关系.
-
Reader和Writer是两个抽象类,里面规定了所有字符流都必须具备的读写字符的相关方法.
-
字符流最小读写单位为字符(char),但是底层实际还是读写字节,只是字符与字节的转换工作由字符流完成.
转换流
java.io.InputStreamReader和OutputStreamWriter
它们是字符流非常常用的一对实现类同时也是一对高级流,实际开发中我们不直接操作它们,但是它们在流连接中是非常重要的一环.
使用转换输出流向文件中写入文本数据
package io; import java.io.*; import java.nio.charset.StandardCharsets; /** * JAVA IO将流按照读写数据的单位将流分为了两类:字节流与字符流 * java.io.InputStream和OutputStream这两个超类是所有【字节流】的超类 * java.io.Reader和Writer这两个是所有【字符流】的超类 * 这两对超类之间是没有继承关系的,属于平级的。 * * 字符流是以字符为最小单位(char)读写数据的。 * 注:底层实际还是读写字节,只不过字符与字节的转换由字符流自动完成了。 * 由于字符流最小读写单位为字符,因此字符流【只适合读写文本数据】 * * 转换流(是一对高级流,同时是一对字符流) * 作用: * 1:衔接字节流与其他字符流 * 2:将字符与字节相互转换 * 实际开发中我们不会直接使用这一对流,但是在流连接中它是重要的一环。 */ public class OSWDemo { public static void main(String[] args) throws IOException { /* 使用这一对流演示转换流的读写字符方法 java.io.Writer所有字符输出流的超类上,定义了写出字符的相关方法 void write(int d)写出一个字符,实际传入的应当是一个char。 void write(char[] data) void write(char[] data,int offset,int len) void write(String str) 直接写出一个字符串 */ FileOutputStream fos = new FileOutputStream("osw.txt"); OutputStreamWriter osw = new OutputStreamWriter(fos,StandardCharsets.UTF_8); String line = "如果你突然打了个喷嚏,那一定就是我在想你。"; osw.write(line);//转换流的write(String str)会将写出的字符串转换为字节然后写出 osw.write("如果半夜被手机吵醒,那一定就是我关心。"); System.out.println("写出完毕!"); osw.close(); } }
使用转换输入流读取文本文件
package io; import java.io.*; /** * 转换字符输入流 * 可以将读取的字节按照指定的字符集转换为字符 */ public class ISRDemo { public static void main(String[] args) throws IOException { //将osw.txt文件中的所有文字读取回来. FileInputStream fis = new FileInputStream("osw.txt"); InputStreamReader isr = new InputStreamReader(fis,"UTF-8"); /* 字符流读一个字符的read方法定义: int read() 读取一个字符,返回的int值实际上表示的是一个char(低16位有效).如果返回的 int值表示的是-1则说明EOF */ //测试读取文件中第一个字 // int d = isr.read(); // char c = (char)d; // System.out.println(c); //循环将文件所有字符读取回来 int d; while((d = isr.read()) != -1){ System.out.print((char)d); } isr.close(); } }
转换流的意义:
实际开发中我们还有功能更好用的字符高级流.但是其他的字符高级流都有一个共通点:不能直接连接在字节流上.而实际操作设备的流都是低级流同时也都是字节流.因此不能直接在流连接中串联起来.转换流是一对可以连接在字节流上的字符流,其他的高级字符流可以连接在转换流上.在流连接中起到"转换器"的作用(负责字符与字节的实际转换)
缓冲字符流
缓冲字符输出流:java.io.PrintWriter
java.io.BufferedWriter和BufferedReader
缓冲字符流内部也有一个缓冲区,读写文本数据以块读写形式加快效率.并且缓冲流有一个特别的功能:可以按行读写文本数据.
java.io.PrintWriter具有自动行刷新的缓冲字符输出流,实际开发中更常用.它内部总是会自动连接BufferedWriter作为块写加速使用.
package io; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.PrintWriter; import java.io.UnsupportedEncodingException; /** * 缓冲字符流(是一对高级流) * java.io.BufferedWriter和BufferedReader * 缓冲流内部维护一个char数组,默认长度8k.以块读写方式读写字符数据保证效率 * * java.io.PrintWriter则是具有自动行刷新的换成字符输出流(实际缓冲功能是靠BufferedWriter * 实现的,它内部总是连接着这个流。) * * 使用缓冲字符流后就可以实现按行读写字符串,并且读写效率高。 */ public class PWDemo1 { public static void main(String[] args) throws FileNotFoundException, UnsupportedEncodingException { //按行向文件pw.txt中写入字符串 /* PrintWriter继承自Writer. 它提供很多构造方法,其中就有可以直接对文件进行写操作的构造器 PrintWriter(File file) PrintWriter(String filename) */ // PrintWriter pw = new PrintWriter("pw.txt"); /* 这里可以按照指定的字符集写出字符串到文本文件中。但是字符集只能以字符串形式 表达。因此注意拼写。字符集不区分大小写。 但是如果字符集名字拼写错误,会抛出异常: UnsupportedEncodingException 不支持的 字符集 异常 */ PrintWriter pw = new PrintWriter("pw.txt","UTF-8"); /* println()方法是输出字符出后带上换行符 print()方法输出字符串后不带换行符 */ pw.println("夜空中最亮的星,能否听清。"); pw.println("那仰望的人,心底的孤独和叹息。"); System.out.println("写出完毕!"); pw.close(); } }
在流链接中使用PW
package io; import java.io.*; import java.nio.charset.StandardCharsets; import java.util.Scanner; /** * 练习PrintWriter的流连接操作 */ public class PWDemo2 { public static void main(String[] args) throws FileNotFoundException { //文件输出流(低级流,字节流) 作用:向文件中写出字节 FileOutputStream fos = new FileOutputStream("pw2.txt"); //转换输出流(高级流,字符流) 作用:1衔接字符与字节流的 2:将写出的字符转换为字节 OutputStreamWriter osw = new OutputStreamWriter(fos, StandardCharsets.UTF_8); //缓冲字符输出流(高级流,字符流) 作用:块写文本数据加速的(内部有一个8k的char数组) BufferedWriter bw = new BufferedWriter(osw); //具有自动行刷新功能(高级流,字符流) 作用:1按行写出字符串(println) 2:自动行刷新 PrintWriter pw = new PrintWriter(bw); /* 完成一个简易记事本工具 将控制台上输入的每一行字符串按行写入到该文件中 如果单独输入exit,则程序退出。 思路: 用一个死循环,重复做下面的工作 1:在控制台上输入一行字符串 2:判断输入的字符串是否为"exit" 若是:则break掉循环退出程序 若不是:则将输入的字符串通过println方法写入文件 */ Scanner scanner = new Scanner(System.in); while(true) { String line = scanner.nextLine(); if("exit".equals(line)){ break; } pw.println(line); } System.out.println("写出完毕!"); pw.close(); } }
总结
java将流分为两类:节点流与处理流:
-
节点流:也称为低级流.
节点流的另一端是明确的,是实际读写数据的流,读写一定是建立在节点流基础上进行的.
-
处理流:也称为高级流.
处理流不能独立存在,必须连接在其他流上,目的是当数据流经当前流时对数据进行加工处理来简化我们对数据的该操作.
实际应用中,我们可以通过串联一组高级流到某个低级流上以流水线式的加工处理对某设备的数据进行读写,这个过程也成为流的连接,这也是IO的精髓所在.
缓冲流
缓冲流是一对高级流,在流链接中链接它的目的是加快读写效率。缓冲流内部默认缓冲区为8kb,缓冲流总是块读写数据来提高读写效率。
java.io.BufferedOutputStream缓冲字节输出流,继承自java.io.OutputStream
常用构造器
-
BufferedOutputStream(OutputStream out):创建一个默认8kb大小缓冲区的缓冲字节输出流,并连接到参数指定的字节输出流上。
-
BufferedOutputStream(OutputStream out,int size):创建一个size指定大小(单位是字节)缓冲区的缓冲字节输出流,并连接到参数指定的字节输出流上。
常用方法
flush():强制将缓冲区中已经缓存的数据一次性写出 缓冲流的写出方法功能与OutputStream上一致,需要知道的时write方法调用后并非实际写出,而是先将数据存入缓冲区(内部的字节数组中),当缓冲区满了时会自动写出一次。
java.io.BufferedInputStream缓冲字节输出流,继承自java.io.InputStream
常用构造器
-
BufferedInputStream(InputStream in):创建一个默认8kb大小缓冲区的缓冲字节输入流,并连接到参数指定的字节输入流上。
-
BufferedInputStream(InputStream in,int size):创建一个size指定大小(单位是字节)缓冲区的缓冲字节输入流,并连接到参数指定的字节输入流上。
常用方法
缓冲流的读取方法功能与InputStream上一致,需要知道的时read方法调用后缓冲流会一次性读取缓冲区大小的字节数据并存入缓冲区,然后再根据我们调用read方法读取的字节数进行返回,直到缓冲区所有数据都已经通过read方法返回后会再次读取一组数据进缓冲区。即:块读取操作
对象流
对象流是一对高级流,在流链接中的作用是完成对象的序列化与反序列化
序列化:是对象输出流的工作,将一个对象按照其结构转换为一组字节的过程。
反序列化:是对象输入流的工作,将一组字节还原为对象的过程。
java.io.ObjectInputStream对象输入流,继承自java.io.InputStream
常用构造器
ObjectInputStream(InputStream in):创建一个对象输入流并连接到参数in这个输入流上。
常用方法
Object readObject():进行对象反序列化,将读取的字节转换为一个对象并以Object形式返回(多态)。
如果读取的字节表示的不是一个java对象会抛出异常:java.io.ClassNotFoundException
java.io.ObjectOutputStream对象输出流,继承自java.io.OutputStream
常用构造器
ObjectOutputStream(OutputStream out):创建一个对象输出流并连接到参数out这个输出流上
常用方法
void writeObject(Object obj):进行对象的序列化,将一个java对象序列化成一组字节后再通过连接的输出流将这组字节写出。
如果序列化的对象没有实现可序列化接口:java.io.Serializable就会抛出异常:java.io.NotSerializableException
序列化接口java.io.Serrializable
该接口没有任何抽象方法,但是只有实现了该接口的类的实例才能进行序列化与反序列化。
实现了序列化接口的类建议显示的定义常量:static final long serialVersionUID = 1L;
可以为属性添加关键字transient,被该关键字修饰的属性在序列化是会被忽略,达到对象序列化瘦身的目的。
字符流
java将流按照读写单位划分为字节与字符流。字节流以字节为单位读写,字符流以字符为单位读写。
转换流java.io.InputStreamReader和OutputStreamWriter
功能无需掌握,了解其核心意义:
1:衔接其它字节与字符流
2:将字符与字节进行转换
相当于是现实中的"转换器"
PrintWriter
具有自动行刷新功能的缓冲字符输出流
构造器: PrintWriter(String filename) PrintWriter(File file)
常用方法:
void println(String line):按行写出字符串