字节流InputStream和OutputStream
java.io.InputStream和OutputStream是所有字节输入流和输出流的超类。
- InputStream中定义了所有字节输入流都要具备的读取字节的方法。
- OutputStream中定义了所有字节输出流都要具备的写出字节的方法。
读写不同的设备java为我们准备了专门连接该设备的输入与输出流。他们都继承了InputStream和OutputStream,因此对我们来说读写操作是一样的,我们只需要在对不同设备进行读写时创建对应的流即可。
文件流
文件流:java.io.FileInputStream和FileOutputStream
文件流是用来连接程序与文件之间的"管道",负责读写文件数据的流。注:他们继承自InputStream和OutputStream。
FileOutputStream
文件流有两种创建模式:覆盖模式和追加模式。
- 构造器1:
FileOutputStream(String path)
FileOutputStream(File file)
以上两种为覆盖模式的构造器,这种情况下创建文件输出流时如果连接的文件已经存在
则会吧文件之前的数据全部清除。
- 构造器2:
FileOutputStream(String path,boolean append)
FileOutputStream(File file,boolean append)
如果第二个参数传入true,则文件流为追加模式,此时连接的文件已经存在会保留文件
之前的所有数据。通过当前流每次写入的内容都会陆陆续续追加到文件后面。
- void write(int d)
写出1个字节,写出的是给定的int值对应的2进制的"低八位"
- void write(byte[] data)
一次性将给定字节数组中所有字节写出
- void write(byte[] data,int offset,int len)
将给定的字节数组从下标offset处开始的连续len个字节一次性写出
public class FOSDemo { public static void main(String[] args) throws IOException { //向文件test.dat中写入数据 /* File file = new File("./test.dat"); FileOutputStream fos = new FileOutputStream(file); */ FileOutputStream fos = new FileOutputStream("./test.dat");//只会自动创建文件不能创建文件夹 fos.write(1); fos.write(2); fos.close();//流在使用完毕后要调用close关闭。 } }
FileInputStream
文件字节输出流常用的构造方法:
FileInputStream(String path)FileInputStream(File file)
- int read()
读取1个字节,并将该字节的2进制保存在返回的int值的"低八位"上。
如果返回的int值为整数"-1"时,则说明读取到了流的末尾。
- int read(byte[] data)
一次性读取给定的字节数组总长度的字节量,并存入到该数组中。返回值为实际读取到的
字节量。如果返回值为整数"-1"表示流读取到了末尾public class FISDemo { public static void main(String[] args) throws IOException { //从文件test.dat中读取数据 FileInputStream fis = new FileInputStream("./test.dat"); int d = fis.read(); System.out.println(d);//1 d = fis.read();//2 System.out.println(d); d = fis.read();//-1 System.out.println(d); fis.close(); } }
块读/写字节的操作
public class CopyDemo2 { public static void main(String[] args) throws IOException { FileInputStream fis = new FileInputStream("ppt.pptx"); FileOutputStream fos = new FileOutputStream("ppt_cp2.pptx"); byte[] data = new byte[1024*10];//10kb int len; long start = System.currentTimeMillis(); while((len = fis.read(data))!=-1){ // fos.write(data); fos.write(data,0,len); } long end = System.currentTimeMillis(); System.out.println("复制完毕!,耗时:"+(end-start)+"ms"); fis.close(); fos.close(); } }
字符串的读写操作
public class WriteStringDemo { public static void main(String[] args) throws IOException { //向文件demo.txt中写入文本数据 FileOutputStream fos = new FileOutputStream("demo.txt"); String line = "beep,beep,i'm sheep~beep,beep,i'm sheep~"; /* String提供了将字符串转换为一组字节的方法 使用的字符集通常用UTF-8 java.nio.charset.StandardCharsets下的UTF_8 */ byte[] data = line.getBytes(StandardCharsets.UTF_8); fos.write(data); fos.write("比,比,爱慕希~比,比,爱慕希~".getBytes(StandardCharsets.UTF_8)); System.out.println("写出完毕!"); fos.close(); } }
public class ReadStringDemo { public static void main(String[] args) throws IOException { FileInputStream fis = new FileInputStream("demo.txt"); /* 利用available表示文件的长度。注:该方法实际是预估目前能通过输入流读取到多少 字节。不能记为流只能读取到这么多就再也读取不到数据了!! */ byte[] data = new byte[fis.available()]; //一次性读取文件中所有的字节到数组中(原因是字节数组长度与文件长度正好一致) fis.read(data); //String的构造方法支持将给定的字节数组按照指定的字符集还原为字符串 String str = new String(data, StandardCharsets.UTF_8); System.out.println(str); fis.close(); } }
缓冲流
java将流分为节点流与处理流两类
- 节点流:也称为低级流,是真实连接程序与另一端的"管道",负责实际读写数据的流。
读写一定是建立在节点流的基础上进行的。
节点流好比家里的"自来水管"。连接我们的家庭与自来水厂,负责搬运水。
- 处理流:也称为高级流,不能独立存在,必须连接在其他流上,目的是当数据经过当前流时
对其进行某种加工处理,简化我们对数据的同等操作。
高级流好比家里常见的对水做加工的设备,比如"净水器","热水器"。
有了它们我们就不必再自己对水进行加工了。实际开发中我们经常会串联一组高级流最终连接到低级流上,在读写操作时以流水线式的加工,完成复杂IO操作。这个过程也称为"流的连接"。
缓冲流是一对高级流,作用是加快读写效率。
java.io.BufferedInputStream和java.io.BufferedOutputStream/** * 缓冲流写出数据的缓冲区问题 */ public class BOS_flushDemo { public static void main(String[] args) throws IOException { FileOutputStream fos = new FileOutputStream("bos.txt"); BufferedOutputStream bos = new BufferedOutputStream(fos); //缓冲流内部默认有一个8K字节数组,写出的数据会先被存入数组中,直到数组装满才会写出 bos.write("super idol的笑容都没你的甜。".getBytes(StandardCharsets.UTF_8)); System.out.println("写出完毕!"); /* flush方法的作用是让缓冲输出流将其缓冲区中已经缓存的数据立即写出 */ bos.flush();//flush 冲水 bos.close(); } }
缓冲流写出数据的缓冲区问题
flush方法的作用是让缓冲输出流将其缓冲区中已经缓存的数据立即写出
public class BOS_flushDemo { public static void main(String[] args) throws IOException { FileOutputStream fos = new FileOutputStream("bos.txt"); BufferedOutputStream bos = new BufferedOutputStream(fos); //缓冲流内部默认有一个8K字节数组,写出的数据会先被存入数组中,直到数组装满才会写出 bos.write("super idol的笑容都没你的甜。".getBytes(StandardCharsets.UTF_8)); System.out.println("写出完毕!"); /* flush方法的作用是让缓冲输出流将其缓冲区中已经缓存的数据立即写出 */ bos.flush();//flush 冲水 bos.close(); } }
对象流
java.io.ObjectOutputStream和ObjectInputStream
对象流是一对高级流,在流连接中完成对象与字节的转换,即:对象序列化与反序列化操作
需要序列化和反序列化的对象:
- 序列化时要求该对象必须实现可序列化接口(Serializable),否则会抛出异常:java.io.NotSerializableException
- 关于序列化版本号serialVersionUID:当对象输出流在进行对象序列化时,会查看是否有显示的定义版本号,如果没有则会根据当前类的结构计算版本号(当前类的结构只要没有发生过变化,那么无论何时序列化版本号始终不会变化,只要改变过,那么序列化版本号一定会改变)并将该版本号保存在序列化后的字节中。当使用对象输入流反序列化时,对象输入流会检查要反序列化的对象与其对应的类的版本号是否一致,①若不一致,则会抛出异常:java.io.InvalidClassException ②如果后期修改了类结构,又希望原来的对象还可以进行反序列化,则需要显示的定义出来序列化版本号,这样一来,当一个对象序列化后,当前类结构改变了,只要版本号不变,那么之前的对象仍然可以进行反序列化。此时对象输入流会采取兼容方式,即:能还原的属性都会进行还原,没有的属性则采用默认值。
- 注意:创建流对象会抛出异常,需要Try-catch-finally处理异常需要先在try-catch-finally,外申明流对象,最后才能顺利关流
public class Person implements Serializable { static final long serialVersionUID = 1L; private String name; private int age; private String gender; /* 当一个属性被关键字transient修饰后,那么当进行对象序列化时,该属性的值会被忽略。 忽略不必要的属性可以达到对象"瘦身"的目的,提高程序响应速度,减少资源开销。 当然,反序列化时,该属性只会采用默认值。 */ private transient String[] otherInfo; // private int salary; ...... }
对象的序列化:
对象输出流提供的独有方法:writeObject(Object obj),该方法会进行对象序列化,并将序列化后的字节通过其连接的流写出。
public class OOSDemo { public static void main(String[] args) throws IOException { String name = "凯菲猫"; int age = 18; String gender = "女"; String[] otherInfo = {"是一名演员","来自霓虹","爱好书法","广大男性同胞的启蒙老师"}; Person p = new Person(name,age,gender,otherInfo); System.out.println(p);//输出p.toString()返回值 //将该Person对象写入文件person.obj中 FileOutputStream fos = new FileOutputStream("./person.obj"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(p); System.out.println("写出完毕!"); oos.close(); } }
对象的反序列化:
不需要序列化的数据可以修饰成static,原因:static资源属于类资源,不随着对象被序列化输出。被修饰成transient(临时的),只在程序运行期间在内存中存在,不会被序列化持久保存
/** * 使用对象输入流进行对象的反序列化 */ public class OISDemo { public static void main(String[] args) throws IOException, ClassNotFoundException { FileInputStream fis = new FileInputStream("person.obj"); ObjectInputStream ois = new ObjectInputStream(fis); Person p = (Person)ois.readObject(); System.out.println(p); ois.close(); } }
字符流Reader和Writer
java IO将流按照读写单位划分为字节流与字符流
- java.io.InputStream和OutputStream是所有字节输入与输出流的超类,读写最小单位是字节
- java.io.Reader和Writer则是所有字符输入流与输出流的超类,读写单位最小为字符。因此注意:字符流仅适合读写文本数据(字符或字符串)。读写文件仅读写文本文件。字符流底层本质还是读写字节,只是字符与字节的转换由字符流自行完成。
转换流(高级流)是一对常用的字符流实现类:
java.io.InputStreamReader和OutputStreamWriter
实际应用中我们不直接操作这一对流,但是在读写文本数据而组建流连接时他们是非常重要的一环。
作用:
1:在流连接中衔接其他高级字符流与下面的字节流(这也是转换流名字的由来)
2:负责将字符与对应的字节按照指定的字符集自动转换方便读写操作。public class OSWDemo { public static void main(String[] args) throws IOException { //向文件osw.txt中写入文本年数据 FileOutputStream fos = new FileOutputStream("osw.txt"); //在创建转换流时通常需要指定第二个参数,明确使用的字符集。 OutputStreamWriter osw = new OutputStreamWriter(fos,StandardCharsets.UTF_8); // String line = "我可以接受你的所有,所有小脾气。"; // byte[] data = line.getBytes(StandardCharsets.UTF_8); // fos.write(data); //字符输出流支持直接写出字符串的write方法 osw.write("我可以接受你的所有,所有小脾气。"); osw.write("我可以带你去吃很多,很多好东西。"); System.out.println("写出完毕!"); osw.close(); } }
/** * 使用转换流测试读取文本数据 */ public class ISRDemo { public static void main(String[] args) throws IOException { //将osw.txt中的所有内容读取出来并输出到控制台 FileInputStream fis = new FileInputStream("osw.txt"); InputStreamReader isr = new InputStreamReader(fis, StandardCharsets.UTF_8); //读取1个字符,返回的int值内容本质上是一个char。但是若返回的int为-1则表示读取到了末尾 // int d = isr.read(); // System.out.println((char)d); int d; while((d = isr.read()) != -1){ System.out.print((char)d); } isr.close(); }
缓冲字符流
java.io.BufferedWriter和BufferedReader
缓冲字符流内部维护一个数组,可以块读写文本数据来进行读写性能的提升。
java.io.PrintWriter是具有自动行刷新功能的缓冲字符输出流,内部总是连接BufferedWriter
实际开发中缓冲字符输出流我们都使用它。
* 特点:
* 1:可以按行写字符串
* 2:可以自动行刷新
* 3:可以提高写出字符的效率(实际由内部连接的BufferedWriter完整)
public class PWDemo { public static void main(String[] args) throws FileNotFoundException, UnsupportedEncodingException { //向文件pw.txt中写入文本数据 /* PrintWriter提供了直接对文件做操作的构造器 PrintWriter(String fileName) PrintWriter(File file) 以上两种构造器都支持一个重载,允许再传入一个String类型的参数用来指定字符集。 需要注意字符集名字的拼写,大小写均可,但是拼写错误会抛出异常: UnsupportedEncodingException */ PrintWriter pw = new PrintWriter("./pw.txt", "UTF-8"); // File file = new File("./pw.txt"); // PrintWriter pw = new PrintWriter(file); pw.println("该配合你演出的我演视而不见。"); pw.println("别逼一个最爱你的人即兴表演。"); System.out.println("写出完毕!"); pw.close(); } }
/** * 在流连接中使用PW */ public class PWDemo2 { public static void main(String[] args) throws FileNotFoundException { //文件字节输出流(是一个低级流),向文件中写入字节数据 FileOutputStream fos = new FileOutputStream("pw2.txt",true); //转换输出流(是一个高级流,且是一个字符流)。1:衔接字符与字节流 2:将写出的字符转换为字节 OutputStreamWriter osw = new OutputStreamWriter(fos, StandardCharsets.UTF_8); //缓冲输出流(是一个高级流,且是一个字符流)。块写文本数据加速 BufferedWriter bw = new BufferedWriter(osw); //具有自动行刷新的缓冲字符输出流 /* PrintWriter提供的构造器中,当第一个参数为一个流时,就支持再传入一个boolean 型的参数表示是否自动行刷新。当该值为true时则打开了自动行刷新功能。这意味着 每当我们调用println方法后会自动flush一次。 */ PrintWriter pw = new PrintWriter(bw,true); //完成简易记事本。控制台输入的每行字符串都按行写入文件。单独输入exit时退出。 Scanner scanner = new Scanner(System.in); while(true){ String line = scanner.nextLine(); if("exit".equalsIgnoreCase(line)){ break; } pw.println(line); } pw.close(); } }
缓冲字符输入流:java.io.BufferedReader
1:块读文本数据加速
2:可以按行读取字符串public class BRDemo { public static void main(String[] args) throws IOException { //将当前程序的源代码输出到控制台上 //文件字节输入流,低级流,字节流。功能:从文件中读取字节 FileInputStream fis = new FileInputStream("./src/io/BRDemo.java"); //转换输入流,高级流,字符流。功能:1衔接字节与其他字符流 2:将读取的字节转换为字符 InputStreamReader isr = new InputStreamReader(fis); //缓冲字符输入流,高级流,字符流。功能:1块读文本数据加速 2:按行读取字符串 BufferedReader br = new BufferedReader(isr); /* 缓冲字符输入流提供的读取一行字符串的方法: String readLine() 该方法会连续读取若干字符,到换行符为止,然后将换行符之前的内容以一个字符串形式 返回。注意:返回的字符串中不含有最后的换行符。 如果单独读取了空行(此行内容只有一个换行符,那么会返回一个空字符串。 当方法返回值为null时,表示流读取到了末尾。 */ String line; while((line = br.readLine()) != null) { System.out.println(line); } br.close(); } }
NIO
BIO:阻塞式IO,读写过程中可能存在阻塞现象(卡住程序),BIO是面向流的,流是单向的,要么用于读(输入),要么用于写(输出)
NIO:非阻塞式IO,读写过程不会出现阻塞现象,NIO是面向通道的,通道是双向的,又能读又能写
Channel是通道,常见的通道有很多:
FileChannel:操作文件的通道,可对文件进行读写操作
SocketChannel:套接字通道,可与远端计算机进行TCP的双向读写操作
ServerSocketChannel:服务端套接字通道,用于监听客户端的链接public class NIODemo1 { public static void main(String[] args)throws IOException { //NIO写法 FileInputStream fis = new FileInputStream("movie.wmv"); FileChannel inChannel = fis.getChannel();//通过该文件输入流获取操作movie.wmv的通道 FileOutputStream fos = new FileOutputStream("movie_cp.wmv"); FileChannel outChannel = fos.getChannel();//通过该输出流获取了操作movie_cp.wmv的通道 //块读写赋值 int len;//记录每次实际读取的字节数 ByteBuffer buffer = ByteBuffer.allocate(1024*10);//开辟一个10K缓冲区 //一次性通过inChannel从原文件读取10K并存入到缓冲区 /* read前buffer position:0 limit:10240 */ while((len = inChannel.read(buffer))!=-1){ /* read后buffer position:10240 limit:10240 */ buffer.flip(); /* flip后buffer 1:limit=position 2:position=0 position:0 limit:10240 */ /* write(ByteBuffer buf) 将buf中position-limit之间所有的数据一次性写出 */ outChannel.write(buffer); /* write完毕buffer position:10240 limit:10240 */ buffer.clear();//clear()的作用1:limit为buffer容量 2:position为0 } System.out.println("复制完毕!"); /* 当读写全部进行完毕后,还是关闭流,因为关闭的同时会自动关闭通道。 */ fis.close(); fos.close(); } }