Java--IO流详解(中)--字节流
目录
字节流
字节流的核心类
InputStream 类的核心方法
1.close()
2.mark(int readAheadLimit)
3.markSupported()
4.read()
5.read(byte[] b)
6.read(byte[] b, int off, int len)
7.reset()
8.skip(long n)
InputStream 的常用子类
1.FileInputStream
2.BufferedInputStream(高级)
3.ByteArrayInputStream
注意 :ByteArrayInputStream和BufferedInputStream的区别。
4.ObjectInputStream
OutputStream 类的核心方法
1.close()
2.flush()
3.write(int b)
4.write(byte[] b)
5.write(byte[] b, int off, int len)
OutputStream 的常用子类
1.FileOutputStream
2.BufferedOutputStream(高级)
3.ByteArrayOutputStream
4.ObjectOutputStream
总结序列化&反序列化
例子
在Java--IO流详解 (上)--字符流-CSDN博客 中已经,详细介绍了有关IO流中的字符流板块以及相关方法,在此介绍另一板块字节流的定义和相关方法。
从本质上来说,字符流是基于字节流实现的。字符流在处理数据时,会先使用字节流读取字节数据,然后根据指定的字符编码将字节数据转换为字符。所以从数据的底层表示来看,字节流可以处理所有类型的数据,包括字符流所处理的文本数据。
字节流
字节流本质上和字符流是类似的。核心类自然是不同,但是核心类中包含的常用方法和相关使用定义却大差不差。
字节流的核心类
-
InputStream
:用于字节输入。 -
OutputStream
:用于字节输出。
InputStream
类的核心方法
1.close()
关闭流并释放与之关联的所有资源。
inputStream.close(); //和前面的关闭相同,通常用于有关流的相关操作后,进行关闭流处理
2.mark(int readAheadLimit)
标记流中的当前位置,以便后续可以通过 reset()
方法返回到该位置。
inputStream.mark(100); // 标记当前位置,允许向前读取最多100个字节
3.markSupported()
判断此流是否支持 mark()
操作,返回 true
或 false
。
if (inputStream.markSupported()) {
inputStream.mark(100);
}
4.read()
读取单个字节,返回读取的字节的整数值(将对应字节的二进制转成整数返回),如果到达流末尾则返回 -1
。
int b = inputStream.read();
while (b != -1) {
System.out.print((char) b);
b = inputStream.read();
}
/*
InputStream 是 Java 中所有字节输入流的抽象基类,它提供了基本的字节读取功能。当创建一个 InputStream 对象并关联到某个数据源(如文件、网络连接等)时,InputStream 会在内部维护一个指针,这个指针初始指向数据源的起始位置。
每次调用 read() 方法时,InputStream 会从当前指针所指向的位置读取一个字节的数据,并将该字节转换为对应的 int 类型值返回。然后,指针会自动向后移动一个字节的位置,以便下一次调用 read() 方法时能读取到下一个字节。
*/
5.read(byte[] b)
将字节读入数组,返回实际读取的字节数。
byte[] buffer = new byte[1024];
int bytesRead = inputStream.read(buffer); //读入数组并且返回实际读取的有效字节个数
System.out.println(new String(buffer, 0, bytesRead)); //这里的bytesRead还可以是其他小于bytesRead的数(根据用户具体想要的有效长度而定)
6.read(byte[] b, int off, int len)
将字节读入数组的某一部分,返回实际读取的字节数(该方法可以将读取的数据存放到字节数组的指定位置,而不是只能从数组起始位置开始存放,这为数据的组织和处理提供了极大的灵活性。)
byte[] buffer = new byte[1024];
int bytesRead = inputStream.read(buffer, 0, 512);
System.out.println(new String(buffer, 0, bytesRead)); //用法本质还是放bytes[],只是更加灵活了
7.reset()
重置流,使流返回到最近一次调用 mark()
方法时的位置。
inputStream.reset(); // 返回到标记的位置
8.skip(long n)
跳过指定数量的字节。
inputStream.skip(10); // 跳过10个字节
InputStream
的常用子类
1.FileInputStream
从文件中读取字节流。
FileInputStream fileInputStream = new FileInputStream("example.txt");
//一般不会单独使用,经常作为其他常用子类的使用基础
2.BufferedInputStream(高级)
提供缓冲功能,提高读取效率。
- 工作原理:当调用
bufferedInputStream
的read
方法时,它首先会尝试从内部缓冲区中读取数据。如果缓冲区中有数据,就直接从缓冲区中取出数据返回;如果缓冲区为空,BufferedInputStream
会从底层的FileInputStream
批量读取一定数量的字节数据到内部缓冲区中,然后再从缓冲区提供数据给调用者。这样做可以减少与底层数据源(如文件系统)的交互次数,提高读取性能。【注:注意甄别byte[] buffer和
bufferedInputStream内部自带的缓冲区的本质不同
】
BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream("example.txt"));
byte[] buffer = new byte[1024]; //这个手动创建的字节数组 buffer 并不是 BufferedInputStream 的缓冲区,它是用于存储从 BufferedInputStream 读取的数据的临时容器。
int bytesRead;
while ((bytesRead = bufferedInputStream.read(buffer)) != -1) {
System.out.write(buffer, 0, bytesRead);
}
/*
这里的操作其实本质就是
int bytesRead=bufferedInputStream.read(buffer);
while ( bytesRead != -1) {
System.out.write(buffer, 0, bytesRead);
}
只是这么写更加简便
*/
3.ByteArrayInputStream
从字节数组中读取字节流。
注意 :
ByteArrayInputStream和BufferedInputStream的区别。
①数据源不同
ByteArrayInputStream
:数据源为内存中的字节数组。BufferedInputStream
:需包装其他输入流,数据源是底层输入流关联的资源(如文件、网络)。②缓冲机制
ByteArrayInputStream
:无专门缓冲机制,直接从字节数组读取。BufferedInputStream
:有缓冲功能,内部缓冲区减少与底层数据源交互。③使用场景
ByteArrayInputStream
:处理内存中已有字节数组数据。BufferedInputStream
:提升从慢速数据源(文件、网络)读取数据的效率。④资源管理
ByteArrayInputStream
:无需额外资源管理,不用关闭(因为本质是在和内存交互,并不涉及数据源的交互)。BufferedInputStream
:使用完需关闭以释放资源。
byte[] data = {72, 101, 108, 108, 111}; // "Hello" in ASCII
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(data);
byte[] buffer = new byte[10];
int bytesRead;
while ((bytesRead = byteArrayInputStream.read(buffer)) != -1) {
System.out.write(buffer, 0, bytesRead);
}
4.ObjectInputStream
用于反序列化对象。
反序列化是序列化的逆过程,它将字节流重新转换为 Java 对象,恢复对象的状态和类的元数据。由于
readObject
方法返回的是Object
类型,所以需要进行强制类型转换可以再自行转换。
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("object.dat"));
Object obj = objectInputStream.readObject();
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("object.dat"));
:创建了一个ObjectInputStream
对象,它用于从文件object.dat
中读取字节流,并将其反序列化为 Java 对象。Object obj = objectInputStream.readObject();
:调用readObject()
方法从输入流中读取字节序列,并将其反序列化为一个Object
类型的对象。由于反序列化的结果是Object
类型,可能需要根据实际情况进行强制类型转换。反序列化使得程序能够恢复之前序列化保存的对象状态(这也是反序列化的根本目的)。
OutputStream
类的核心方法
1.close()
关闭流并释放与之关联的所有资源。
outputStream.close();
2.flush()
刷新流的缓冲区,确保所有数据都被写入目标。
outputStream.flush();
3.write(int b)
写入单个字节。
outputStream.write('a');
4.write(byte[] b)
写入字节数组。
byte[] data = {72, 101, 108, 108, 111}; // "Hello" in ASCII
outputStream.write(data);
5.write(byte[] b, int off, int len)
写入字节数组的某一部分。
byte[] data = {72, 101, 108, 108, 111}; // "Hello" in ASCII
outputStream.write(data, 0, 5); // 写入整个数组
OutputStream
的常用子类
1.FileOutputStream
将字节写入文件。
FileOutputStream fileOutputStream = new FileOutputStream("example.txt");
fileOutputStream.write("Hello World".getBytes());
fileOutputStream.close();
2.BufferedOutputStream(高级)
提供缓冲功能,提高写入效率。
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream("example.txt")); //bufferedOutputStream 的缓冲区在这里new文件时就存在在其内部了
bufferedOutputStream.write("Hello".getBytes());
bufferedOutputStream.write("\n".getBytes()); // 写入换行符
bufferedOutputStream.write("World".getBytes());
bufferedOutputStream.close();
3.ByteArrayOutputStream
将字节写入字节数组。
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byteArrayOutputStream.write("Hello".getBytes());
byteArrayOutputStream.write(" World".getBytes());
byte[] data = byteArrayOutputStream.toByteArray();
System.out.println(new String(data)); // 输出 "Hello World"
注意,这里看起来貌似也具备缓冲作用,但是这里的“缓冲”和
BufferedOutputStream中的缓冲机制和作用完全不同(所以ByteArrayOutputSteam本身并不具备缓冲功能)
ByteArrayOutputStream(可以动态变化大小)
:主要用于在内存中临时存储字节数据,方便后续对这些数据进行统一处理,比如将多个小的字节数据合并成一个大的字节数组,或者将这些数据转换为字符串等。它并不直接与外部的文件、网络等资源交互。BufferedOutputStream(大小固定)
:主要是为了提高向外部资源(如文件、网络连接等)写入数据的效率。它通过在内存中设置一个缓冲区,减少与底层资源的频繁交互,将多次小数据的写入操作合并为一次大数据的写入操作。
4.ObjectOutputStream
用于序列化对象。
序列化是指将 Java 对象转换为字节序列的过程,在这个过程中,对象的状态(即对象的属性值)和类的元数据(如类名、类的继承关系等)都会被转换为字节流。这些字节流可以被存储到文件、通过网络传输或者在内存中进行持久化保存。
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class SerializationExample {
public static void main(String[] args) {
try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("object.dat"))) {
// 创建一个 String 对象
String data = new String("Hello World");
// 将对象写入 ObjectOutputStream
objectOutputStream.writeObject(data);
} catch (IOException e) {
e.printStackTrace();
}
}
}
总结序列化&反序列化
- 序列化:
ObjectOutputStream
的主要作用是将 Java 对象转换为字节序列,以便进行持久化存储(如保存到文件)或网络传输。- 反序列化:
ObjectInputStream
的主要作用是将之前序列化生成的字节序列重新转换为 Java 对象,恢复对象的原始状态,使得程序可以继续使用这些对象。
例子
经过上面的讲解,通过BufferedInputStream
和 BufferedOutputStream
就可以实现文件复制。即:BufferedInputStream
用于从源文件中读取数据,BufferedOutputStream
用于将读取的数据写入到目标文件中,这样就完成了文件的复制操作。
import java.io.*;
public class FileCopyExample {
public static void main(String[] args) {
String sourceFilePath = "source.txt"; // 源文件路径
String destFilePath = "destination.txt"; // 目标文件路径
try (
// 创建 BufferedInputStream 用于读取源文件
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(sourceFilePath));
// 创建 BufferedOutputStream 用于写入目标文件
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFilePath))
) {
byte[] buffer = new byte[1024]; // 定义缓冲区
int bytesRead;
// 从源文件读取数据到缓冲区,并将缓冲区的数据写入目标文件
while ((bytesRead = bis.read(buffer)) != -1) {
bos.write(buffer, 0, bytesRead);
}
System.out.println("文件复制成功!");
} catch (IOException e) {
System.err.println("文件复制过程中出现错误: " + e.getMessage());
}
}
}