Java的IO流是实现输入/输出流的基础,它可以方便的实现数据的输入/输出操作,在Java中把不同的输入输出源(键盘、文件、网络连接等)抽象描述为流,通过流的方式允许Java程序使用相同的方式来访问不同的输入/输出源。

一. 流的分类

  • 输入流和输出流(根据内存判断)
  • 字节流和字符流(8位字节和16位字符)
  • 节点流和处理流(包装流)

InputStream/Reader (读取)
所有输入流的基类,前者是字节输入流,后者是字符输入流

OutputStream/Writer (写入)
所有输出流的基类,前者是字节输出流,后者是字符输出流

二. FileInputStream和FileOutputStream(字节流)

FileInputStream:从本地磁盘读取字节数据。
例:

1
2
3
4
5
6
7
8
9
10
11
12
13
//创建字节输入流,即要读取的文件
FileInputStream fis = new FileInputStream("D:\\学习\\Java\\b\\a.txt");
//创建一个长度为1024的“竹筒”
byte[] bbuf = new byte[1024];
//用于保存实际读取的字节
int hasRead = 0;
//循环读取数据
while((hasRead=fis.read(bbuf))>0){
//将字节数组转换为字符串输入
System.out.println(new String(bbuf,0,hasRead));
}
//关闭文件输入流
fis.close();

