- 流向: - 输入流 读取数据 - 输出流 写出数据 - 数据类型: - 字节流 - 字节输入流 读取数据 InputStream - 字节输出流 写出数据 OutputStream - 字符流 - 字符输入流 读取数据 Reader - 字符输出流 写出数据 Writer
字符流存在的问题 * 字符流只能操作文本文件,如果操作非文本文件(图片,视频,音频)等文件,会出现数据丢失问题。 * 如果要操作非文本文件,只能使用字节流,因为字节流可以操作任意类型的文件。
OutputStream类概述
* Output:输出 * Stream:字节流 * 是一个字节流。 * 是一个抽象类,不能直接创建该类的对象。 * 是所有字节输出流的父类。
OutputStream类常用的子类 * FileOutputStream * BufferedOutputStream
FileOutputStream类构造方法
* FileOutputStream(String pathname) 根据文件路径创建文件字节输出流对象 * FileOutputStream(File file) 根据文件对象创建文件字节输出流对象 * FileOutputStream(File file, boolean append) * FileOutputStream(String pathname, boolean append) * 可以通过 append 指定是否是追加输出 * append:true表示追加,false表示不追加。
字节输出流操作步骤:
* A:创建字节输出流对象 //FileOutputStream(File file) File file = new File("a.txt"); FileOutputStream fos = new FileOutputStream(file); //FileOutputStream(String name) FileOutputStream fos = new FileOutputStream("a.txt"); 创建字节输出流对象了做了几件事情: 1.调用系统功能去创建文件 2.创建fos对象 3.把fos对象指向这个文件 * B:写数据 fos.write("hello,IO".getBytes()); fos.write("12345678".getBytes()); * C:释放资源 //关闭此文件输出流并释放与此流有关的所有系统资源。 fos.close();
字节输出流写数据
* public void write(int b):写一个字节 * public void write(byte[] b):写一个字节数组 * public void write(byte[] b,int off,int len): * 将字节数组b的一部分内容输出到目标文件中 * off:数组的起始索引 * len:要输出的字节个数
public static void main(String[] args) throws IOException { // 创建字节输出流对象 FileOutputStream fos = new FileOutputStream("test\\a.txt"); // public void write(int b):写一个字节 fos.write(97); // 97 -- 底层二进制数据 -- 通过记事本打开 -- 找97对应的字符值 -- a fos.write('\r');//\r\n为换行 fos.write('\n'); fos.write(65);//A fos.write(256+66);//B // public void write(byte[] b):写一个字节数组 byte[] bys = { 97, 98, 99, 100, 101 }; fos.write(bys); // public void write(byte[] b,int off,int len):写一个字节数组的一部分 fos.write(bys, 1, 3); // 释放资源 fos.close(); }
* 如何实现数据的换行? * 因为不同的系统针对不同的换行符号识别是不一样的? * windows:\r\n * linux:\n * Mac:\r * 如何实现数据的追加写入? * 用构造方法带第二个参数是true的情况即可
加入异常处理的字节输出流操作
public static void main(String[] args) { // 为了在finally里面能够看到该对象就必须定义到外面,为了访问不出问题,还必须给初始化值 FileOutputStream fos = null; try { fos = new FileOutputStream("a.txt"); fos.write("java".getBytes()); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { // 如果fos不是null,才需要close() if (fos != null) { // 为了保证close()一定会执行,就放到这里了 try { fos.close(); } catch (IOException e) { e.printStackTrace(); } } } }
// 利用字节输出流一次写一个字节数组的方式向C盘的b.txt文件输出内容。 public static void main(String[] args) throws IOException { // 1.创建字节输出流FileOutputStream对象并指定文件路径。 FileOutputStream fos = new FileOutputStream("c:/b.txt"); // 2.调用字节输出流的write(byte[] buf)方法写出数据。 byte[] buf = "i love java".getBytes(); fos.write(buf); // 3.关闭资源 fos.close(); }
字节输入流操作步骤:
* A:创建字节输入流对象 FileInputStream fis = new FileInputStream("a.txt"); * B:调用read()方法读取数据,并把数据显示在控制台 int by = 0; // 读取,赋值,判断 while ((by = fis.read()) != -1) { System.out.print((char) by); } // 数组的长度一般是1024或者1024的整数倍 byte[] bys = new byte[1024]; int len = 0; while ((len = fis.read(bys)) != -1) { System.out.print(new String(bys, 0, len)); } * C:释放资源 fis.close();
字节输入流读取数据:
* abstract int read(); 从流关联的目标文件中读取一个字节,返回读取到的字节 * int read(byte[] b); * 从流关联的目标文件中读取一个字节数组的数据, * 返回实际读取到字节个数。 * int read(byte[] b, int off, int len) * 将读取到的字节存储到指定的字节数组b中 * off:存储字节的起始索引 * len:指定能够存储的字节个数。
int by = 0; // 读取,赋值,判断 while ((by = fis.read()) != -1) { System.out.print((char) by); } // 数组的长度一般是1024或者1024的整数倍 byte[] bys = new byte[1024]; int len = 0; while ((len = fis.read(bys)) != -1) { System.out.print(new String(bys, 0, len)); }
/* 用字节流将C盘下的a.png图片复制到D盘下(文件名保存一致) 要求:一次读写一个字节数组的方式进行复制 */ public static void main(String[] args) throws IOException { // 创建字节输入流对象并关联文件 FileInputStream fis = new FileInputStream("c:/a.png"); // 创建字节输出流对象并关联文件 FileOutputStream fos = new FileOutputStream("d:/a.png"); // 定义数据接收读取的字节数 byte[] buffer = new byte[1024]; // 定义变量接收读取的字节数 int len = -1; // 循环读取图片数据 while((len = fis.read(buffer)) != -1) { // 将字节数组中的数据写出到目标文件中 fos.write(buffer,0,len); } // 关闭流 fis.close(); fos.close(); }
缓冲区类(高效类)
* 写数据:BufferedOutputStream * 读数据:BufferedInputStream 构造方法可以指定缓冲区的大小,但是我们一般用不上,因为默认缓冲区大小就已经足够了。 为什么不传递一个具体的文件或者文件路径,而是传递一个OutputStream对象呢? * 原因很简单,字节缓冲区流仅仅提供缓冲区,为高效而设计的。但是呢,真正的读写操作还得靠基本的流对象实现。 BufferedOutputStream类注意事项 * 使用缓冲字节输出流输出数据不是直接输出到目标文件中,而是先存储到内部的缓冲区 数组中,当缓冲区数组满了或调用了flush或close方法,则由FileOutputStream将缓冲 区数组的数据输出到目标文件中。 记忆技巧 * BufferedOutputStream的使用方式除了构造方法和FileOutputStream不一样之外,其他完全一样。 BufferedInputStream类注意事项 * 字节缓冲输入流读取数据不是直接从目标文件中读取,而是从内部的缓冲区数组中读取, * 如果缓冲区数组没有内容或者读完了,则会通过FileInputStream从目标文件中 一次读取8192个字节数据到缓冲区数组中。 记忆技巧 * BufferedInputStream的使用方式除了构造方法和FileInputStream不一样之外,其他完全一样。
操作步骤:
* A:创建对象 BufferedOutputStream bos = new BufferedOutputStream( new FileOutputStream("a.txt")); BufferedInputStream bis = new BufferedInputStream(new FileInputStream("a.txt")); * B:读写操作 //高效字节流一次读写一个字节: int by = 0; while ((by = bis.read()) != -1) { bos.write(by); } // 高效字节流一次读写一个字节数组: byte[] bys = new byte[1024]; int len = 0; while ((len = bis.read(bys)) != -1) { bos.write(bys, 0, len); } * C:释放资源 bos.close(); bis.close();
四种文件复制方式的效率比较
public static void main(String[] args) throws IOException{ System.out.println("字节流复制文件一次读写一个字节耗时:"+ copy01()); System.out.println("字节流复制文件一次读写一个字节数组耗时:"+ copy02()); System.out.println("高效流复制文件一次读写一个字节耗时:"+ copy03()); System.out.println("高效流复制文件一次读取一个字节数组耗时:"+ copy04()); } // 字节流复制文件一次读写一个字节 public static long copy01() throws IOException{ // 获得当前时间毫秒值 long startTime = System.currentTimeMillis(); // 创建字节输入流对象并关联文件 FileInputStream fis = new FileInputStream("aaa.jpg"); // 创建字节输出流对象并关联文件 FileOutputStream fos = new FileOutputStream("ttt.jpg"); // 定义变量接收读取的字节数 int len = -1; // 循环读取图片数据 while((len = fis.read()) != -1) { // 每读取一个字节的数据就写出到目标文件中 fos.write(len); } // 关闭流 fis.close(); fos.close(); return System.currentTimeMillis() - startTime; } // 字节流复制文件一次读写一个字节数组 public static long copy02() throws IOException{ // 获得当前时间毫秒值 long startTime = System.currentTimeMillis(); // 创建字节输入流对象并关联文件 FileInputStream fis = new FileInputStream("aaa.jpg"); // 创建字节输出流对象并关联文件 FileOutputStream fos = new FileOutputStream("eee.jpg"); // 定义数据接收读取的字节数 byte[] buffer = new byte[1024]; // 定义变量接收读取的字节数 int len = -1; // 循环读取图片数据 while((len = fis.read(buffer)) != -1) { // 将字节数组中的数据写出到目标文件中 fos.write(buffer,0,len); } // 关闭流 fis.close(); fos.close(); return System.currentTimeMillis() - startTime; } // 高效流复制文件一次读写一个字节 public static long copy03() throws IOException{ // 获得当前时间毫秒值 long startTime = System.currentTimeMillis(); // 创建字节输入流对象并关联文件路径 FileInputStream fis = new FileInputStream("aaa.jpg"); // 利用字节输出流对象创建高效字节输出流对象 BufferedInputStream bis = new BufferedInputStream(fis); // 创建字节输出流对象并指定文件路径。 FileOutputStream fos = new FileOutputStream("jjj.jpg"); // 利用字节输出流创建高效字节输出流对象 BufferedOutputStream bos = new BufferedOutputStream(fos); // 定义变量接收读取的字节数 int len = -1; // 循环读取图片数据 while((len = bis.read()) != -1) { // 每读取一个字节的数据就写出到目标文件中 bos.write(len); } // 关闭流 bis.close(); bos.close(); return System.currentTimeMillis() - startTime; } // 高效流复制文件一次读取一个字节数组 public static long copy04() throws IOException{ // 获得当前时间毫秒值 long startTime = System.currentTimeMillis(); // 创建字节输入流对象并关联文件路径 FileInputStream fis = new FileInputStream("aaa.jpg"); // 利用字节输出流对象创建高效字节输出流对象 BufferedInputStream bis = new BufferedInputStream(fis); // 创建字节输出流对象并指定文件路径。 FileOutputStream fos = new FileOutputStream("ddd.jpg"); // 利用字节输出流创建高效字节输出流对象 BufferedOutputStream bos = new BufferedOutputStream(fos); // 定义字节数组接收读取的字节 byte[] buffer = new byte[1024]; // 定义变量接收读取的字节数 int len = -1; // 循环读取图片数据 while((len = bis.read(buffer)) != -1) { // 每读取一个字节的数据就写出到目标文件中 bos.write(buffer,0,len); } // 关闭流 bis.close(); bos.close(); return System.currentTimeMillis() - startTime; }
结论:推荐使用缓冲流来操作文件。
flush
方法和close
方法区别
flush用来刷新缓冲区,将缓冲区中的数据输出到目标文件中,流还可以继续使用。
close用来关闭流释放资源,如果流是带缓冲区,则在关闭流之前会调用flush方法刷新缓冲区,流不可以再次使用。
计算机是如何识别什么时候该把两个字节转换为一个中文呢?
* 在计算机中中文的存储分两个字节: * 第一个字节肯定是负数。 * 第二个字节常见的是负数,可能有正数。但是没影响。 * String(byte[] bytes, String charsetName):通过指定的字符集解码字节数组 * byte[] getBytes(String charsetName):使用指定的字符集合把字符串编码为字节数组 * * 编码:把看得懂的变成看不懂的 * String -- byte[] * * 解码:把看不懂的变成看得懂的 * byte[] -- String
String s = "好好学习"; byte[] bys = s.getBytes(); System.out.println(Arrays.toString(bys)); String ss = new String(bys, "GBK"); System.out.println(ss);
编码读写数据:
* InputStreamReader(InputStream is):用默认的编码读取数据 * InputStreamReader(InputStream is,String charsetName):用指定的编码读取数据 * OutputStreamWriter(OutputStream out):根据默认编码把字节流的数据转换为字符流 * OutputStreamWriter(OutputStream out,String charsetName):根据指定编码把字节流数据转换为字符流 * 把字节流转换为字符流。 * 字符流 = 字节流 +编码表。
操作步骤:
* A:创建对象 InputStreamReader isr = new InputStreamReader(new FileInputStream("a.txt"), "UTF-8"); OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("a.txt"), "GBK"); * B:读写操作 //一次读写一个字符: int by = 0; while ((by = isr.read()) != -1) { osw.write(by); } // 一次读写一个字符数组: byte[] bys = new byte[1024]; int len = 0; while ((len = isr.read(bys)) != -1) { osw.write(bys, 0, len); } * C:释放资源 osw.close(); isr.close();