JavaIO的整体框架图
IO流从方向上来说,可以分为输入流和输出流;
从传输内容上来说,可以分为字符流和字节流
防止记混的口诀
所谓的IO,说白了就是数据在内存和硬盘之间的传输
输入流 = %Reader = %InputStream,从硬盘写入内存;
输出流 = %Writer = %OutputStream,从内存写入硬盘;
站在内存的角度,输入流是读入,输出流是写出;
站在硬盘的角度,输入流是读出,输出流是写入
我们为了防止记忆错乱,我建议站在内存的角度记忆,因为In和Out能对应上入和出
输入流 = %Reader = %InputStream = 读 入内存
输出流 = %Writer = %OutputStream = 写 出
字符流
文本,我们能读的懂的都可以认为是字符流。比如:文章,java文件等等
字符流的本质是字符数组char[]
字符输入流的超类:
Reader: 子类FileReader,BufferedReader
字符输出流的超类:
Writer:子类FileWriter,BufferedWriter
字符流可以分为字符输入流Reader和字符输出流Writer
文件字符输入流FileReader
import java.io.*;public class ReaderTest {public static void main(String[] args) {File file = new File("D:\\2_WorkSpace\\qcbyProject\\shixun\\collection-test\\src\\FileTestPath\\hello.txt");File file2 = new File("D:\\2_WorkSpace\\qcbyProject\\shixun\\collection-test\\src\\FileTestPath\\charInput.txt");Reader reader = null;try {reader = new FileReader(file);// 1. 单个字符读取int c = reader.read();// 这样只能一个字符一个字符地读System.out.println((char)c);// 2. while循环读取while((c = reader.read()) != -1){System.out.print((char)c);}// 3. reader填充字符数组char[] cArr = new char[100];// 填充int len = reader.read(cArr);System.out.println("读取的长度:" + len + " 读取的内容:" + Arrays.toString(cArr));len = reader.read(cArr);System.out.println("第二次读取的长度:" + len + " 读取的内容:" + Arrays.toString(cArr));len = reader.read(cArr);System.out.println("第三次读取的长度:" + len + " 读取的内容:" + Arrays.toString(cArr));// 4. reader填充字符数组,并且拼接为字符串char[] cArr2 = new char[200];int len2 = -1;while ((len2 = reader.read(cArr2)) != -1) {String str = new String(cArr2, 0, len2 );System.out.println(str);}} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();} finally {//关闭资源if (reader != null) {try {reader.close();} catch (IOException e) {e.printStackTrace();}}}}
}
文件字符输出流FileWriter
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;public class WriterTest {public static void main(String[] args) {File file = new File("D:\\2_WorkSpace\\qcbyProject\\shixun\\collection-test\\src\\FileTestPath\\hello.txt");Writer writer = null;try {// 1. 直接write会删掉之前全部的,然后放进去新的writer = new FileWriter(file);writer.write("Hello 111Writer!!!");// 2. 在源文件后拼接字符流writer = new FileWriter(file, true);writer.write("append text!!!");// 3. 循环分段拼接字符流writer = new FileWriter(file, true);for (int i = 0; i < 100; i++) {writer.write("HelloWorld\n");//每次写入10个helloworld的时候做一次flushif (i % 10 == 0) {writer.flush();}}// 4. 字符数组(字符流)的直接输出(写入)File file2 = new File("D:\\2_WorkSpace\\qcbyProject\\shixun\\collection-test\\src\\FileTestPath\\charInput.txt");writer = new FileWriter(file2);//定义一个数组char[] c = {'a', 'b', 'p', 'b', 'p'};writer.write(c);writer.write("\r\n");writer.write(c, 2, 2);writer.write("\r\n");writer.write(98);writer.write("\r\n");writer.write("我们今生注定是沧桑", 2, 6);} catch (IOException ioe) {ioe.printStackTrace();} finally {//判断writer不是空防止空指针异常if (writer != null) {try {//关闭流writer.close();} catch (IOException e) {e.printStackTrace();}}}}
}
注意:一般的writer会将原内容删除,再写新内容;如果想要在原有内容的基础上填充,就需要让append为true
Writer writer = new FileWriter(file, true);
flush方法是干什么的?
writer.flush()的作用在于将缓冲区的所有字符都发送给硬盘
只有Writer和OutputStream才有flush方法;但是不管读写都有自己的缓冲区,只不过读操作不需要我们手动清空缓冲区罢了
java在使用流时,都会有一个缓冲区,按一种它认为比较高效的方法来发数据:把要发的数据先放到缓冲区,缓冲区放满以后再一次性发过去,而不是分开一次一次地发.
而flush()表示强制将缓冲区中的数据发送出去,不必等到缓冲区满.
执行close()方法时,会强制执行一次flush()方法
但是如果忘了使用close(),也没有用flush()这个方法,很多情况下会出现流的另一边读不到数据的问题,特别是在数据特别小的情况下
我们测试用的数据都是很少的,FileWriter默认的缓冲区大小是8KB,显然到不了这个长度
为什么需要缓冲区(重点)
因为内存与硬盘的读写操作比较耗费时间,为了减少内存与磁盘的交互次数,降低时间消耗,我们使用缓冲区缓存一部分内容,只有缓冲区满了才会向硬盘发送写请求,这样做可以减少交互次数,降低耗时
使用FileReader和FileWriter完成文件拷贝
import java.io.*;public class CopyFileTest {public static void main(String[] args) throws IOException {File file = new File("D:\\2_WorkSpace\\qcbyProject\\shixun\\collection-test\\src\\FileTestPath\\hello.txt");Writer writer = new FileWriter("helloCopy.txt", true);Reader reader = new FileReader(file);// 清空之前的内容writer.write("");// 一字节一字节地读取char[] cArr = new char[1024];int len = -1;while ((len = reader.read(cArr)) != -1) {String str = new String(cArr, 0, len);// 读到的字符串写入文件中writer.write(cArr, 0, len);writer.flush();}if (reader != null) {reader.close();}if (writer != null) {writer.close();}}
}
缓冲字符输入流BufferedReader
public class BufferReaderTest {public static void main(String[] args) {File file = new File("D:\\2_WorkSpace\\qcbyProject\\shixun\\collection-test\\src\\FileTestPath\\charInput.txt");BufferedReader bufferedReader = null;try {bufferedReader = new BufferedReader(new FileReader(file));String line = null;// BufferedReader是一行一行读取的while((line = bufferedReader.readLine()) != null){//打印一行System.out.println(line);}} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}finally {//关闭外层的对象的时候,内存的资源会自动的被关闭if(bufferedReader != null){try {bufferedReader.close();} catch (IOException e) {e.printStackTrace();}}}}
}
BufferedReader读取数据是一行一行地读的
之前说过Reader和Writer都有自己的缓冲区,BufferedReader和FileWriter的缓冲区大小相同,区别在于BUfferedWriter只有缓冲区满了才会进行字符转码(将字符转化为字节)
缓冲字符输出流BufferedWriter
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;public class BufferWriterTest {public static void main(String[] args) {File file = new File("D:\\2_WorkSpace\\qcbyProject\\shixun\\collection-test\\src\\FileTestPath\\charInput.txt");BufferedWriter bufferWriter = null;try {bufferWriter = new BufferedWriter(new FileWriter(file, true));bufferWriter.write("BufferedWriter写入的内容:11111");//换行bufferWriter.newLine();bufferWriter.write("BufferedWriter写入的内容:22222");bufferWriter.flush();} catch (IOException e) {e.printStackTrace();} finally {//资源关闭if (bufferWriter != null) {try {bufferWriter.close();} catch (IOException e) {e.printStackTrace();}}}}
}
使用BufferedReader和BufferedWriter完成文件拷贝
原理同上,写法不同
import java.io.*;public class BufferedCopyFile {public static void main(String[] args) {File file = new File("D:\\2_WorkSpace\\qcbyProject\\shixun\\collection-test\\src\\FileTestPath\\charInput.txt");BufferedReader bufferedReader = null;BufferedWriter bufferWriter = null;try {bufferedReader = new BufferedReader(new FileReader(file));String line = null;// BufferedReader是一行一行读取的while ((line = bufferedReader.readLine()) != null) {bufferWriter = new BufferedWriter(new FileWriter("bufferedCopyFile.txt", true));bufferWriter.write(line);bufferWriter.flush();}} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}finally {//关闭外层的对象的时候,内存的资源会自动的被关闭if(bufferedReader != null){try {bufferedReader.close();} catch (IOException e) {e.printStackTrace();}}//资源关闭if (bufferWriter != null) {try {bufferWriter.close();} catch (IOException e) {e.printStackTrace();}}}}
}
Buffered为什么高效?
所有的Writer字符流,都要先变成InputStreamReader/OutputStreamWriter,然后转化为InputStream/OutputStream变成字节数组,最终转化为二进制数据才能在计算机底层进行传输
所有的Reader字符流,过程相反
我们可以看到,字符流读写存在编解码的过程,而Buffered之所以高效,就是因为FileWriter每次调用write()方法,就会调用一次OutputStreamWriter中的write()方法,而BufferedWriter只有在缓冲区满了才会调用OutputStreamWriter中的write()方法。
打印字符输出流PrintWriter
PrintWriter和FileWriter之间的区别
区别在于 :
- PrintWriter设计之初就是为了写出,所以没有PrintReader
- PrintWriter 提供了一些额外的格式化方法,如 println ,print和 printf,而且可以将内容打印到控制台上
- 封装了字符输出流,还可以字符流和字节流的转换
- 可以打印各种数据类型
- PrintWriter 在传递流时所做的是以 缓冲(BufferedWriter) 方式打开它,FileWriter不是
- 与 PrintStream 类不同,如果启用了自动刷新,但只有在调用 println、printf 或 format 的其中一个方法时才可能完成此操作(PrintStream是任何写方法都可以自动刷新)
PrintWriter(Writer out, boolean autoFlush);// true开启,false关闭
public PrintWriter(File file) throws FileNotFoundException {this(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file))),false);
}
FileWriter没有缓冲
public FileWriter(File file) throws IOException {super(new FileOutputStream(file));
}
demo
打印控制台
PrintWriter pw = null;BufferedReader br = null;try {br = new BufferedReader(new FileReader("c.txt"));pw = new PrintWriter("d.txt");String line = null;while((line = br.readLine()) != null){pw.println(line);pw.flush();}} catch (Exception e) {e.printStackTrace();} finally {if(pw != null){pw.close();}if(br != null){try {br.close();} catch (IOException e) {e.printStackTrace();}}}
打印到文件中(其实就是向文件中写)
PrintWriter pw = null;try {pw = new PrintWriter("c.txt");pw.println(1);pw.println(1.1);pw.println("liangge");pw.println('c');pw.println(false);pw.println(1.2f);pw.flush();} catch (FileNotFoundException e) {e.printStackTrace();} finally {if(pw != null){pw.close();}}
字节流
二进制的数据,这种数据一般用文本打开我们读不懂。比如,图片文件,mp3文件。
字节输入流的超类:InputStream:
子类:FileInputStream
字节输出流的超类:
OutputStream:
子类FileOutputStream
字节流传输的是二进制的数据
字节流的本质是字节数组byte[]
字符流能传输的,字节流也可以传输;
字节流能传输的,字符流不一定能传输
文件字节输入流FIleInputStream
public static void main(String[] args) {//创建字符输入流的对象InputStream in = null;try {in = new FileInputStream("a.txt");int r = in.read();System.out.println((char)r);} catch (Exception e) {e.printStackTrace();} finally {if(in != null){try {in.close();} catch (IOException e) {e.printStackTrace();}}}}
文件字节输出流FIleOutputStream
public class IOByteDemo {public static void main(String[] args) {//创建字节输出流OutputStream out = null;try {out = new FileOutputStream(new File("a.txt"));//字节流不需要flushout.write(98);// Ascii码对应的是小写字母b} catch (Exception e) {e.printStackTrace();} finally {try {if(out != null){out.close();}} catch (IOException e) {e.printStackTrace();}}}
}
缓冲字节输入流BufferedInputStream
public static void main(String[] args) throws IOException {File file = new File("D:\\2_WorkSpace\\qcbyProject\\shixun\\collectionAndIOStream-test\\helloCopy.txt");// 以下两种方式均可// BufferedInputStream bis = new BufferedInputStream(Files.newInputStream(file.toPath()));BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));byte[] bytes = new byte[1024];int len = -1;while ((len = bis.read(bytes)) != -1) {String s = new String(bytes, 0, len);System.out.print(s);}}
比BufferedReader更快,因为不需要转化为InputStreamReader,但是仍然需要编码
缓冲字节输入流BufferedOutputStream
public static void main(String[] args) throws IOException {File file = new File("D:\\2_WorkSpace\\qcbyProject\\shixun\\collectionAndIOStream-test\\buf.txt");// 以下两种方式均可// BufferedOutputStream bos = new BufferedOutputStream(Files.newOutputStream(file.toPath()));BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file));//创建一个字节的数组byte[] bs = {97, 99, 103, 111};bos.write(bs, 1, 2);}
比BufferedWriter更快,因为不需要转化为OutputStreamWriter,但是仍然需要解码
BufferedStream拷贝文件
public static void main(String[] args) throws IOException {File fileRead = new File("D:\\2_WorkSpace\\qcbyProject\\shixun\\collectionAndIOStream-test\\helloCopy.txt");File fileWrite = new File("bisAndBos.txt");BufferedInputStream bis = new BufferedInputStream(new FileInputStream(fileRead));BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(fileWrite));int count = 0;while (count == 0) {count = bis.available();}int fileSize = bis.available();long written = 0;int beteSize = 1024;byte[] bytes = new byte[beteSize];while (written < fileSize) {if (written + beteSize > fileSize) {beteSize = (int) (fileSize - written);bytes = new byte[beteSize];}bis.read(bytes);bos.write(bytes);bos.flush();written += beteSize;}}
对象字节输出流ObjectOutputStream(序列化流)
对象转化为二进制数据,被称为序列化;
将二进制数据转化为对象,被称为反序列化
我们假设有这样一个Person
必须实现Serializable接口!!!
public class Person implements Serializable{//唯一的序列化接口的id值private String name;private int age;private Date birth;private static final long serialVersionUID = 3587752576949978144L;private String addr;public Person(String name, int age, Date birth) {this.name = name;this.age = age;this.birth = birth;}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 Date getBirth() {return birth;}public void setBirth(Date birth) {this.birth = birth;}@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +", age=" + age +", birth=" + birth +", addr='" + addr + '\'' +'}';}
}
对象流进行序列化
public static void main(String[] args) {Person p = new Person("宇智波止水", 16, new Date());ObjectOutputStream out = null;try {out = new ObjectOutputStream(new FileOutputStream("person-data.txt"));out.writeObject(p);out.flush();} catch (IOException e) {e.printStackTrace();} finally {if(out != null){try {out.close();} catch (IOException e) {e.printStackTrace();}}}}
txt内容
对象字节输入流ObjectInputStream(反序列化流)
把我们刚才序列化的字节序列反序列化为java对象
public static void main(String[] args) {ObjectInputStream in = null;try {in = new ObjectInputStream(new FileInputStream("person-data.txt"));//读取一个对象, 叫反序列化Object o = in.readObject();System.out.println(o);} catch (Exception e) {e.printStackTrace();} finally {if(in != null){try {in.close();} catch (IOException e) {e.printStackTrace();}}}}