day011 IO【字节流、高效流】
1 概述
2 计算机中一切均为字节
8个比特位就是1个字节,1个二进制位就是一个比特位。
在数据传输过程中,一切数据(文本、图像、声音等)最终存储的均为一个个字节,即8个二进制数字。所以数据传输过程中使用二进制数据可以完成任意数据的传递。
我们向一个文件中存储一定的数据(一些数字),如果使用文本方式打开,则会以文本的方式解释数据。如果以视频的方式打开,则会以视频的方式解释数据。音频、可行执行文件等亦是如此。所以,在文件传输过程中,我们要时刻明确,传输的始终为二进制数据。
3 字节流
3.1 字节输出流OutputStream
OutputStream是所有字节输出流的顶层父类。并且他是一个抽象类。
我们一般使用它的子类叫做FileOutputStream。
输出流用来写,可以将java程序中的数据写入到文件中。
构造方法:
FileOutputStream(File file): 传递一个File类型的文件路径。
FileOutputStream(String name): 传递一个字符串类型的文件路径。
其他方法:
void write(int b):向文件中写入一个字节。如果传递的是一个数字,最终写入进行的会是对应ASCII码表中的字符。
void write(byte[] b): 向文件中写入一个字节数组。
void write(byte[] b, int off, int len): 向文件中写入字节数组的一部分。b表示要写入的字节数组。 off表示要从这个位置开始写。 len写入的个数。
写入的具体步骤:
1. 创建字节输出流对象,绑定一个目的地,用来写入。
2. 调用write方法进行写入。
3. 释放资源。
注意:只有字符流写需要刷新,字节流写是不需要刷新的。
字符在java中占两个字节。但是如果这个字符是ASCII码表中的字符,那么这个字符在操作系统中是占一个字节的。
注意:在操作系统中,中文默认是占2个字节。所以不能使用一次写入一个字节的方式写入一个中文字符。
3.2 字符串与字节数组的互转
字符串->字节数组
byte[] getBytes(): 把字符串转成字节数组。
字节数组->字符串
String(byte[] bytes): 将字节数组转成字符串。
String(byte[] bytes, int offset, int length) :将字节数组的一部分转成字符串。bytes字节数组。 offset从那个位置开始转。 length个数。
gbk中文占2个字节。
utf中文占3个字节。
3.3 使用字节输出流向文件中写入字节数组
void write(byte[] b): 向文件中写入一个字节数组。
void write(byte[] b, int off, int len): 向文件中写入字节数组的一部分。b表示要写入的字节数组。 off表示要从这个位置开始写。 len写入的个数。
字节数组推荐通过字符串调用getBytes方法获取到,因为这样更加的简便。
写入中文,可以先把对应的中文字符串转成字节数组
3.4 IO流释放资源的方式
无论如何,一定要保证流最终释放掉。
把close的代码放入到finally,因为finally里面的代码无论如何都会执行到。
package cn.itcast.demo01.outputstream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; /* IO流的异常处理。 无论如何,一定要保证流最终释放掉。 把close的代码放入到finally,因为finally里面的代码无论如何都会执行到。 */ public class Demo04OutputStreamException { public static void main(String[] args) { FileOutputStream fos = null; try { //创建字节输出流对象 fos = new FileOutputStream("c.txt"); //调用方法写入 fos.write(100);//d } catch(IOException e) { e.printStackTrace(); } finally { //释放资源 try { if(fos != null) { fos.close(); } } catch(IOException e) { e.printStackTrace(); } } } }
3.5 多个流对象的异常处理
shift + alt + z 直接生成try...catch...
先定义个工具类
package cn.itcast.demo01.outputstream; import java.io.Closeable; import java.io.IOException; /* 定义的工具类,里面的方法可以释放IO流对象。 所有的IO流都实现了一个接口,这个接口叫做Closeable 并且这个接口中有一个方法,可以释放资源。 void close() */ public class IOUtils { public static void close(Closeable... cs) { //遍历c for(Closeable c : cs) { //把每个c给释放掉 try { if(c != null) { c.close(); } } catch (IOException e) { e.printStackTrace(); } } } }
关闭方法
package cn.itcast.demo01.outputstream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; /* 多个流对象的异常处理。 shift + alt + z */ public class Demo05OutputStreamException { public static void main(String[] args) { FileOutputStream fos1 = null; FileOutputStream fos2 = null; try { //创建两个流对象 fos1 = new FileOutputStream("d.txt"); fos2 = new FileOutputStream("e.txt"); //向这两个文件中写数据. fos1.write(65); fos2.write(66); } catch (IOException e) { e.printStackTrace(); } finally { //释放资源 /* try { if(fos1 != null) { fos1.close(); } } catch (IOException e) { e.printStackTrace(); } try { if(fos2 != null) { fos2.close(); } } catch (IOException e) { e.printStackTrace(); } */ IOUtils.close(fos1, fos2); } } }
3.6 追加写以及换行写
如果要写换行,需要使用换行符。
windows:\r\n
linux:\n
mac:\r
文件续写:
调用有两个参数的构造方法创建对象。
FileOutputStream(File file, boolean append):如果第二个参数是true表示续写。
FileOutputStream(String name, boolean append) :如果第二个参数是true表示续写。
3.7 字节输入流InputStream
InputStream 是所有字节输入流的顶层父类。这个类是一个抽象类。
一般使用它的子类FileInputStream。
这个类的作用是用来读取,可以将文件中的字节读取到java程序中。
构造方法:
FileInputStream(File file):传递一个File类型的文件名。如果要读取的文件不存在,就会报错。
FileInputStream(String name): 传递一个字符串类型的文件名。如果读取结束返回-1.
其他方法:
int read(): 一次读取一个字节。会将读取到的字节返回。如果读取结束,返回的是-1
使用这个read方法一次只能读取一个字节。如果读取中文,就会产生问题。因为中文占两个字节。
他会把这两个字节拆开读取并转换。
int read(byte[] b): 一次读取一个字节数组。 返回值为读取到的个数。如果读取结束返回-1.
字节流不建议操作中文,因为可能会出现各种奇妙的问题。
如果操作中文,要使用字符流。
使用FileInputStream去文件中读取字节。
采用一次读取一个字节的方式。
步骤:
1. 创建FileInputStream对象。
2. 调用read方法进行读取。
3. 释放资源。
一次读一个字节的原理
一次读一个字节数组
3.8 一次读取一个字节的方式复制文件
package cn.itcast.demo02.inputstream; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; /* 字节流复制文件。 一次读取一个字节的方式。 文件复制其实就是一边读一边写。每读取到一个字节,就把读取到的字节写入到文件中。 思路: 1. 创建输入流FileInputStream,用来读取。 2. 创建输出流FileOutputStream, 用来写入 3. 开始读取,一次读取一个字节。 4. 每读取到一个字节,就把读取到的这个字节写入到目的地文件中。 5. 释放资源 */ public class Demo01Copy { public static void main(String[] args) throws IOException { //创建输入流FileInputStream,用来读取。 FileInputStream fis = new FileInputStream("aa.jpg"); //创建输出流FileOutputStream,用来写入 FileOutputStream fos = new FileOutputStream("dest01.jpg"); //开始读取,一次读取一个字节。 int i;//保存每次读取到的字节 while((i = fis.read()) != -1) { //每读取到一个字节,就把读取到的这个字节写入到目的地文件中。 fos.write(i); } //释放资源 fos.close(); fis.close(); } }
3.9 一次读取一个字节数组的方式复制文件
package cn.itcast.demo02.inputstream; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; /* 一次读取一个字节数组的方式复制文件,效率高。 思路: 1. 创建FileInputStream用来读取。 2. 创建FileOutputStream用来写入。 3. 开始读取,一次读取一个字节数组。 4. 每读取一次字节数组,就把这个读取到的字节数组写入到文件中。 5. 释放资源。 */ public class Demo02Copy { public static void main(String[] args) throws IOException { //创建FileInputStream用来读取。 FileInputStream fis = new FileInputStream("aa.jpg"); //创建FileOutputStream用来写入。 FileOutputStream fos = new FileOutputStream("dest02.jpg"); //开始复制 //开始读取,一次读取一个字节数组。 byte[] bArr = new byte[1024]; //定义字节数组用来保存读取到的字节 int len;//用来保存每次读取到的字节的个数。 //循环读取 while((len = fis.read(bArr)) != -1) { //读取到的内容会放入到bArr这个字节数组中。 //每读取一次字节数组,就把这个读取到的字节数组写入到文件中。 fos.write(bArr, 0, len);//读取几个,就写几个 } //释放资源 fos.close(); fis.close(); } }
4 缓冲流
字节缓冲流根据流的方向,共有2个
写入数据到流中,字节缓冲输出流 BufferedOutputStream
读取流中的数据,字节缓冲输入流 BufferedInputStream
它们的内部都包含了一个缓冲区,通过缓冲区读写,就可以提高了IO流的读写速度
4.1 BufferedOutputStream字节输出缓冲流
BufferedOutputStream 字节输出缓冲流,用来进行写入。
特点:
效率高,因为内部有一个缓冲区。
构造方法
BufferedOutputStream(OutputStream out): 传递一个字节输出流。
BufferedOutputStream的使用
1. 创建一个FileOutputStream对象。
2. 创建BufferedOutputStream对象。
3. 调用方法,进行写入。
4. 释放资源
其实缓冲流本身并不具备读或者写的功能,他的作用其实是为其他流提供加速。
package cn.itcast.demo03.bufferedstream; import java.io.BufferedOutputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; public class Demo01BufferedOutputStream { public static void main(String[] args) throws IOException { //创建一个FileOutputStream对象。 FileOutputStream fos = new FileOutputStream("h.txt"); //创建BufferedOutputStream对象。 BufferedOutputStream bos = new BufferedOutputStream(fos);//表示要对fos这个流进行加速。 //调用方法,进行写入。 bos.write("今天天气不错, 孔子东游".getBytes()); //释放资源。 bos.close(); } }
4.2 BufferedInputStream 字节输入缓冲流
BufferedInputStream 字节输入缓冲流,可以读取数据。
内部有一个缓冲区,可以进行加速,所以他的效率比较高。
构造方法:
BufferedInputStream(InputStream in):传递一个字节输入流。可以传递子类FileInputStream。
使用步骤:
1. 创建一个FileInputStream对象
2. 创建BufferedInputStream,并且在构造方法中传递FileInputStream对象,表示要对这个流进行加速。
3. 调用方法,进行读取。
4. 释放资源。
package cn.itcast.demo03.bufferedstream; import java.io.BufferedInputStream; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; public class Demo02BufferedInputStream { public static void main(String[] args) throws IOException { //创建一个FileInputStream对象 FileInputStream fis = new FileInputStream("source03.txt"); //创建BufferedInputStream,并且在构造方法中传递FileInputStream对象,表示要对这个流进行加速。 BufferedInputStream bis = new BufferedInputStream(fis);//表示要对fis这个流进行加速。 //调用方法,进行读取。 //一次读取一个字节 /* int i; while((i = bis.read()) != -1) { //把读取到的内容进行打印 System.out.print((char)i); } */ //一次读取一个字节数组。 byte[] bArr = new byte[1024]; int len; while((len = bis.read(bArr)) != -1) { System.out.println(new String(bArr, 0, len));//打印 } //释放资源 bis.close(); } }
5 四种方式复制文件的效率比较。
四种方式复制文件,并比较效率。
使用基本字节流一次读取一个字节复制文件 7091ms
使用基本字节流一次读取一个字节数组复制文件 18ms
使用缓冲流一次读取一个字节复制文件 145ms
使用缓冲流一次读取一个字节数组复制文件 6ms
package cn.itcast.demo03.bufferedstream; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; public class Demo03Copy { public static void main(String[] args) throws IOException { //复制之前记录一下时间 long start = System.currentTimeMillis(); //开始复制 //copyFile1("aa.jpg", "copytest01.jpg"); //copyFile2("aa.jpg", "copytest02.jpg"); //copyFile3("aa.jpg", "copytest03.jpg"); copyFile4("aa.jpg", "copytest04.jpg"); //记录时间 long end = System.currentTimeMillis(); System.out.println(end - start); } /* * 使用缓冲流一次读取一个字节数组 */ public static void copyFile4(String source, String dest) throws IOException { //创建BufferedInputStream,用来读取 BufferedInputStream bis = new BufferedInputStream(new FileInputStream(source)); //创建BufferedOutputStream,用来写入 BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(dest)); //一次读取一个字节数组。 byte[] bArr = new byte[1024]; int len; //循环读取 while((len = bis.read(bArr)) != -1) { bos.write(bArr, 0, len);//读取几个,就写几个 } //释放资源 bos.close(); bis.close(); } /* * 使用缓冲流一次读取一个字节复制文件 */ public static void copyFile3(String source, String dest) throws IOException { //创建BufferedInputStream,用来读取 BufferedInputStream bis = new BufferedInputStream(new FileInputStream(source)); //创建BufferedOutputStream,用来写入 BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(dest)); //开始读取,每读取一个字节,就把读取到的字节写入到文件中 int i; //一次读取一个字节 while((i = bis.read()) != -1) { //写入到文件 bos.write(i); } //释放资源 bos.close(); bis.close(); } /* * 基本的字节流一次读取一个字节数组 */ public static void copyFile2(String source, String dest) throws IOException { FileInputStream fis = new FileInputStream(source); FileOutputStream fos = new FileOutputStream(dest); byte[] bArr = new byte[1024]; int len; //循环读取 while((len = fis.read(bArr)) != -1) { fos.write(bArr, 0, len);//读取几个,就写几个 } //释放资源 fos.close(); fis.close(); } /* * 使用基本字节流一次读取一个字节复制文件 */ public static void copyFile1(String source, String dest) throws IOException { //创建字节输入流用来读取 FileInputStream fis = new FileInputStream(source); //创建字节输出流用来写入 FileOutputStream fos = new FileOutputStream(dest); //开始复制 int i; //一次读取一个字节 while((i = fis.read()) != -1) { //写入到文件 fos.write(i); } //释放资源 fos.close(); fis.close(); } }
5 复制多级文件夹
复制文件夹,如果这个文件夹中还有子文件夹,也可以复制过去。
源文件夹:
d:\source
aa.txt
bb.doc
cc文件夹
目标文件夹:
d:\dest
要把source文件夹复制到dest下面,source文件夹下面的所有的东西也会过去。
复制之后source里面的所有的东西都会放到下面文件夹中。
结果路径:d:\dest\source
思路:
1. 定义方法,用来将源文件夹中所有的东西复制到目标文件夹。
2. 先根据源文件夹和目标文件夹拼接一个结果路径。
3. 创建这个结果路径对应的文件夹。
4. 拿到源文件夹中的所有文件和文件夹。然后遍历。
5. 如果遍历到的是一个文件夹。递归调用把当前遍历到的这个文件夹作为源复制到结果文件夹中
如果遍历到的是一个文件,就复制文件。(使用输入流输出流一边读一边写。)
package cn.itcast.demo04.test; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; /* 复制文件夹,如果这个文件夹中还有子文件夹,也可以复制过去。 源文件夹: d:\source aa.txt bb.doc cc文件夹 目标文件夹: d:\dest 要把source文件夹复制到dest下面,source文件夹下面的所有的东西也会过去。 复制之后source里面的所有的东西都会放到下面文件夹中。 结果路径:d:\dest\source 思路: 1. 定义方法,用来将源文件夹中所有的东西复制到目标文件夹。 2. 先根据源文件夹和目标文件夹拼接一个结果路径。 3. 创建这个结果路径对应的文件夹。 4. 拿到源文件夹中的所有文件和文件夹。然后遍历。 5. 如果遍历到的是一个文件夹。递归调用把当前遍历到的这个文件夹作为源复制到结果文件夹中 如果遍历到的是一个文件,就复制文件。(使用输入流输出流一边读一边写。) */ public class Demo01CopyTest { public static void main(String[] args) throws IOException { copyDirToDir(new File("d:\\source"), new File("d:\\dest")); } /* * 定义方法,用来将源文件夹中所有的东西复制到目标文件夹。 */ public static void copyDirToDir(File source, File dest) throws IOException { // 先根据源文件夹和目标文件夹拼接一个结果路径。 //父路径是目标文件夹。 子路径是源文件夹的名字 File result = new File(dest, source.getName()); //创建这个结果路径对应的文件夹。 result.mkdirs(); //拿到源文件夹中的所有文件和文件夹 File[] files = source.listFiles(); //遍历 for(File thisFile : files) { //thisFile表示的是源文件夹中的每一个文件或者文件夹。 //如果当前遍历到的是一个文件夹, 源文件夹就是当前遍历到的文件夹, 目的地文件夹就是结果文件夹result if(thisFile.isDirectory()) { copyDirToDir(thisFile, result); } else { //如果是一个文件,就要复制文件 //创建输入流 FileInputStream fis = new FileInputStream(thisFile); //拼接一个最终复制过去文件的路径 File destFile = new File(result, thisFile.getName()); //创建一个输出流 FileOutputStream fos = new FileOutputStream(destFile); //开始读写 //一次读取一个字节数组 byte[] bArr = new byte[1024]; int len; while((len = fis.read(bArr)) != -1) { fos.write(bArr, 0, len); } //释放资源 fos.close(); fis.close(); } } } }