部分参考:
https://www.cnblogs.com/oubo/archive/2012/01/06/2394638.html
Java IO 学习总结
Java流类图结构
Java流操作的相关类或接口
-
File – 文件类
- RandomAccessFile – 随机存储文件类
-
InputStream – 字节输入流
-
OutputStream – 字节输出流
-
Reader – 字符输入流
-
Writer – 字符输出流
输入流 or 输出流
输入 与 输出,是以程序端的角度来看流向,从文件(磁盘)中读取数据到程序就是输入in,将数据从程序写出到文件就是out。
in <-- read
out --> write
InputStream 字节输入流
但 InputStream 是抽象类,读文件的方法(read)是抽象方法。需要其实现类。
FileInputStream
InputStream 有很多的子类,其中最常用的是文件流FileInputStream了,主要用来读取文件。
File file = new File("MockData.txt");
FileInputStream fis = new FileInputStream(file);
// FileInputStream fis = new FileInputStream("MockData.txt");
有两个构造器:
FileInputStream(File file);
FileInputStream(String path);
读取的方法:
int read();
int read(byte[] b);
int read(byte[] b, int off, int len);
- int read()
调用一次读到一个数据字节
返回的int值就是读到的数据,如果已到达文件末尾,则返回 -1。
FileInputStream fis = new FileInputStream(file);
int i;
while ((i = fis.read()) != -1){
System.out.println((char)i);
}
fis.close();
- int read(byte[] b)
调用一次本方法表示可以读取多个数据。
读到的内容保存传入的byte数组b中。
返回的是本次调用方法读到的数据字节个数。
FileInputStream fis = new FileInputStream(file);
byte[] bytes = new byte[1024]; // 长度 决定了每次循环中读取的字节长度。长度越小循环次数越多
while (fis.read(bytes) != -1){
String string = new String(bytes);
System.out.println(string);
}
fis.close();
字节数组是按字节长度来拆分到每一次批次循环的,一个中文是两个数据字节,不能避免一个中文被拆到两个批次中,导致不能识别而间歇性乱码
听说�
��文�
�乱码
需要关闭输入流
fis.close();
已关闭的流不能再读取到任何内容。
OutputStream 字节输出流
同样 OutputStream 是抽象类,写文件的方法(write)是抽象方法。需要其实现类。
FileOutputStream
构造方法:
FileOutputStream(File file);
FileOutputStream(String name);
// append 表示追加(true) or 覆盖(false),不传时默认false-覆盖
FileOutputStream(File file, boolean append);
FileOutputStream(String name, boolean append);
方法:
void write(int b); // 调用一次写入一个数据字节
void write(byte[] b); // 调用一次,可以把一个byte数组中的数据写入
void write(byte[] b, int off, int len); //调用一次,把b数组中的一部分数据写入
FileOutputStream在执行write时,如果文件不存在,会自动创建一个文件(但文件的路径必须存在的)
- void write(int b)
FileOutputStream fos = new FileOutputStream(file,true);
// 会自动将int 值转为char作为一个字节写入
fos.write(66); // 写到文件里的是 B
fos.close();
- void write(byte[] b)
FileOutputStream fos = new FileOutputStream(file,true);
fos.write("66".getBytes()); // 写到文件里的才是 66
fos.close();
getBytes() 时默认编码为"UTF-8"
仍然要需要关闭输出流
InputStream 其他实现类
- ObjectInputStream
反序列化,从文件中读取成一个java对象。
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.txt"));
byte[] read = (byte[]) ois.readObject();
String s2 = new String(read); // 再解析成对象
ObjectInputStream 只是装饰器(装饰者模式),源码中可知只有一个public的构造器,需要借助一个已有的InputStream才能使用,如FileInputStream。
public ObjectInputStream(InputStream in);
protected ObjectInputStream();
- ByteArrayInputStream 和 StringBufferInputStream
ByteArrayInputStream、StringBufferInputStream、FileInputStream是三种基本的介质流,它们分别从Byte数组、StringBuffer、和本地文件中读取数据。
- PipedInputStream
PipedInputStream是从与其他线程共用的管道中读取数据。
-
FilterInputStream
FilterInputStream 也是一个装饰器,只能通过构造器
protected FilterInputStream(InputStream in)
创建对象,且必须通过子类(受protected修饰):
-
DataInputStream :
使用InputStream我们只能读取byte,DataInputStream使得我们可以直接从stream中读取java中的int,String等类型。
-
BufferInputStream:
这个类提供了一个缓存来加速我们从字节输入流的读取。
-
OutputStream 其他实现类
- ObjectOutputStream
ObjectOutputStream和所有FileOutputStream的子类都是装饰流。
- ByteArrayOutputStream
ByteArrayOutputStream、FIleOutputStream是两种基本的介质,它们分别向Byte 数组,和本地文件中写入数据。
- PipedOutputStream
PipedOutputStream是从与其他线程共用的管道中写入数据。
字符流和字节流
字节流以字节(8bit)为单位读取数据。读取中文的时候非常不方便,容易产生乱码。
字符流的由来:本质其实就是基于字节流。读取时,去查了指定的码表,而有了对字符进行高效操作的流对象。
字节流和字符流的区别:
- 读写单位不同:字节流以字节为单位,字符流以字符为单位,根据码表映射字符,一次可能读多个字节。
- 处理对象不同:字节流能处理所有类型的数据(如图片、视频等),而字符流只能处理字符类型的数据。
- 结论:只要是处理纯文本数据,就优先考虑使用字符流。 除此之外都使用字节流。
Reader 字符输入流
InputStreamReader
首先看他有唯一个子类 FileReader
FileReader的构造方法就是new FileInputStream字节流,再调用父类InputStreamReader构造器得到inputStreamReader对象
。
public FileReader(String fileName) {
super(new FileInputStream(fileName));
}
FileReader都是使用InputStreamReader的read方法,以char[]为单位读取,而FileInputStream的read是以byte[]为单位读取。
int read();
read(char cbuf[], int offset, int length);
//底层是 read(char[] var1, int var2, int var3);
Reader reader = new FileReader(file);
等效于
Reader isr = new InputStreamReader(new FileInputStream(file));
FileReader reader = new FileReader(file);
char[] cbuf = new char[3];
int len;
while((len = reader.read(cbuf))!=-1){
System.out.println(new String(cbuf,0,len));
}
Reader isr = new InputStreamReader(new FileInputStream(file));// 等效于 Reader reader = new FileReader(file)
char[] cbuf = new char[3];
int len;
while((len = isr.read(cbuf))!=-1){
System.out.println(new String(cbuf,0,len));
}
isr.close();
同样,我们发现InputStreamReader 的创建,也是基于一个FileInputStream,同ObjectInputStream一样,只是一个装饰器。它将字节流转变为字符流。
子类FileReader的存在就是代码上可以省略了这一步。
可以说,使用字符流都离不开InputStreamReader来将字节流转为字符流。
BufferedReader
Buffer:表示缓冲区的。之前的StringBuffer,缓冲区中的内容可以更改,可以提高效率。
如果想接收任意长度的数据,而且避免中文乱码的产生,就可以使用BufferedReader。
有两个构造器(其实一个)
public BufferedReader(Reader in, int sz)
public BufferedReader(Reader in) {
this(in, defaultCharBufferSize);
}
基于一个Reader(如InputStreamReader)创建对象,说明 BufferedReader也只是一个装饰者。
除了继承自Reader的
read(char cbuf[])
覆写了Reader的
read();
read(char cbuf[], int off, int len)
自己提供了
// 一次性从缓冲区中读取一行
String readLine();
String readLine(boolean ignoreLF);
// 此时,没有任何长度限制,可以输入很多的内容,每次都以回车结束(只要是一行不管多长)。
// 不仅可以接收键盘输入,还可以将文件中的内容读取到缓冲区之中 然后调用readLine()方法将缓冲区中的全部内容转为字符串.
需要注意的是,如果从文件中读取的话readLine一次只能读取一行的数据。
可以发现:从文件中使用readLine()方法读取行内容时,会自动接着上次在流中的位置进行读取。
如果要全部读取文件的中的内容有如下两种方法:
方法一:使用StringBuffer类不停的连接readLine()从每次读取的一行内容,直至读取的为null为止。然后进行输出。
方法二:使用StringBuffer类不停的连接read()方法读取到的每一个数字转化后的字符。然后进行输出。
StringReader
就是方便将代码里的字符串创建输入流。
String str = "Hello World";
StringReader sr = new StringReader(str);
int sc = sr.read(); // 读取单个字符,若到流末尾则返回-1
System.out.println((char) sc);
String str = "Hello World";
char[] chars = new char[1024];
int num = sr.read(chars, 0, str.length()); // 读取len个字符到chars数组中,从chars数组中的下标off开始存储,返回实际存储的字符数
System.out.println(new String(chars, 0, num));
CharArrayReader
- CharArrayReader 通过字符数组直接创建输入流。
// 构造器
public CharArrayReader(char buf[])
- 其他
输入流的使用总结
-
FileInputStream (字节输入流)
- FileInputStream
如果需要从文件读取图片、视频等,和简单的读取文件不用考虑中文,使用最基础的字节输入流。
FileInputStream fis = new FileInputStream(file); fis.read();
- 其他实现类:
读java对象就ObjectInputStream;从从Byte数组、StringBuffer就ByteArrayInputStream、StringBufferInputStream…;需要直接读成java类型就DataInputStream、期望带缓存的字节流就BufferedInputStream
- FileInputStream
-
InputStreamReader (字符输入流)
如果考虑中文乱码或其他原因要使用字符流,那就绕不开 InputStreamReader。
- 代码简洁地读取文件,可以直接用 FileReader,它的本质也是 InputStreamReader
Reader reader = new FileReader(file); // 等效于 Reader isr = new InputStreamReader(new FileInputStream(file),"UTF-8"); // 不指定编码则默认UTF8
-
BufferedReader
如果想接收任意长度的数据,或按整行读取,而且避免中文乱码的产生,就可以使用 BufferedReader。
// 套娃比较多了,但是对于读中文文件最爽 Reader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file))); // 当然也能指定编码 // 其实写法等效于 Reader reader = new BufferedReader(new FileReader(file));
- 其他实现类:
直接从字符串就StringReader,从Char数组就CharArrayReader…
- 其他实现类:
Writer 字符输出流
OutputStreamWriter
同样的,它也是字节输出流与字符输出流之间的桥梁。
也有唯一一个子类FileWriter,提供了更快速使用OutputStreamWriter的方式。
FileWriter 构造方法摘要:
public FileWriter(String fileName) throws IOException {
super(new FileOutputStream(fileName));
}
public FileWriter(File file) throws IOException {
super(new FileOutputStream(file));
}
// append 代表覆盖 or 追加 (用的就是 FileOutputStream的覆盖 or 追加)
public FileWriter(File file, boolean append) throws IOException {
super(new FileOutputStream(file, append));
}
OutputStreamWriter 构造方法摘要:
OutputStreamWriter(OutputStream out)
// 支持设置编码,而FileWriter默认使用UTF8
OutputStreamWriter(OutputStream out, Charset cs)
OutputStreamWriter(OutputStream out, String charsetName)
FileWriter 是 OutputStreamWriter子类,可快速创建出 OutputStreamWriter
Writer writer = new FileWriter(file);
// 等效于
Writer writer = new OutputStreamWriter(new FileOutputStream(file));
Writer writer = new FileWriter(file,true);
// 等效于
Writer writer = new OutputStreamWriter(new FileOutputStream(file,true));
同样有方法
void write(int);
void write(char[]);
void write(char[],int,int);
void write(String,int,int);
void write(String);
FileWriter都是使用OutputStreamWriter的write方法,方法签名和OutputStream也相似,但以char[]为单位读取,而FileOutputStream的write是以byte[]为单位读取。
Writer osw = new OutputStreamWriter(new FileOutputStream(file));
osw.write("宫廷玉液酒");
osw.close();
字符流对比字节流,还会有一个flush()方法,在close()中会自动flush()。
public void flush();
当一次写入数据很多时,可以在中途手动刷新提交数据。
没有flush(),也没有close()时,数据是不会成功写到文件的。
BufferedWriter
BufferedWriter通过字符数组来缓冲数据,当缓冲区满或者用户调用flush()函数时,它就会将缓冲区的数据写入到输出流中。
查看构造函数也是依赖一个字符流,装饰者。
// 构造函数
BufferedWriter(Writer out)
BufferedWriter(Writer out, int sz)
// 方法
void close() // 关闭此流,但要先刷新它。
void flush() // 刷新该流的缓冲。
void newLine() // 写入一个换行符。
void write(char[] cbuf, int off, int len) // 写入字符数组的某一部分。
void write(int c) // 写入单个字符。
void write(String s, int off, int len) // 写入字符串的某一部分。
创建BufferedWriter:
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file,true))); // 覆盖 or 追加-true
for (int i = 0; i < 10; i++) {
writer.write(StringRandomTest.charRandomFromInt()); // 生成6位随机字符串
writer.newLine(); // 换行
}
writer.flush();
writer.close();
输出流的使用总结
与输入流的使用总结类似,分别考虑(字节输出流、字符输出流、带缓冲字符输出流)
-
FileOutputStream (字节输入流)
- FileInputStream
如果需要写入其他格式文件,和写入简单的文件而不用考虑中文,使用最基础的字节输出流。
- FileInputStream
-
OutputStreamWriter (字符输出流)
如果考虑中文乱码或其他原因要使用字符流,那就绕不开 OutputStreamWriter。
- 代码简洁地写文件,可以直接用子类 FileWriter,它的本质也是 OutputStreamWriter
Writer writer = new FileWriter(file); // 等效于 Writer writer = new OutputStreamWriter(new FileOutputStream(file));
-
BufferedWriter
如果想使用缓冲,或按整行写入,而且避免中文乱码的产生,就可以使用 BufferedReader。
// 套娃比较多了(装饰者模式) BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file,true))); // 其实写法等效于 Writer writer = new BufferedWriter(new FileWriter(file,true));
-
其他实现类的用法参考对比输入流其他实现类。
总结
输入流核心的类:FileIntputStream、IntputStreamReader
输出流核心的类:FileOutputStream、OutputStreamWriter
整理后的IO类结构
字符流与字节流使用(装饰者模式)
字节流和字符流区别
-
操作的单位不一样,一个是字节,一个是字符
-
操作中文的时候使用字符流更方便, 字节流更广泛:文本,视频,音频,图片…
-
字符流中有可以直接写字符串的方法
-
字节输出流 :
程序 —> 磁盘文件 如果不关闭流也会写入
字符输出流 :
程序 —> 缓冲 —> 磁盘文件 如果不关闭流或者刷新缓冲区,不会写入文件
字符输出流,关闭的时候会先刷新,关闭之后不能够在操作,刷新之后可以继续操作。
刷新 : 写入的数据比较多时,可以在中途手动刷新提交数据。
输入流Demo
FileInputStream
FileInputStream fis = new FileInputStream(file);
/* int i;
while ((i = fis.read()) != -1){
System.out.println((char)i);
}*/
byte[] bytes = new byte[5];
while (fis.read(bytes) != -1){
String string = new String(bytes);
System.out.println(string);
}
FileReader、InputStreamReader
// InputStreamReader isr = new InputStreamReader(new FileInputStream(file));
FileReader reader = new FileReader(file);
char[] cbuf = new char[3];
int len;
while((len = reader.read(cbuf))!=-1){
System.out.println(new String(cbuf,0,len));
}
BufferedReader
- readLine 按行读取
// BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
BufferedReader reader = new BufferedReader(new FileReader(file));
String line = null;
StringBuilder builder = new StringBuilder();
while ((line = reader.readLine()) != null) {
builder.append(line);
}
- 从当前项目resource下读取文件
ClassPathResource classPathResource = new ClassPathResource("application.properties");
InputStream inputStream = classPathResource.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
String line = null;
StringBuilder builder = new StringBuilder();
while ((line = reader.readLine()) != null) {
builder.append(line);
}
输出流Demo
FileOutputStream
FileOutputStream fos = new FileOutputStream(file,true);
fos.write("每次一字节写入,中文还不会乱码".getBytes());
fos.write("中文会乱码".getBytes(),0,10); // 指定长度就可能乱码了
fos.close(); // 不关也会写入,建议手动关
FileWriter 、OutputStreamWriter
// OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(file,true));
FileWriter writer = new FileWriter(file,true);
writer.write("直接写字符");
writer.write("中文不乱码",0,5);
// 一定要close或flush的时候才能将内容写到磁盘
writer.flush();
writer.close(); // 兼容一次flush
BufferedWriter
// BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file,true)));
BufferedWriter writer = new BufferedWriter(new FileWriter(file,true));
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 2; j++) {
writer.write(charRandomFromInt()); // 6 位随机字符
writer.write(",");
}
writer.write(charRandomFromInt());
writer.newLine(); // 换行
}
writer.flush();
writer.close();
其他Demo
文件拷贝
使用输入流读到内存,再用输出流写到磁盘,就完成了文件拷贝。
/*每次读取到数组中,并且从数组中写入到文件,边读边写*/
FileInputStream fis = new FileInputStream(src);
FileOutputStream fos = new FileOutputStream(dest);
byte[] b = new byte[1024];
int len;
while((len = fis.read(b)) != -1){
fos.write(b,0,len);
}
IO流操作一般都应该关闭;
Java7起实现了AutoCloseable的IO流支持自动关闭(一般我们用到的IO流都是有实现此接口的)
从终端读取
// System.in 就是一个InputStream
InputStreamReader isr = new InputStreamReader(System.in);
BufferedReader reader = new BufferedReader(isr);
String line = reader.readLine();
System.out.println(line);
// 或者
Scanner sc = new Scanner(System.in);
String line = sc.nextLine();