FileOutputStream:将字节数据写到指定文件。如果文件存在则写到指定文件,如果目标文件不存在则自动创建该文件,如果目标文件所在目录也不存在,则报错
例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//用FileInputStream输入,FileOutputStream输出来实现文件复制
try (FileInputStream fis = new FileInputStream("D:\\学习\\Java\\b\\a.txt");//从读取的文件
FileOutputStream fos = new FileOutputStream("D:\\学习\\Java\\b\\c.txt");//创建要写入的文件
){
byte[] buff = new byte[64];
int hasRead = 0;
//循环读取数据
while((hasRead = fis.read(buff))>0){
//写入文件
fos.write(buff, 0, hasRead);
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

三. FileReader和FileWriter(字符流)

FileReader:以字符为基本单读取文本文件
例:

1
2
3
4
5
6
7
8
9
10
11
12
13
//创建字符输入流,即要读取的文件
FileReader fr = new FileReader("D:\\学习\\Java\\b\\a.txt");
//创建一个长度为32的“竹筒”
char[] cbuf = new char[32];
//用于保存实际读取的字符
int hasRead = 0;
//循环读取数据
while((hasRead=fr.read(cbuf))>0){
//将字符数组转换为字符串输入
System.out.println(new String(cbuf,0,hasRead));
}
//关闭文件输入流
fr.close();

FileWriter:将字符数据写入到文本文件
例:

1
2
3
4
5
6
7
8
9
10
try (FileWriter fw = new FileWriter("D:\\学习\\Java\\b\\d.txt"))
{
fw.write("白日依山尽\r\n");
fw.write("黄河入海流\r\n");
fw.write("欲穷千里目\r\n");
fw.write("更上一层楼\r\n");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

四. PrintStream和PrintWriter(处理流)

PrintStream:打印类,只能封装OutputStream类型的字节输出流
例:

1
2
3
4
5
6
7
8
try(FileOutputStream fos = new FileOutputStream("D:\\学习\\Java\\b\\e.txt"); //创建要写入的文件
PrintStream ps = new PrintStream(fos); //用PrintStream处理流来包装OutputStream
) {
ps.print("成功"); //用PrintStream直接写入数据
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}

PrintWriter:打印类,既可封装OutputStream类型的字节输出流,也能封装Writer类型的字符输出流
例:

1
2
3
4
5
6
7
8
try (FileWriter fw = new FileWriter("D:\\学习\\Java\\b\\f.txt"); //创建写入的文件
PrintWriter pw = new PrintWriter(fw); //用PrintWriter处理流包装Writer
){
pw.print("f测试成功"); //直接写入数据
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

五. BufferedReader和BufferedWriter(字符流)

BufferedReader:是从字符输入流中读取文本,将多个字符存入缓存提供读取字符、数据或行的有效方法。
BufferedWriter:将字符输出流缓冲后写出。(缓冲区容量可以在构造方法中指定)

六. InputStreamReader和OutputStreamWriter(转换流)

InputStreamReader:将字节输入流转换成字符输入流
例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
try(//将system.in(字节)对象转换为reader(字符)对象
InputStreamReader reader = new InputStreamReader(System.in);
//将普通的reader包装成BufferedReader
BufferedReader br = new BufferedReader(reader);
) {
String line = null;
while((line = br.readLine())!=null){
if(line.equals("exit")){
System.exit(1);
}
System.out.println("输入的内容为:"+ line);
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

OutputStreamWriter:将字节输出流转换成字符输出流

七. 重定向标准输入/输出

在默认情况下,当程序通过System.in来获取输入时,实际上是从键盘读取输入,当程序通过System.out执行输出时,程序总是输出到屏幕
例:重定向到指定文件输出

1
2
3
4
5
6
7
8
9
//设置输出文件路径
try (PrintStream ps = new PrintStream(new FileOutputStream("D:\\学习\\Java\\b\\g.txt"));){
//将标准输出重定向到ps输出流
System.setOut(ps);
System.out.println("g测试成功");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

例:重定向到指定文件输入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//设置要读取的文件路径
try(FileInputStream fis = new FileInputStream("D:\\学习\\Java\\b\\g.txt")) {
//将标准输入重定向到fis输入流
System.setIn(fis);
Scanner sc = new Scanner(System.in);
sc.useDelimiter("\n"); //将回车符作为分隔符
while(sc.hasNext()){
System.out.println("内容:"+sc.next());
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}

八. RandomAccessFile

既可以读取文件内容,也可以向文件输出内容
任意访问,可以跳转到文件的任意地方读写数据
只能读写文件,不能读写其他IO节点

例:读取文件指定位置后的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
try {
//设定要读取的文件
RandomAccessFile raf = new RandomAccessFile("D:\\学习\\Java\\b\\g.txt","r");
raf.seek(3); //从3字节处开始读取
byte[] buff = new byte[1024];
int hasRead = 0;
//循环读取
while((hasRead = raf.read(buff))>0){
System.out.print(new String(buff,0,hasRead));
}
raf.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

例:向指定文件后面追加内容

1
2
3
4
5
6
7
8
9
//设定要写入的文件
try (RandomAccessFile raf = new RandomAccessFile("D:\\学习\\Java\\b\\g.txt","rw")){
//指针跳转到当前文件结尾处开始写入
raf.seek(raf.length());
raf.write("追加内容\r\n".getBytes());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

例:向文件指定位置插入插入数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//使用临时文件来保存插入点后的数据
File temp = File.createTempFile("temp", null);
temp.deleteOnExit();
try (RandomAccessFile raf = new RandomAccessFile(fileName,"rw"); //设置项指定文件执行读写操作
FileOutputStream tempOut = new FileOutputStream(temp); //向指定文件执行写入操作
FileInputStream tempIn = new FileInputStream(temp); //向指定文件执行读取操作
)
{
raf.seek(pos); //跳转到文件指定点
/*将指定点后的数据保存到临时文件中*/
byte[] buff = new byte[32];
int hasRead = 0;
while((hasRead = raf.read(buff))>0){
tempOut.write(buff,0,hasRead); //从raf中读取数据到tempOut中去
}
raf.seek(pos); //重新的跳转到文件指定点
raf.write(insertContent.getBytes()); //将数据写入到指定点后面
//从临时文件tempIn中读取数据写入到raf文件中
while((hasRead = tempIn.read(buff))>0){
raf.write(buff,0,hasRead);
}
}
}
public static void main(String[] args) throws IOException{
insert("D:\\学习\\Java\\b\\g.txt",3,"中间插入");
}

九. 对象序列化

对象序列化指将一个Java对象写入IO流中。对象的反序列化则指从IO流中恢复该Java对象。
序列化机制会把内存中的Java对象转换成与平台无关的二进制流,从而永久地保存在磁盘上或是通过网络传输到另一个网络节点。
为了使某个类是可序列化的,必须实现Serializable和Externalizable接口

序列化对象
例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//建立一个普通的Java类Person实现了Serializable接口
public class Person implements java.io.Serializable {
private String name;
private int age;
public Person(String name,int age){
System.out.println("有参构造函数");
this.name = name;
this.age = age;
}
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 static void main(String[] args){
//创建一个ObjectOutputStream输出流
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\学习\\Java\\b\\h.txt"));){
Person per = new Person("孙悟空",500);
oos.writeObject(per); //将per对象写入输出流
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

反序列化对象
例:

1
2
3
4
5
6
7
8
9
10
11
12
13
public static void main(String[] args) throws ClassNotFoundException{
//创建一个ObjectInputStream输入流
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\学习\\Java\\b\\h.txt"));){
Person per = (Person)ois.readObject(); //从输入流读取一个java对象,并强制转换为Person类型
System.out.println("名字:"+per.getName()+"\n年龄:"+per.getAge());
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

序列化机制算法
1.所有保存到磁盘中的对象都有一个序列化编号。 
2.当程序试图序列化一个对象时,程序先检查该对象是否已经被序列化过。如果从未被序列化过,系统就会将该对象转换成字节序列并输出。如果已经序列化过,将直接输出一个序列化编号。

序列化对象注意事项
1.对象的类名、属性都会被序列化;而方法、static属性(静态属性)、transient属性(即瞬态属性)都不会被序列化(这也就是第4条注意事项的原因)
2.虽然加static也能让某个属性不被序列化,但static不是这么用的
3.要序列化的对象的引用属性也必须是可序列化的,否则该对象不可序列化,除非以transient关键字修饰该属性使其不用序列化。
4.反序列化地象时必须有序列化对象生成的class文件(很多没有被序列化的数据需要从class文件获取)
5.当通过文件、网络来读取序列化后的对象时,必须按实际的写入顺序读取。

最后更新: 2020年07月27日 03:39

原始链接: https://www.lousenjay.top/2018/01/27/I-O流详解/