写在前面:
视频是什么东西,有看文档精彩吗?
视频是什么东西,有看文档速度快吗?
视频是什么东西,有看文档效率高吗?
诸小亮:下面我们学习——字节流
张小飞:什么是字节流?
诸小亮:就是以字节的形式操作文件中的数据,分为:InputStream、OutputStream
- Stream:流,InputStream是输入流,OutputStream是输出流
张小飞:这个。。。,还是不太懂,能否演示一下?
FileOutputStream
诸小亮:好吧,我们先看——FileOutputStream
FileOutputStream:文件输出流,是 OutputStream 的子类,用于保存数据到文件
张小飞:您的意思是,可以利用 FileOutputStream 把数据存到文件中?
诸小亮:是的
张小飞:那么,具体怎么操作呢?
创建对象
诸小亮:首先,需要创建 FileOutputStream 对象,有两种方式?
张小飞:是给它一个 文件路径 吗?
诸小亮:不错,比如:
public static void main(String[] args) throws FileNotFoundException {
//1. 创建输出流对象,如果指定路径上的文件不存在,会自动创建,如果存在会自动覆盖
FileOutputStream out = new FileOutputStream("G:\\learn\\hero.txt");
}
张小飞:我这里怎么报错了呢?
诸小亮:这是因为你提供的路径上的文件夹不存在,所以抛出 FileNotFoundException
张小飞:哦~~,原来如此
诸小亮:除了根据 文件路径 创建对象之外,还可以根据 File 创建,比如:
FileOutputStream out = new FileOutputStream(new File("G:\\learn\\hero.txt"));
张小飞:这种方式,如果路径上的文件夹不存在,也会报错吗?
诸小亮:是的
存储数据
张小飞:那么,如何保存数据呢?
诸小亮:使用它的 write 方法写入字节数据,比如:
public static void main(String[] args) throws IOException {
//1. 创建输出流对象
FileOutputStream out = new FileOutputStream("G:\\learn\\hero.txt");
//2. 调用写入功能
String str = "yase,lvbu,liubei";
//write:接收一个字节数组,把数组中的所有数据都写到输出流中
out.write(str.getBytes());
//3. 流是系统资源,需要释放,理论上应该放到finally中
out.close();
}
诸小亮:运行程序后,你就会发现硬盘上会多一个 hero.txt 文件
张小飞:还真是的,好神奇
诸小亮:接下来,我们优化一下代码
public static void main(String[] args) {
FileOutputStream out = null;
try{
//FileOutputStream 可能报异常,放到try中
out = new FileOutputStream("G:\\learn\\hero.txt");
String str = "yase,lvbu,liubei";
out.write(str.getBytes());
}catch (Exception e){
e.printStackTrace();
}finally {
//创建 out 对象可能失败,所以要判断是不是 null;
if(out!=null){
try {
out.close();
} catch (IOException e) {
//如果close失败,我们无法处理,直接抛出
throw new RuntimeException("流关闭失败:"+e.getMessage());
//注意:本人没见过close方法失败的,但还是得这样写
}
}
}
}
张小飞:您这是优化代码吗?
诸小亮:是的,还记得我们说过嘛,流——是系统资源,使用过后要关闭
张小飞:记得,记得,不过您上面的代码也太麻烦了吧
诸小亮:这倒是,不过还可以再次优化
public static void main(String[] args) {
//JDK7 开始,可以把 out 的定义放到 try 中,会自动 close
try(FileOutputStream out = new FileOutputStream("G:\\learn\\hero.txt")){
String str = "yase,lvbu,liubei";
out.write(str.getBytes());
}catch (Exception e){
e.printStackTrace();
}
}
张小飞:嗯,这样子就优雅多了
续写和换行
张小飞:上面的程序,每次执行程序后,发现文件中的内容都一样,不能往文件中追加内容吗?
诸小亮:目前上面代码,每次执行都会重新创建文件,所以内容都一样,当然可以追加内容,比如:
public static void main(String[] args) throws IOException {
//1. 第二个参数是 append:true表示追加数据,false表示重新创建文件
FileOutputStream out = new FileOutputStream("G:\\learn\\hero.txt", true);
//2. 调用写入功能
String str = "diaochan,change,xishi";
//write:接收一个字节数组,把数组中的所有数据都写到输出流中
out.write(str.getBytes());
//3. liu是系统资源,需要释放,应该放到finally中
out.close();
}
张小飞:嗯嗯,不错,再次执行后,内容追加成功了
张小飞:还有个问题,能不能写入数据的时候,换一行?
诸小亮:这个也没问题,加上 \r\n 就可以实现换行,比如:
结果:
张小飞:明白了,我也来试试
诸小亮:不过,建议使用 System.lineSeparator()
张小飞:嗯?这是为什么呢?
诸小亮:\r\n 是 window 系统中的换行符,也就是说,当程序跑在 window 中才可以换行
如果是 linux 、Mac 就不行了,使用 System.lineSeparator(),可以让程序跨平台
结果:
张小飞:明白了
FileInputStream
诸小亮:接着是——FileInputStream
FileInputStream:文件输出流,是 InputStream 的子类,用于读取数据到内存中
张小飞:原来使用它读取文件数据
创建对象
诸小亮:FileInputStream创建对象的方式跟 FileOutputStream 一行,不再详细解释了
public static void main(String[] args) throws FileNotFoundException {
//第一种:根据路径创建FileInputStream
FileInputStream in = new FileInputStream("G:\\learn\\hero.txt");
//第二种:根据File创建FileInputStream
// FileInputStream in = new FileInputStream(new File("G:\\learn\\hero.txt"));
}
如果路径上的文件夹都不存在,则报错,比如:
结果:
读取数据
张小飞:它是如何读取数据的呢?
诸小亮:它读取的方式有好几种,我们一个个介绍
单字节读取
诸小亮:第一种,单字节读取
张小飞:这是什么意思?
诸小亮:它提供了一个 read 方法,但是每次只能读取一个字节,比如:
FileInputStream in = new FileInputStream("G:\\learn\\hero.txt");
//读取数据,每次只是读取一个字节,当 返回 -1 时,文件读取完毕
int read = in.read();
System.out.println(read);
//关闭流
in.close();
结果:
张小飞:呀,结果怎么是一个数字?
诸小亮:多新鲜啊,字节是 byte ,这不是数字类型吗?
张小飞:噢~,差点儿忘记了,那为什么是 121 呢?
诸小亮:文件中第一个字母是 y,而 121 就是字母 y 对应的ASCII码
张小飞:明白了,再把它转换为 char 类型,就行了吧
诸小亮:是的,把 int 转换为 char
结果:
一次性读取
张小飞:这一次读取一个字节也太费劲了,有没有其他方式?
诸小亮:readAllBytes 可以一次读出所有内容,返回字节数组,比如:
FileInputStream in = new FileInputStream("G:\\learn\\hero.txt");
//readAllBytes:一次读出所有内容
byte[] bytes = in.readAllBytes();
//把字节数组转换问String
String content = new String(bytes);
System.out.println(content);
//关闭流
in.close();
结果:
张小飞:嗯嗯,这个好,以后就用这个吧
读取指定大小
诸小亮:一点儿都不好,如果文件太大,这样读取的话会占用很多内存,导致程序卡死的
张小飞:。。。。。,那怎么办?
诸小亮:它提供的 read 有重载的方法,可以一次读取指定大小
- read(byte b[]):一次读取指定数量的字节,放到数组中,并返回读取的字节数量
张小飞:原来还有这种方法,怎么不早点说出来呢?
诸小亮:这不是让你也了解一下其他方法嘛
public static void main(String[] args) throws IOException {
FileInputStream in = new FileInputStream("G:\\learn\\hero.txt");
//指定一次读取 16 字节
byte[] buffer = new byte[16];
//read方法读取的内容都会放到buffer中,当返回 -1 时候,文件读取完毕
int read = in.read(buffer);
//把字节数组转换为String
String content = new String(buffer);
System.out.println(content);
//关闭流
in.close();
}
结果:
张小飞:这才读取了 16 字节,得把文件中的内容都读出来啊
诸小亮:使用循环就行了
public static void main(String[] args) throws IOException {
FileInputStream in = new FileInputStream("G:\\learn\\hero.txt");
//指定一次读取16字节
byte[] buffer = new byte[16];
//read方法读取的内容都会放到buffer中,当返回-1时候,文件读取完毕
int read = 0;
while((read = in.read(buffer)) != -1){
//把字节数组转换问String
String content = new String(buffer);
System.out.print(content);
}
//关闭流
in.close();
}
结果:
张小飞:您这个输出,怎么多出来一些内容?
诸小亮:因为最后一次读出来的数据不够16字节,数组中还存留着上一次读出的内容,所以。。。。
张小飞:那,这样肯定不行啊,怎么解决?
诸小亮:放心,放心,read的返回值是每次读出来的字节数,可以用用它的返回值
诸小亮:上图,每次读出来几个字节就转换几个字节
结果:
张小飞:嗯嗯,这样好多了
缓冲区大小的设置
诸小亮:你知道我们使用缓冲区的目的是什么吗?
张小飞:不就是为了一次多读出一些数据吗?
诸小亮:其实准确来说,是减少读取文件的次数
张小飞:嗯?为什么?
诸小亮:因为读写文件是IO操作,需要操作硬盘,频繁的读写性能会很差
张小飞:那,目前缓冲区是 16 字节,如果一个文件太大,不是还会读很多次吗?
诸小亮:所以我们要合理设置缓冲区的大小
张小飞:怎么合理设置呢?
诸小亮:有个 available 方法 :返回流中剩余的未读的字节数量(其实就是文件大小)
张小飞:然后呢?
诸小亮:然后就可以根据我们的需求设置缓冲区了,比如:
按照上图中设置,最多读 6 次就可以读完
张小飞:那如果文件很大呢?比如 5 个 G
诸小亮:。。。。。,那这就要看你机器性能了,性能很高,就可以设置比较大的缓冲区
张小飞:哦,好吧
诸小亮:另外,还有一个获取文件大小的方法——length
复制文件
诸小亮:我们已经学习了 FileInputStream 、FileOutputStream,你能用它们做一个文件复制功能吗?
张小飞:这个。。。。,还不太行
诸小亮:好吧,先给你一个示例
public static void main(String[] args) throws IOException {
//从源文件中读
FileInputStream in = new FileInputStream("G:\\learn\\hero.txt");
//目标文件
FileOutputStream out = new FileOutputStream("G:\\hero.txt");
//指定一次读取16字节
byte[] buffer = new byte[16];
int read = 0;
while((read = in.read(buffer)) != -1){
//每次只是写入这一次读取的字节数量
out.write(buffer,0,read);
}
//关闭流
in.close();
out.close();
}
结果:
诸小亮:其中原理图都给你画好了
BufferedInputStream 和 BufferedOutputStream
诸小亮:接下来我们看看——BufferedInputStream、BufferedOutputStream
张小飞:这是做什么的?
诸小亮:它们是——字节流缓冲区对象,目的是增强 InputStream 和 OutputStream 的功能
张小飞:如何增强?
诸小亮:BufferedInputStream、BufferedOutputStream 内部有一个字节数组
张小飞:缓冲区?
诸小亮:是的,使用它们后,读写顺序是这样的
- 先从源文件读数据到 **BufferedInputStream **的内部数组
- 然后从 **BufferedInputStream **中再读到自定义的数组
- 接着从自己定义的数组写到 BufferedOutputStream 的内部数组
- 最后写到硬盘的新文件中
代码:
public static void main(String[] args) throws IOException {
BufferedInputStream inBuffer = new BufferedInputStream(new FileInputStream("G:\\learn\\hero.txt"));
BufferedOutputStream outBuffer = new BufferedOutputStream(new FileOutputStream("G:\\hero.txt"));
byte[] buffer = new byte[16];
int read = 0;
//之前是从硬盘中读取,现在是从 BufferedInputStream 中读取
while((read = inBuffer.read(buffer)) != -1){
//之前直接写到硬盘上,现在写到 BufferedOutputStream 中
outBuffer.write(buffer,0,read);
}
//关闭流
inBuffer.close();
inBuffer.close();
}
张小飞:是不是写错了,我运行了程序,发现文件中没有任何内容啊
诸小亮:这是因为 BufferedOutputStream 中的数组大小是 8192 字节,而我们文件 83 字节
- 默认只有 BufferedOutputStream 中的数组满了后才会写到硬盘上
张小飞:那该怎么办?
诸小亮:可以手动调用 flush 方法,把 BufferedOutputStream 中的数据刷到硬盘上
张小飞:好的,我再试试
中文数据
张小飞:刚刚读写的都是英文,能不能弄点儿中文?
诸小亮:当然可以了
public static void main(String[] args) throws IOException {
FileOutputStream out = new FileOutputStream("G:\\learn\\hero.txt");
String str = "亚瑟";
out.write(str.getBytes());
out.close();
}
张小飞:我看了一下文件大小,默认用的是 UTF-8 编码吗?
诸小亮:用的什么编码,得看你自己在 idea 中怎么配置的