IO流——字节、字符、输入、输出流

例子见Github-JavaSE-Day07

对于程序来说:
读(Reader)进来(InputStream)(in) 写(Writer)出去(OutputStream/PrintStream)(out)

流表示一个文件将数据返送到另一个文件,包含一个流向的问题
当前所写的代码作为参照物:
 从一个文件中读取数据到程序叫输入流
 从程序写入文件叫输出流

字符是一个逻辑概念。
纯文本的,用字符流多一点,如果有图片,只能用字节流。

在这里插入图片描述

注意:每次编写IO流的时候一定要注意关闭流。原因:一个进程最多打开1024个文件。如果不关闭流对象,会占用系统资源,并发执行会卡死。

步骤

  1. 选择合适的IO流对象
  2. 创建对象
  3. 传输数据
  4. 关闭流对象

节点流

字节流

处理图片、视频,其他文件格式的时候,最好还是使用字节流处理。
在这里插入图片描述

InputStream

FileInputStream

FileInputStream构造函数:
FileInputStream(File file)
通过打开与实际文件的连接创建一个 FileInputStream ,该文件由文件系统中的 File对象 file命名。

字节流读取数据的方式:
InputStream inputStream = new FileInputStream("abc.txt");

  1. inputStream.read() 返回值为数据的下一个字节,如果达到流的末尾, -1。每次读取一个字节。直接(char)强制类型转换进行字符读取。
  2. inputStream.read(byte[] b) 返回值为读取到缓冲区的总字节数,已经到达流的末尾,那么 -1。添加缓冲区方式进行读取,每次将数据添加到缓冲区,满了之后一次读取,而不是每个字节读取。
  3. inputStream.read(byte[] b,off,len) 返回值为读取到缓冲区的总字节数,已经到达流的末尾,那么 -1。第一个字节读取存储在元素b[off]…这些字节将存储在元素b[off]至b[off+ k -1]。
ByteArrayInputStream

ByteArrayInputStream包含一个内部缓冲区,其中包含可以从流中读取的字节。

1
2
3
4
5
6
7
8
String str = "www.baidu.com";
byte[] buffer = str.getBytes();
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(buffer);
int read = 0;
while ((read = byteArrayInputStream.read()) != -1) {
byteArrayInputStream.skip(4);
System.out.println((char)read);
}

OutputStream

FileOutputStream

字节流写入数据的方式:

1
2
3
4
File file = new File("aaa.txt");
OutputStream outputStream = new FileOutputStream(file);
outputStream.write(99);
outputStream.write("\r\nliu".getBytes());
ByteArrayOutputStream

该类实现了将数据写入字节数组的输出流。

1
2
3
4
5
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
// write()将指定的字节写入此字节数组输出流 {
byteArrayOutputStream.write(123);
byteArrayOutputStream.write("www.baidu.com".getBytes()); // 无返回值
System.out.println(byteArrayOutputStream.toString());
BufferedInputStream 和 BufferedOutputStream

BufferedInputStream构造方法:
BufferedInputStream(InputStream in)
创建一个 BufferedInputStream并保存其参数(输入流 in) ,供以后使用
注:

  • 一个BufferedInputStream为另一个输入流添加了功能。当创建好流对象后,在内部会创建一个内部缓冲区数组,当从流中读取或跳过(skip)字节时,内部缓冲区将根据需要从所包含的输入流中重新填充。
  • 中文的时候必须用Reader,用字节流是没有办法进行处理的
