在开始前,我们再来回顾一下这张图:
本篇博客主要为大家讲解字节流。
我们都知道,一切文件(文本、视频、图片)的数据都是以二进制的形式存储的,传输时也是。所以,字节流可以传输任意类型的文件数据。
1. 字节输入流InputStream
java.io.InputStream
是字节输入流的超类。来看一下它的抽象方法:
close()
:关闭此输入流并释放与此流相关的系统资源。int read()
: 从输入流读取数据的下一个字节。read(byte[] b)
: 该方法返回的 int 值代表的是读取了多少个字节,读到几个返回几个,读取不到返回-1available()
:返回输入流中可以读取的字节数
2. 字节输入流FileInputStream
InputStream有很多子类,FileInputStream就是实现子类中最简单的一个。看名字就知道是文件输入流,用于将数据从文件中读取数据。
2.1 构造方法
- FileInputStream(String name):创建一个 FileInputStream 对象,并打开指定名称的文件进行读取。文件名由 name 参数指定。如果文件不存在,将会抛出 FileNotFoundException 异常。
- FileInputStream(File file):创建一个 FileInputStream 对象,并打开指定的 File 对象表示的文件进行读取。
实战一下:
@Test
public void test005() throws IOException {FileInputStream fis = new FileInputStream("test.txt");int data;while((data=fis.read())!=-1){System.out.println((char)data);}fis.close();
}
2.2 常用方法
一、读取字节
read()
方法会读取一个字节并且返回其正数表示,如果到达文件末尾,返回-1。读取时发生错误会抛IOException异常。
实战代码:
@Test
public void test005() throws IOException {FileInputStream fis = new FileInputStream("test.txt");int data;while((data=fis.read())!=-1){System.out.println((char)data);}fis.close();
}
二、读取字节数组
read(byte[] b)
方法会从输入流中最多读取b.length
个字节,并将它们存储到缓冲区数组 b 中。
实战代码:
@Test
public void test005() throws IOException {File file = new File("test.txt");FileInputStream fis = new FileInputStream(file);byte[] buf = new byte[1024];int count;while((count=fis.read(buf))!=-1){System.out.println(new String(buf,0,count));}fis.close();
}
3. 字节输出流OutputStream
java.io.OutputStream
是字节输出流的超类。来看一下它的抽象方法:
close()
:关闭此输出流并释放与此流相关联的系统资源。flush()
:刷新此输出流并强制缓冲区的字节被写入到目的地。write(byte[] b)
:将 b.length 个字节从指定的字节数组写入此输出流。write(byte[] b, int off, int len)
:从指定的字节数组写入 len 字节到此输出流,从偏移量 off开始。 也就是说从off个字节数开始一直到len个字节结束。
4. 字节输出流FileOutputStream
OutputStream
有很多子类,最简单的一个子类就是 FileOutputStream 。看名字就知道是文件输出流,用于将数据写入到文件。
4.1 构造方法
- FileOutputStream(String name):创建一个 FileOutputStream 对象,并打开指定名称的文件进行写入。文件名由 name 参数指定。如果文件不存在,则创建一个新文件;如果文件已经存在,则覆盖原有文件。
- FileOutputStream(String name, boolean append):参数append表示是否为"追加"模式,默认为false不追加也就是清空原有数据。
- FileOutputStream(File file):创建一个 FileOutputStream 对象,并打开指定的 File 对象表示的文件进行写入。
实战一把:
@Test
public void test006() {FileOutputStream fos = null;try {fos = new FileOutputStream("test2.txt");fos.write("i love cola.你好世界!".getBytes());} catch (IOException e) {throw new RuntimeException(e);} finally {try {fos.close();} catch (IOException e) {throw new RuntimeException(e);}}
}
4.2 常用方法
一、写入字节
write(int b)
方法,每次可以写入一个字节,代码如下:
// 使用文件名称创建流对象
FileOutputStream fos = new FileOutputStream("fos.txt");
// 写出数据
fos.write(97); // 第1个字节,等效于'a' 因为a的ASCII码为97
fos.write(98); // 第2个字节
fos.write(99); // 第3个字节
// 关闭资源
fos.close();
当使用 write(int b) 方法写出一个字节时,参数 b 表示要写出的字节的整数值。由于一个字节只有8位,因此参数 b 的取值范围应该在 0 到 255 之间,超出这个范围的值将会被截断。例如,如果参数 b 的值为 -1,那么它会被截断为 255,如果参数 b 的值为 256,那么它会被截断为 0。
在将参数 b 写入输出流中时,write(int b) 方法只会将参数 b 的低8位写入,而忽略高24位。这是因为在 Java 中,整型类型(包括 byte、short、int、long)在内存中以二进制补码形式表示。当将一个整型值传递给 write(int b) 方法时,方法会将该值转换为 byte 类型,只保留二进制补码的低8位,而忽略高24位。
例如,如果要写出的整数为 0x12345678
,它的二进制补码表示为 0001 0010 0011 0100 0101 0110 0111 1000
。当使用 write(int b) 方法写出该整数时,只会将二进制补码的低8位 0111 1000 写出,而忽略高24位 0001 0010 0011 0100 0101 0110。这就是参数 b 的高24位被忽略的原因。
0111 1000 是一个8位的二进制数,它对应的十进制数是 120,对应的 ASCII 码字符是小写字母 “x”。在 ASCII 码表中,小写字母 “x” 的十进制 ASCII 码值为 120。因此,如果使用 write(int b) 方法写出一个字节值为 0x78(十进制为 120),那么写出的结果就是小写字母 “x”。
验证一下:
FileOutputStream fos = null;
try {fos = new FileOutputStream("example.txt");fos.write(120);fos.write('x');fos.write(0x12345678);
} catch (IOException e) {e.printStackTrace();
} finally {if (fos != null) {try {fos.close();} catch (IOException e) {e.printStackTrace();}}
}
最终文本中:
二、写入字节数组
write(byte[] b)
,代码示例:
// 使用文件名称创建流对象
FileOutputStream fos = new FileOutputStream("fos.txt");
// 字符串转换为字节数组
byte[] b = "你好世界~Hello World".getBytes();
// 写入字节数组数据
fos.write(b);
// 关闭资源
fos.close();
另外再补充一下,在 Windows 系统中,换行符号是\r\n
,而在Linux系统中换行则是\n
。
5. 字节流实战
实战环节,要求通过字节流复制图片。
实现很简单,就是通过字节输入流读取图片,再用字节输出流输出到指定位置即可。
@Test
public void test007() throws IOException{FileInputStream fis = new FileInputStream("snoopy.png");FileOutputStream fos = new FileOutputStream("snoopy_copy.png");byte[] buf = new byte[1024];int count=0;while((count=fis.read(buf))!=-1){fos.write(buf,0,count);}fis.close();fos.close();
}
上面的代码创建了一个 FileInputStream 对象以读取原始图片文件,并创建了一个 FileOutputStream 对象以写入复制后的图片文件。然后,使用 while 循环逐个读取原始图片文件中的字节,并将其写入复制后的图片文件中。最后,关闭输入流和输出流释放资源。
6. 小结
InputStream
是字节输入流的抽象类,它定义了读取字节数据的方法,如read()
、read(byte[] b)
、read(byte[] b, int off, int len)
等。OutputStream
是字节输出流的抽象类,它定义了写入字节数据的方法,如 write(int b)
、write(byte[] b)
、write(byte[] b, int off, int len)
等。这两个抽象类是字节流的基础。
FileInputStream
是从文件中读取字节数据的流,它继承自InputStream
。FileOutputStream
是将字节数据写入文件的流,它继承自OutputStream
。这两个类是字节流最常用的实现类之一。