前言
- 学习之前,需要对IO流的分类有个简单的认识,下列是关于本文的IO流分类介绍
IO流的分类
- 按流的方向划分
- 输入流
- 输出流
- 按流动的数据类型划分
- 字节流
- 字符流
本文提到的OutputStream、InputStream属于字节流
- 字节流
- 输入流
- 顶级父类InputStream
- 输出流
- 顶级父类OutputStream
- 还需要明确一个概念
- 计算机中的任何数据(文本、图片、视频等)都是二进制存储的
- 8个二进制位(bit)为一个字节(byte)
- IO流的底层都是二进制
- “流”其实就是传输数据的管道,把数据从管道的一段输送到另一端
- 流入管道的一方是输入流,即读取某些数据,放到管道中
- 流出管道的一方是输出流,即把管道里的数据,写入到某些文件中
一、java.io.OutputStream
1、概述
- 此抽象类是表示字节输出流的所有类的超类
- 也就是所有字节输出流的顶级父类
2、常用API
void close()
- 关闭此输出流,释放与该流有关的所有系统资源
- 当不再需要用到流对象时,尽早关闭流对象
- 如果不确定什么时候会用到流对象,那么在程序的最后一定要记得关闭流对象
void flush()
- 刷新此输出流,强制写出任何缓冲的字节数据
void write(byte[] b)
- 将字节数组b中存储的数据,通过输出流对象写出
void write(byte[] b, int off, int len)
- 将字节数组b中存储的数据,从起始下标off开始,通过流对象写出len个数据
abstract void write(int b)
- 该方法由子类实现,将指定的字节通过此流对象写出
- 写到文档时,写入的int数据会根据ASCALL表转化为对应的字符
二、FileOutputStream
1、概述
- 是字节输出流(OutputStream)最常用的子类
2、构造方法分析
FileOutputStream(File file)
- 传入一个文件对象来建立文件输出流
- 如果文件对象不存在,会自动创建
- 如果创建失败,可能是权限不够
FileOutputStream(File file, boolean append)
- 传入一个文件对象来建立文件输出流
- 如果文件对象不存在,会自动创建
- 如果创建失败,可能是权限不够
- append
- true
- 表示在file文件原有数据的基础上,继续写入数据
- false(默认)
- 表示把file文件的内容清空后,再写入数据
- true
- 注意
- 如果是同一个流对象,操作的就是同一个文件。此时如果append为false,重复调用write方法写入数据,也是可以追加内容的
- 只有使用新的流对象(重新运行程序或创建新的流对象),append为false时才会清空原文件的数据
FileOutputStream(String name)
- 同FileOutputStream(File file),区别在于本构造器是使用文件的路径来创建字节输出流对象
FileOutputStream(String name, boolean append)
- 同FileOutputStream(File file, boolean append),区别在于本构造器是使用文件的路径来创建字节输出流对象
3、举例
- 写入单个字节
public static void main(String[] args) throws IOException {
//创建字节输出流,并自动创建a.txt文档
FileOutputStream fos = new FileOutputStream("I:\\a.txt");
//写入一个字节,65对应的ASCALL编码为A
fos.write(65);
//写入一个字节,65对应的ASCALL编码为B
fos.write(66);
//写完记得关闭字节数出流
fos.close();
}
- 写入字节数组
public static void main(String[] args) throws IOException {
//创建字节输出流,并自动创建a.txt文档
FileOutputStream fos = new FileOutputStream("I:\\a.txt");
byte[] str1 = {65,66}; //AB
byte[] str2 = "CD".getBytes(); //CD
//写入一个字节数组
fos.write(str1);
//写入一个字节数组
fos.write(str2);
//写完记得关闭字节数出流
fos.close();
}
- append设置为true,在原文件的基础上继续写入数据
- a.txt文件原来的内容:ABCD
- 程序执行后的内容:ABCDBCD
public static void main(String[] args) throws IOException {
//创建字节输出流,并自动创建a.txt文档,在原有文件的基础上继续写出内容
FileOutputStream fos = new FileOutputStream("I:\\a.txt",true);
byte[] str = {66,67,68}; //BCD
fos.write(str);
fos.close();
}
三、java.io.IntputStream
1、概述
- 此抽象类是表示字节输入流的所有类的超类
- 也就是所有字节输入流的顶级父类
2、常用API
void close()
- 关闭此输入流,释放与该流有关的所有系统资源
abstract int read()
- 从输入流中读取下一个字节数据
- 返回值为int,读取到字节的范围是0到255
- 如果达到流的末尾,读取不到下一个字节,返回-1
- 是阻塞方法
int read(byte[] b)
- 从输入流中读取一些字节数,并存储到字节缓冲数组b中
- 返回值是实际读取到的字节数
- 如果读到文件末尾,返回-1
- 此方法经常使用
- 相比读取单个字节方法,可以减少java代码进行IO操作的频率,提高性能
int read(byte[] b, int off, int len)
- 从输入流中读取一些字节数,存储到字节缓冲数组b中,并从off下标开始,读取b数组的len个数据
四、FileIntputStream
1、概述
- 是字节输入流(IutputStream)最常用的子类
2、构造方法分析
FileInputStream(File file)
- 传入一个文件对象来建立文件输入流
- 该文件必须存在,否则会报错
FileInputStream(String name)
- 传入一个文件的路径来建立文件输入流
- 传入路径代表的文件必须存在,否则报错
3、举例
- 在I盘下有一个a.txt文档,里面存放了二十六个字母
-
读取单个字节
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("I:\\a.txt");
//读取文件的第一个字节
int read = fis.read();
System.out.println(read); //97
System.out.println((char) read); //a
fis.close();
}
-
读取一个字节数组
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("I:\\a.txt");
//字节缓冲数组,一次读取10个字节
byte[] bytes = new byte[10];
//第一次读取
fis.read(bytes);
//abcdefghij
System.out.println(new String(bytes));
//第二次读取
fis.read(bytes);
//klmnopqrst
System.out.println(new String(bytes));
//第三次读取
fis.read(bytes);
//uvwxyzqrst
System.out.println(new String(bytes));
fis.close();
}
- 我们会发现,第三次读取的字符有问题,正确的输出应该为最后的六个字母
- 出现上述情况的解释
- 缓存字节数组是共用的,最后一次读取时还剩6个字母,这六个字母会放在字节数组的前六个位置,因此最后一次读整个字节数组,得到的结果就是uvwxyzqrst
- 针对上面情况的改良
- 利用read的返回值,控制每一次读取的字节个数
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("I:\\a.txt");
//字节缓冲数组,一次读取10个字节
byte[] bytes = new byte[10];
//第一次读取
int len = fis.read(bytes);
//abcdefghij
System.out.println(new String(bytes,0,len));
//第二次读取
len = fis.read(bytes);
//klmnopqrst
System.out.println(new String(bytes,0,len));
//第三次读取
len = fis.read(bytes);
//uvwxyz
System.out.println(new String(bytes,0,len));
fis.close();
}
- 可以使用下面的方式进行文件遍历读取操作
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("I:\\a.txt");
//字节缓冲数组,一次读取10个字节
byte[] bytes = new byte[10];
//读取的字节个数
int len = -1;
//当读取的字节数个数不为-1时,还没有读取到文件末尾,继续读取
while ((len = fis.read(bytes)) != -1) {
//打印0~len的数据
System.out.println(new String(bytes,0,len));
}
fis.close();
}
五、乱码问题
- 如果数据输入和输出的编码格式不同,就会产生乱码问题
- 解决办法
- 将文件的编码格式和读取文件的编码格式统一,通常用的是UTF-8编码
- UTF-8编码
- 可变长度字符编码,是1-4字节动态变化的字符编码
- 字节流的乱码问题
- 字节流可能会出现两种乱码情况
- 第一种:上面说的输入和输出编码格式不一致导致的编码
- 第二种:输入和输出的编码格式统一了,假设采用UTF-8变长字符编码,因为不确定读取的每个数据占多少个字节,因此有可能出现某个字只读了一半的情况,从而导致乱码
- 字节流可能会出现两种乱码情况
- 结论
- 乱码问题通常是出现在读取中文的情况,因此读取文字会用到字符流
- 除了读取文字的情况,其他情况最好都使用字节流,效率会更高一些