1
2
3
4
5
6
7
8
9
10
11
12
File file = new File("abc.txt");
FileInputStream fileInputStream = null;
BufferedInputStream bufferedInputStream = null;
try {
fileInputStream = new FileInputStream(file);
bufferedInputStream = new BufferedInputStream(fileInputStream);
int read = 0;
while ((read = bufferedInputStream.read()) != -1) {
// bufferedInputStream.skip(10);
System.out.print((char)read);
}
……

读出的中文部分是乱码。

BufferedOutputStream构造方法:
BufferedOutputStream(OutputStream out)
创建一个新的缓冲输出流,以将数据写入指定的底层输出流。

1
2
3
4
5
6
7
8
9
10
File file = new File("123.txt");
FileOutputStream fileOutputStream = null;
BufferedOutputStream bufferedOutputStream = null;
try {
fileOutputStream = new FileOutputStream(file);
bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
bufferedOutputStream.write(98);
bufferedOutputStream.write("www.aidu.com".getBytes());
}
……
DataInputStream 和 DataOutputStream

用来装饰其他输入流,以从底层输入流中读取基本java数据类型。即可以指定输入输出的类型。

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
FileInputStream fileInputStream = null;
DataInputStream dataInputStream = null;
DataOutputStream dataOutputStream = null;
FileOutputStream fileOutputStream = null;

try {
// 向文件中写入数据流
fileOutputStream = new FileOutputStream("abc.txt");
dataOutputStream = new DataOutputStream(fileOutputStream);
dataOutputStream.writeBoolean(true);
dataOutputStream.writeInt(1);
dataOutputStream.writeShort(222);
dataOutputStream.writeDouble(22.33);
dataOutputStream.writeUTF("www.baidu百度");

// 从文件读取数据流
fileInputStream = new FileInputStream("abc.txt");
dataInputStream = new DataInputStream(fileInputStream);
// 读写必须一致
System.out.println(dataInputStream.readBoolean());
System.out.println(dataInputStream.readInt());
System.out.println(dataInputStream.readShort());
System.out.println(dataInputStream.readDouble());
System.out.println(dataInputStream.readUTF());
}
……
ObjectInputStream 和 ObjectOutputStream (用的少)

序列化 :把Java对象转换为字节序列的过程
反序列化:字节序列–>Java对象
作用:(1)序列化:在传递和保存对象时,保证对象的完整性和可传递性。对象转换为字节流以便在网络上传输或者保存在本地文件中。
(2)反序列化:根据字节流中保存的对象状态和描述信息,通过反序列化重建对象。
核心:整个过程核心点就是字节流中所保存的对象状态及描述信息

注意:
如果需要通过io流进行传输,必须要实现以下两点:

  1. 实现序列化接口。ObjectOutputStream使用writeObject()时,里面的自定义类必须实现Serializable,否则会保错。
  2. 需要在自定义类中声明long serialVersionUID = 1L;这个值只是起标志作用,多少无所谓,只要写死就OK。

如果不想被人可见的属性比如密码,可以加上transient关键字。

transient:瞬时。使用它修饰的变量,在进行序列化的时候,不会被序列化。写入文件时,打开并没有密码,读取时候也不会显示。

通过ObjectOutputStream的write相关方法写入到文件中的,打开来会发现是乱码,人无法看懂。

字符流

在这里插入图片描述

字符流读取数据的方式:
一个中文汉字占的字节数要看具体编码。

如果用字符流能够正确读出中文汉字,而字节流在处理的时候会出现中午文乱码。

1
Reader reader = new FileReader("abc.txt");
  1. // 读的是字符
    int read = reader.read();
    // 可以读取汉字
    System.out.println((char)read);
    <!--7-->
  2. int length = 0;
    char[] chars = new char[1024];
    while ((length = reader.read(chars)) != -1) {
        System.out.println(new String(chars,0,length));
    }
    <!--8-->
    

注:什么时候需要加flush?

  • 保险起见,在输出流关闭之前,每次都进行flush再关闭。
  • 当某个输出流对象中带有缓冲区的时候就需要进行flush。

处理流

本来要按照一个字节一个字节进行处理,现在通过处理流在字节外包了一层,相当于是一个对应的字符了。现在处理的时候是按照字符为单位进行处理的,而不是再按照字节为单位的。

字符流优点
1)效率。如果是纯文本,用字符流处理效率相对是比较高的。
2)灵活性。如果用字符流进行输出的时候,在new的时候可以指定对应的编码格式

InputStreamReader字节流通向字符流的桥梁:它使用指定的 charset 读取字节并将其解码为字符。
它使用的字符集可以由名称指定或显式给定,或者可以接受平台默认的字符集。

OutputStreamWriter字符流通向字节流的桥梁

InputStreamReader构造函数:
InputStreamReader(InputStream in)
创建一个使用默认字符集的InputStreamReader。

处理流的使用
例如下面将字节流包裹后进行读写。关闭流对象时候建议按照创建时的逆序关闭。

1
2
3
fileOutputStream = new FileOutputStream(file);
outputStreamWriter = new OutputStreamWriter(fileOutputStream,"jbk");
outputStreamWriter.write(99);
1
2
3
4
5
6
fileInputStream = new FileInputStream(file);
inputStreamReader = new InputStreamReader(fileInputStream,"gbk");
// 数据较少无法占用1024个缓冲区时,不用循环方式,读取一次即可
char[] chars = new char[1024];
int len = inputStreamReader.read(chars);
System.out.println(new String(chars,0,len));

为什么需要这样的处理流?
网络传输的数据肯定是纯文本,此时获取到的就是InputStream或者OutputStream的对象。但它们都是根据字节流来处理的,非常低效。因为以知都是纯文本,所以我们包装成InputStreamReader或者OutputStreamReader对象,可以提高效率。
Socket中函数只有getOutputStream()getInputStream(),没有getWriter(),getReader()。想要高效处理,需要在上面套字符流。