I/O
教程来源:https://how2j.cn/k/io/io-file/345.html
流关系图
- 流分为字节流和字符流
- 字节流下面常用的又有数据流和对象流
- 字符流下面常用的又有缓存流
FileInputStream, FileOutputStream(文件输入输出流)
对于文件输入输出流,流的构造函数的参数都是文件。
FileInputStream fis = new FileInputStream(f);
FileOutputStream fos = new FileOutputStream(f);
try {
File f = new File("d:/lol.txt");
// 创建基于文件的输入流
FileInputStream fis = new FileInputStream(f);
// 通过这个输入流,就可以把数据从硬盘,读取到Java的虚拟机中来,也就是读取到内存中
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
File f = new File("d:/lol.txt");
FileOutputStream fos = new FileOutputStream(f);
} catch (IOException e) {
e.printStackTrace();
}
InputStream, OutputStream(字节输入输出流)
用于以字节的形式读取和写入数据。
InputStream和OutputStream是抽象类。
FileInputStream 是InputStream子类。
流的实例(如fis, fos)在向字节数组输入、从字节数组输出时,参数都是字节数组
fis.read(all); // 以字节流的形式读取文件所有内容
fos.write(data); // 把数据写入到输出流
try {
//准备文件lol.txt其中的内容是AB,对应的ASCII分别是65 66
File f =new File("d:/lol.txt");
//创建基于文件的输入流
FileInputStream fis =new FileInputStream(f);
//创建字节数组,其长度就是文件的长度
byte[] all =new byte[(int) f.length()];
//以字节流的形式读取文件所有内容
fis.read(all);
//每次使用完流,都应该进行关闭
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
FileOutputStream 是OutputStream子类
try {
File f = new File("d:/lol2.txt");
// 准备长度是2的字节数组,用88,89初始化,其对应的字符分别是X,Y
byte data[] = { 88, 89 };
// 创建基于文件的输出流
FileOutputStream fos = new FileOutputStream(f);
// 把数据写入到输出流
fos.write(data);
// 关闭输出流
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
关闭流的方式
标准关闭流的方式
- 首先把流的引用声明在try的外面,如果声明在try里面,其作用域无法抵达finally.
- 在finally关闭之前,要先判断该引用是否为空
- 关闭的时候,需要再一次进行try catch处理
File f = new File("d:/lol.txt");
FileInputStream fis = null;
try {
fis = new FileInputStream(f);
byte[] all = new byte[(int) f.length()];
fis.read(all);
for (byte b : all) {
System.out.println(b);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 在finally 里关闭流
if (null != fis){ // 如果引用不为空,关闭
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
try-with-resources
在try, catch, finally结束的时候自动关闭,回收相关资源。
在try中多个资源用分号隔开即可。
File f = new File("d:/lol.txt");
try (FileInputStream fis = new FileInputStream(f)) {
byte[] all = new byte[(int) f.length()];
fis.read(all);
} catch (IOException e) {
e.printStackTrace();
}
字符流
Reader字符输入流,Writer字符输出流,专门用于字符的形式读取和写入数据。
FileReader 是Reader子类。
FileReader与FileInputStream相似,差异表现为接收数组为字符数组类型。
File f = new File("d:/lol.txt");
// 创建基于文件的Reader
try (FileReader fr = new FileReader(f)) {
// 创建字符数组,其长度就是文件的长度
char[] all = new char[(int) f.length()];
// 以字符流的形式读取文件所有内容
fr.read(all);
} catch (IOException e) {
e.printStackTrace();
}
同样的,FileWriter 是Writer的子类,以FileWriter 为例把字符串写入到文件
File f = new File("d:/lol2.txt");
// 创建基于文件的Writer
try (FileWriter fr = new FileWriter(f)) {
// 以字符流的形式把数据写入到文件中
String data="abcdefg1234567890";
char[] cs = data.toCharArray(); // 注意这里要把String转换成字符数组
fr.write(cs);
} catch (IOException e) {
e.printStackTrace();
}
它们也是使用read和write函数。
编码问题
工作后经常接触的编码方式有如下几种:
ISO-8859-1 ASCII 数字和西欧字母
GBK GB2312 BIG5 中文
UNICODE (统一码,万国码)
其中
ISO-8859-1 包含 ASCII
GB2312 是简体中文,BIG5是繁体中文,GBK同时包含简体和繁体以及日文。
UNICODE 包括了所有的文字,无论中文,英文,藏文,法文,世界所有的文字都包含其中
用FileInputStream 字节流正确读取中文
假设已经使用 FileInputStream fis 将文本读出到了字节数组byte [] all中
将字节数组转换成GBK编码的字符串:
String str = new String(all,"GBK");
注: 在GBK的棋盘上找到的 某个字 后,JVM会自动找到 这个字 在UNICODE这个棋盘上对应的数字,并且以UNICODE上的数字保存在内存中。
用FileReader 字符流正确读取中文
FileReader使用的编码方式是Charset.defaultCharset()的返回值,如果是中文的操作系统,就是GBK
FileReader是不能手动设置编码方式的,为了使用其他的编码方式,只能使用InputStreamReader来代替
new InputStreamReader(new FileInputStream(f),Charset.forName("UTF-8"));
InputStreamReader也是字符流
举例,使用FileReader和InputStreamReader分别读取一个txt文件(使用UTF-8编码)
18~25行:FileReader
28~36行:InputStreamReader
package stream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
public class TestStream {
public static void main(String[] args) throws UnsupportedEncodingException, FileNotFoundException {
File f = new File("E:\\project\\j2se\\src\\test.txt");
System.out.println("默认编码方式:"+Charset.defaultCharset());
//FileReader得到的是字符,所以一定是已经把字节根据某种编码识别成了字符了
//而FileReader使用的编码方式是Charset.defaultCharset()的返回值,如果是中文的操作系统,就是GBK
try (FileReader fr = new FileReader(f)) {
char[] cs = new char[(int) f.length()];
fr.read(cs);
System.out.printf("FileReader会使用默认的编码方式%s,识别出来的字符是:%n",Charset.defaultCharset());
System.out.println(new String(cs));
} catch (IOException e) {
e.printStackTrace();
}
//FileReader是不能手动设置编码方式的,为了使用其他的编码方式,只能使用InputStreamReader来代替
//并且使用new InputStreamReader(new FileInputStream(f),Charset.forName("UTF-8")); 这样的形式
try (InputStreamReader isr = new InputStreamReader(new FileInputStream(f),Charset.forName("UTF-8"))) {
char[] cs = new char[(int) f.length()];
isr.read(cs);
System.out.printf("InputStreamReader 指定编码方式UTF-8,识别出来的字符是:%n");
System.out.println(new String(cs));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
输出如下:
FileInputStream字节流 与 InputStreamReader (或FileReader ) 字符流读取的区别
FileInputStream是先把文本读取到字节数组,在转换到字符串时手动设置编码。
InputStreamReader是在构造流的时候设置编码,直接把文本读取到字符数组。
缓存流
以介质是硬盘为例,字节流和字符流的弊端:
在每一次读写的时候,都会访问硬盘。 如果读写的频率比较高的时候,其性能表现不佳。
缓存流在读取的时候,会一次性读较多的数据到缓存中,以后每一次的读取,都是在缓存中访问,直到缓存中的数据读取完毕,再到硬盘中读取。
缓存流必须建立在一个存在的流的基础上
使用缓存流读取数据
缓存字符输入流 BufferedReader 可以一次读取一行数据
File f = new File("d:/lol.txt");
try (FileReader fr = new FileReader(f);
BufferedReader br = new BufferedReader(fr);){
while (true) {
// 一次读一行
String line = br.readLine();
if (null == line)
break;
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
使用缓存流写出数据
PrintWriter 缓存字符输出流, 可以一次写出一行数据
注意PrintWriter实例实用的方法。
File f = new File("d:/lol2.txt");
try (
FileWriter fw = new FileWriter(f);
PrintWriter pw = new PrintWriter(fw);){
pw.println("garen kill teemo");
pw.println("teemo revive after 1 minutes");
pw.println("teemo try to garen, but killed again");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
flush
有的时候,需要立即把数据写入到硬盘,而不是等缓存满了才写出去。 这时候就需要用到flush
例如在上面pw.println("…")下一行加上pw.flush();
数据流
使用数据流的writeUTF()和readUTF() 可以进行数据的格式化顺序读写
如本例,通过DataOutputStream 向文件顺序写出 布尔值,整数和字符串。 然后再通过DataInputStream 顺序读入这些数据。
数据流也必须建立在一个存在的流的基础上
注: 要用DataInputStream 读取一个文件,这个文件必须是由DataOutputStream 写出的,否则会出现EOFException,因为DataOutputStream 在写出的时候会做一些特殊标记,只有DataInputStream 才能成功的读取。
package stream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class TestStream {
public static void main(String[] args) {
write();
read();
}
private static void read() {
File f =new File("d:/lol.txt");
try (
FileInputStream fis = new FileInputStream(f);
DataInputStream dis =new DataInputStream(fis);
){
boolean b= dis.readBoolean();
int i = dis.readInt();
String str = dis.readUTF();
System.out.println("读取到布尔值:"+b);
System.out.println("读取到整数:"+i);
System.out.println("读取到字符串:"+str);
} catch (IOException e) {
e.printStackTrace();
}
}
private static void write() {
File f =new File("d:/lol.txt");
try (
FileOutputStream fos = new FileOutputStream(f);
DataOutputStream dos =new DataOutputStream(fos);
){
dos.writeBoolean(true);
dos.writeInt(300);
dos.writeUTF("123 this is gareen");
} catch (IOException e) {
e.printStackTrace();
}
}
}
对象流
对象流指的是可以直接把一个对象以流的形式传输给其他的介质,比如硬盘
一个对象以流的形式进行传输,叫做序列化。 该对象所对应的类,必须是实现Serializable接口
对象输入输出流也是建立在一个存在的流上
写入和读取对象使用oos.writeObject(原对象)和ois.readObject()方法
//创建一个Hero garen
Hero h = new Hero();
h.name = "garen";
h.hp = 616;
//准备一个文件用于保存该对象
File f =new File("d:/garen.lol");
try(
//创建对象输出流
FileOutputStream fos = new FileOutputStream(f);
ObjectOutputStream oos =new ObjectOutputStream(fos);
//创建对象输入流
FileInputStream fis = new FileInputStream(f);
ObjectInputStream ois =new ObjectInputStream(fis);
) {
oos.writeObject(h);
Hero h2 = (Hero) ois.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.in
System.in返回的是InputStream指向命令行输入的字节流,它的read方法以字节流的方式来读取命令行的输入的数据
Scanner(System.in)创建一个Scanner,控制台会一直等待输入,直到敲回车键结束,把所输入的内容传给Scanner,作为扫描对象。如果要获取输入的内容,则只需要调用Scanner的nextLine()方法即可。
package stream;
import java.util.Scanner;
public class TestStream {
public static void main(String[] args) {
Scanner s = new Scanner(System.in);
int a = s.nextInt();
System.out.println("第一个整数:"+a);
int b = s.nextInt();
System.out.println("第二个整数:"+b);
}
}
一些Scanner常见的API: