字节流

字节流的输入输出都是以字节为单位处理数据,一个字节等于 8 个二进制位,即由 8 位 0 或 1 组成的序列,如 01102300 为一个字节。在Java中所有的字节流都是从抽象类InputStream或OutputStream 类继承而来。大部分字节流使用方式相同,只是它们的构造方式可能不一样。

InputStream

image.png

InputStream提供的方法

方法名 签名 描述
read public abstract int read() 这是一个抽象方法,需要子类实现,用于从输入流中读取下一个字节数据。由于一个字节是 8 位,返回值介于0~255之间,如果没有可读数据,即达到了流的末尾,则返回-1。方法会一直阻塞直到有字节数据可读、到达流末尾或者发生IO异常。
read public int read(byte b[]) 从输入流中读取b.length个字节,并储存到字节数组b中,如果b⻓度为 0 ,则不会读取任何字节数据,方法返回 0 。 read(byte b[]) -> read(byte b[], int off, int len) -> read
read public int read(byte b[], int off, int len) 从输入流中读取最多len个字节,如果len为 0 ,不会读取任何字节数据,直接返回 0 ,读取的字节从数组b的off位置开始存放。 read(byte b[], int off, int len) -> read()
skip public long skip(long n) 跳过并丢弃输入流中的n个字节数据,通过调用read(byte b[], int off, int len)读取字节并丢弃。
available public int available() 返回下一次操作时,输入流中预估的可读字节数量
close public void close() 关闭输入流,释放与流关联的系统资源
markSupported public boolean markSupported() 用于测试输入流是否支持mark与reset方法
mark public synchronized void mark(int readlimit) 在输入流中标记当前位置,标记位置后,还可以读取最多readlimit字节,后续可以通过reset方法回到上一次mark的位置,达到重复读取相同字节数据的操作,使用mark方法,则markSupported必须返回true,在关闭的流中调用mark方法,不会有任何效果。
reset public synchronized void reset() 在流中复位,回到上一次调用mark方法标记的位置,如果未发现任何mark或者上一次mark标记后读取的字节数超过readlimit,则发生IOException,使用reset方法,则markSupported必须返回true。

InputStream中的方法大部分都是一种规范,最终的字节流子类某些方法可能会有所不同。

OutputStream

OutputStream提供的方法

方法名 签名 描述
write public abstract void write(int b) 这是一个抽象方法,需要子类实现,用于将指定的字节数据写入到输出流中,在Java中int是 32 位,4个字节,write方法实际写入的只是整型数据b的低 8位,其余 24 位直接忽略。write public void write(byte b[]) 将字节数组b中的数据写入到输出流中,write(byte b[])->write(byte b[], int off, int len)->write()
write public void write(byte b[], int off, int len) 从字节数组b下标为off的位置开始,将len个字节数据写入到输出流中。write(byte b[], int off, int len)->write()
flush public void flush() 刷新输出流,强制一些缓冲输出流将内部缓冲的一些字节数据立即写入目标地,如果目标地是磁盘文件,调用flush方法后,并不保证文件⻢上更新,这取决于操作系统。
close public void close() 关闭输出流,释放与流关联的系统资源

操作使用

文件IO字节流FileInputStream、FileOutputStream是使用最广泛的两个类,以这两个类进行操作说明。

public class CopyBytes {
public static void main(String[] args) throws IOException {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
// 文件数据输入流,如果文件不存在或者是一个目录,则发生FileNotFoundException
fis = new FileInputStream("D://test/source.txt");
// 数据输出流,目标文件不存在,大部分系统都会自动创建,如果文件已存在,则覆盖文件中已有内容
fos = new FileOutputStream("D://test/target.txt");
int c;
// 如果返回-1,表示已经读到流的末端
while ((c = fis.read()) != -1) {
// 如果是ascii字符,每个字符对应的整型值一定是0~127,
// 如果是汉字,而一个汉字由多个字节组成,因此会输出多个整型数据,128~255
System.out.print(c + " ");
fos.write(c);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 从输入流读取数据完成后,一定要关闭流,释放系统资源
if (fis != null) {
fis.close();
}
// 将数据写入输出流完成后,一定要关闭流,释放系统资源
if (fos != null) {
fos.close();
}
}
}
}

IO流操作完成后,一定要在finally语句块中进行关闭,Java7开始提供了try-with-resources语法,编
译器识别该语法后,在字节码文件中自动生成了对应的finally语句块,并在语句块中关闭对应的流。

try (...) {
...
} catch (Exception e) {
...
}

用try-with-resources语法读写文件代码

public class CopyBytes2 {
public static void main(String[] args) {
try (FileInputStream fis = new FileInputStream("D://test/source.txt");
FileOutputStream fos = new FileOutputStream("D://test/target.txt")) {
int c;
// 内容长度(可读取的字节数量)
System.out.println("内容有效字节数: " + fis.available());
while ((c = fis.read()) != -1) {
System.out.print(c + " ");
fos.write(c);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

如source.txt文件有如下字符内容:I2023,我 ,文件编码为UTF-8时输出 9 个字节(汉字占用了三个字节)。

最后三个大于 127 的十进制值则表示汉字 我,其余的对应的是ascii字符。

FileOutputStream可以控制字节内容写入起始位置

  • 通过new FileOutputStream(D://test/target.txt) 构造输出流

每次从文件开头写入字节数据,因此会覆盖上一次打开文件时写入的内容。

public class FileOutputStreamDemo {
public static void main(String[] args) {
try (FileOutputStream fos = new FileOutputStream("D://test/target.txt")) {
String s = "2023,Ksyun 星云训练营";
// 调用FileOutputStream的write(byte b[])方法,写入多个字节
fos.write(s.getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
e.printStackTrace();
}
}
}
  • 通过new FileOutputStream(D://test/target.txt, true) 构造输出流

每次从文件末尾写入字节数据,相当于向文档中追加数据内容,多次运行上述main方法,会发现
D://test/target.txt文件内容增加。

字节流是IO流中最底层的低水平操作流,文件中包含的是字符数据,更高效的方法是通过字符流进行操作。