字节流


一切皆为字节

一切文件数据 (文本, 图片, 视频等) 在存储时, 都是以数字的形式保存, 是一个个的字节, 那么传输时一样如此. 所以, 字节流可以传输任意文件数据. 在操作的时候, 我们要时刻明确, 无论使用什么样的流对象, 底层传输的始终为二进制数据.

字节输出流 (OutputStream)

​java.io.OutputStream​​抽象类是表示字节输出流的所有类的超类, 将指定的字节信息写出到目的地. 它发定义了子节输出的基本功能方法:


  • ​public void close()​​: 关闭此输出流并释放与此流相关的任何系统资源
  • ​public void flush()​​: 刷新此输出流并强制任何缓冲的输出字节被写出
  • ​public void write(byte[] b)​​: 将 b.length 字节指定的字节数组写入此输出流
  • ​public void write(byte[] b, int off, int len)​​: 从指定的字节数组写入 len 字节, 从偏移量 off 开始输出到此输出流
  • ​public abstract void write(int b)​​: 将指定的字节输出
    注: close 方法, 当完成流的操作时. 必须调用此方法, 释放系统资源.

FileOutputStream 类

OutputStran 有很多子类, 我们从最简单的一个子类开始.

```java.io.FileOutputStream``类是文件输出流, 用于将数据写出到文件.

构造方法

​public FileOutputStream(File file)​​: 创建文件输出流以写入由指定的 File 对象表示的文件

​public FileOutputStream(String name)​​: 创建文件输出流以指定的名称写入文件

当创建一个流对象时, 必须传入一个文件路径. 该路径下, 如果没有这个文件, 会创建该文件. 如果有这个文件, 会清空这个文件的数据.

构造举例, 代码如下:

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
public class Test {
public static void main(String[] args) throws FileNotFoundException {
// 使用File对象创建流对象
File file = new File("a.txt");
FileOutputStream fos1 = new FileOutputStream(file);
// 使用文件名创建流对象
FileOutputStream fos2 = new FileOutputStream("b.txt");
}
}

写出字节数据

写出字节

写出字节: ​​write(int b)​​方法, 每次可以写出一个字节数据. 代码使用演示:

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class Test {
public static void main(String[] args) throws IOException {
// 使用文件名称创建流对象
FileOutputStream fos = new FileOutputStream("a.txt");
// 写出数据
fos.write(97); // 写出第1个字节
fos.write(98); // 写出第2个字节
fos.write(99); // 写出第3个字节
// 关闭资源
fos.close();
}
}
输出结果:
abc

注:

虽然参数为 int 类型四个字节, 但是只会保留一个字节的信息写出.

流操作完毕后, 必须释放系统资源. 调用 close 方法, 千万记得.

写出字节数组

写出字节数组: ​​write(byte[] b)​​, 每次可以写出数组中的数据, 代码使用演示:

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class Test {
public static void main(String[] args) throws IOException {
// 使用文件名称创建流对象
FileOutputStream fos = new FileOutputStream("a.txt");
// 字符串转换为字节数组
byte[] b = "程序员".getBytes();
// 写出字节数组数据
fos.write(b);
// 关闭资源
fos.close();
}
}
输出结果:
程序员

写出指定长度的字节数组

写出指定长度字节数组:

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class Test {
public static void main(String[] args) throws IOException {
// 使用文件名创建流对象
FileOutputStream fos = new FileOutputStream("a.txt");
// 字符串转换为字节数组
byte[] b = "abcde".getBytes();
fos.write(b,2,2);
// 关闭资源
fos.close();
}
}
输出结果:
cd

数据追加续写

经过以上的演示, 每次程序运行, 创建输出流对象, 都会清空目标文件中的数据. 如何保留目标文件中的数据, 还能继续添加数据呢?


  • ​public FileOutputStream(File file, boolean append)​​: 创文件输出流以写入由指定的 File 对象表示文件
  • ​public FileOutputStream(String name, boolean append)​​: 文件输出流指定的名称写入文件

这两个构造方法, 参数中都需要传入一个 boolean 类型的值. true 表示追加数据, flase 表示清空原有数据. 这样创建的输出流对象, 就可以指定是否追加续写了, 代码使用演示:

import java.io.FileOutputStream;
import java.io.IOException;
public class Test {
public static void main(String[] args) throws IOException {
// 使用文件名创建流对象
FileOutputStream fos = new FileOutputStream("a.txt",true);
// 字符串转换为字节数组
byte[] b = "abcde".getBytes();
// 写出字节数组数据
fos.write(b);
// 关闭资源
fos.close();
}
}
输出结果:
文件操作前:cd
文件操作后:cdabcde

写出换行

Windows 系统里, 换行符号是 \r\n. 可以指定是否换行.

代码展示:

import java.io.FileOutputStream;
import java.io.IOException;
public class Test8 {
public static void main(String[] args) throws IOException {
// 使用文件名创建流对象
FileOutputStream fos = new FileOutputStream("a.txt");
// 定义字节数组
byte[] words = {97,98,99,100,101};
// 遍历数组
for (byte word : words) {
// 写出一个字节
fos.write(word);
// 写出一个换一行, 换行符号转成数组写出
fos.write("\r\n".getBytes());
}
// 关闭资源
fos.close();
}
}
输出结果:
a
b
c
d
e

注意事项

回车符 \r 和换行符 \n:


  • 回车符: 回到一行的开头 ( return )
  • 换行符: 下一行 ( newline )

系统中的换行:


  • Windows 系统里, 每行结尾是 回车 + 换行, 即 \r\n
  • Unix 系统里, 每行结尾只有 换行, 即 \n
  • Mac 系统里. 每行结尾是 回车, 即 \r. 从 Mac OS X 开始与 Linux 统一

字节输入流 (InputStream)

​java.io.InputStream​​抽象类是表示字节输入流的所有类的超类, 可以读取字节信息到内存中. 它定义了字节输入流基本共性功能方法:


  • ​public void close()​​: 关闭此输入流并释放与此相关联的任何系统资源
  • ​public void int read()​​: 从输入流读取数据的下一个字节
  • ​public int read(byte[] b)​​: 从输入流中读取一些字节数, 并将它们存储到字节数组 b 中

注: close 方法, 当完成流的操作时, 必须调用此方法, 释放系统资源.

FileInputStream 类

​java.io.FileInputStream​​类是文件输入流, 从文件中读取字节.

构造方法


  • ​FileInputStream(File file)​​: 通过打开与实际文件的连接来创建一个 FileInputStream, 该文件由文件系统中的 File 对象 file 命名.
  • ​FileInputStream(String name)​​: 通过打开与实际文件的连接来创建一个 FileInputStream, 该文件由文件系统路径名 name 命名.

当你创建一个流对象时, 必须传入一个文件的路径. 该路径下, 如果没有该文件, 会抛出​​FileNotFoundExption​​.

代码展示:

import java.io.File;
import java.io.FileInputStream;;
import java.io.IOException;
public class Test {
public static void main(String[] args) throws IOException {
// 使用File 对象创建流对象
File file = new File("a.txt");
FileInputStream fis1 = new FileInputStream(file);
// 使用文件名称创建流对象
FileInputStream fis2 = new FileInputStream("a.txt");
}
}

读取字节数据

读取字节

读取字节: read 方法, 每次可以读取一个字节的数据, 提升为 int 类型. 读取到文件末尾, 返回 -1. 代码展示:

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class Test {
public static void main(String[] args) throws IOException {
// 使用文件名称创建流对象
FileInputStream fis = new FileInputStream("read.txt");
// 读取数据, 返回一个字节
int read = fis.read();
System.out.println((char) read);
read = fis.read();
System.out.println((char) read);
read = fis.read();
System.out.println((char) read);
read = fis.read();
System.out.println((char) read);
read = fis.read();
System.out.println((char) read);
// 读取到末尾, 返回-1
read = fis.read();
System.out.println(read);
// 关闭资源
fis.close();
}
}
输出结果:
a
b
c
d
e
-1

循环改进读取方式, 代码使用演示:

import java.io.FileInputStream;
import java.io.IOException;
public class Test {
public static void main(String[] args) throws IOException {
// 使用文件名称创建流对象
FileInputStream fis = new FileInputStream("read.txt");
// 定义变量, 保存数据
int b;
// 循环读取
while ((b = fis.read()) != -1 ){
System.out.println((char)b);
}
// 关闭资源
fis.close();
}
}
输出结果:
a
b
c
d
e

注:


  • 虽然读取了一个字节, 但是会自动提升为 int 类型.
  • 流操作完毕后, 必须释放系统资源, 调用 close 方法, 千万记得

使用字节数组读取

使用字节数组读取: ​​read(byte[] b)​​, 每次读取 b 的长度个字节到数组中, 返回读取到的有效字节个数. 读取到末尾时, 返回 -1. 代码展示:

import java.io.FileInputStream;
import java.io.IOException;
public class Test {
public static void main(String[] args) throws IOException {
// 使用文件名称创建流对象
FileInputStream fis = new FileInputStream("read.txt"); // 文件中为abcde
int len;
// 定义字节数组, 作为装字节数据的容器
byte[] b = new byte[2];
// 循环读取
while ((len = fis.read(b)) != -1) {
// 每次读取后, 把数组编程字符串打印
System.out.println(new String(b));
}
// 关闭资源
fis.close();
}
}
输出结果:
ab
cd
ed

错误数据 d, 是由于最后一次读取时, 只读取一个字节 e. 数组中, 上次读取的数据没有被完全替换, 所以要通过 len, 获取有效的字节, 代码使用展示:

import java.io.FileInputStream;
import java.io.IOException;
public class Test13 {
public static void main(String[] args) throws IOException {
// 使用文件名称创建流对象
FileInputStream fis = new FileInputStream("read.txt");
// 定义变量, 作为有效个数
int len;
// 定义字节数组, 作为装字节数据的容器
byte[] b = new byte[2];
// 循环读取
while ((len = fis.read(b)) != -1) {
// 每次读取后, 把数组变成字符串打印
System.out.println(new String(b,0,len)); // len: 每次读取的有效字节个数
}
// 关闭资源
fis.close();
}
}
输出结果:
ab
cd
e

注: 使用数组读取, 每次读取多个字节, 减少了系统间的 IO 操作次数. 从而提高了读写的效率, 建议开发中使用.

字节流练习: 图片复制

复制原理图解

Java基础 第四节 第十二课_java

案例实现

复制图片文件, 代码使用演示:

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class Test14 {
public static void main(String[] args) throws IOException {
// 创建流对象
// 指定数据源
FileInputStream fis = new FileInputStream("C:/Users/Windows/Pictures/OIP.jpg");
// 指定目的地
FileOutputStream fos = new FileOutputStream("copy.jpg");
// 定义数组
byte[] b = new byte[1024];
// 定义长度
int len;
// 循环读取
while ((len = fis.read(b)) != -1) {
// 写出数据
fos.write(b,0,len);
}
fos.close();
fis.close();
}
}

执行结果:

Java基础 第四节 第十二课_java_02

注: 流的关闭原则: 先开后关, 后开先关.