字符流
FileReader、FileWriter
BufferedReader、BufferedWriter
字节流
InputStream 此抽象类是表示字节输入流的所有类的超类。 需要定义 InputStream 子类的应用程序必须总是提供返回下一个输入字节的方法OutputStream 此抽象类是表示输出字节流的所有类的超类。输出流接受输出字节并将这些字节发送到某个接收器。
常用子类FileInputStream、FileOutputStream
字节流和字符流最大的区别是,字节流可以操作非文本数据,字符流操作非文本数据时会导致文件损坏
字节流文本操作示例:
import java.io.*; class FileStream{ public static void main(String[] args) throws IOException { //writeFile(); readFile_3(); } public static void readFile_3() throws IOException//3,使用available方法统计个数 { FileInputStream fis = new FileInputStream("fos.txt"); //int num = fis.available(); //返回有效的字符个数,windows回车符含两个字符 byte[] buf = new byte[fis.available()];//直接传入有效的字符个数,大小刚好,不用再循环 fis.read(buf); System.out.println(new String(buf)); fis.close(); } public static void readFile_2() throws IOException //2,使用数组提高读取效率 { FileInputStream fis = new FileInputStream("fos.txt"); byte[] buf = new byte[1024]; int len = 0; while ((len=fis.read(buf)) !=-1){ //1,读取fos.txt中内容,一次读一个数组 System.out.println(new String(buf,0,len)); } fis.close(); } public static void readFile_1() throws IOException//字节流读取 { FileInputStream fis = new FileInputStream("fos.txt"); int ch = 0; while ((ch=fis.read()) !=-1){ //读取fos.txt中内容,一次读一个字符(两个字节) System.out.println((char)ch); } fis.close(); } public static void writeFile() throws IOException //字节流写入 { FileOutputStream fos = new FileOutputStream("fos.txt");//创建fos.txt fos.write("hahahah".getBytes());//该类的write方法接收byte型数组,所以要转型 fos.close(); //不需要刷新,但是要关闭资源 } }
【小结】
1,字符流在底层调用了字节流的缓冲区,所以需要刷新动作;而字节流在操作时则不需要刷新
2,available()方法要慎用,虚拟机内存默认为64M,如果只用一个大小刚好的缓冲数组,在copy较大的文件时显然是无法承受的,所以还是以第二种方法为主
【字节流copy图片】
思路:
1,用字节读取流读取图片
2,用字节写入流对象创建图片文件,用于存储获取到的图片数据
3,通过循环读写,完成数据存储
4,关闭资源
import java.io.*; class CopyPicture { public static void main(String[] args) { FileOutputStream fos = null; FileInputStream fis = null; try { fos = new FileOutputStream("G:\2.png");//写入 fis = new FileInputStream("G:\1.png"); //读取 byte[] buf = new byte[1024]; int len = 0; while ((len=fis.read(buf)) !=-1) { fos.write(buf,0,len); } } catch (IOException e){ throw new RuntimeException("复制失败"); } finally{ try{ if (fis!=null) fis.close(); } catch (IOException e){ throw new RuntimeException("读取关闭失败"); } try{ if (fos!=null) fos.close(); } catch (IOException e){ throw new RuntimeException("写入关闭失败"); } } } }
【字节流缓冲区复制Mp3】
BufferedInputStream、BufferedOutputStreamimport java.io.*; class CopyMp3 { public static void main(String[] args) throws IOException { long start = System.currentTimeMillis(); //模板设计模式 copy_1(); long end = System.currentTimeMillis(); System.out.println((end-start)+"毫秒"); //打印运行时间 } public static void copy_1() throws IOException { //Buffer从硬盘进缓冲区 File读硬盘 BufferedInputStream bis = new BufferedInputStream(new FileInputStream("G:\圣诞结-Eason.flac"));//将源文件导入读取流 BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("G:\圣诞结-陈奕迅.flac"));//目的地 int by = 0; while ((by=bis.read()) !=-1){ bos.write(by); } bis.close(); bos.close(); } }
【自定义字节流缓冲区】
import java.io.*; class MyBufferedIOStream { private InputStream in;//装饰模式,私有化成员变量 private byte[] buf = new byte[1024]; //1,定义数组 private int pos = 0, count = 0; //2,定义指针pos、3,定义计数器count表示数组中剩余元素个数 MyBufferedInputStream(InputStream in){ //装饰类构造函数 this.in = in; } public int myRead() throws IOException //自定义字节流缓冲区read方法,一次读取一个字节, { if (count == 0) //计数器为0,则读取一个数组,开始计数 { //通过in对象读取硬盘上数据,存储到buf数组中 count = in.read(buf);//read返回该数组中有效元素个数,到末尾返回-1 if (count<0) //健壮性判断、read到达文件末尾返回为-1 return -1; pos = 0; //执行到这一步说明有一个新的数组,初始化指针 byte b = buf[pos];//通过指针获取字节数组元素 count--; //执行一次,元素有效个数-1 pos++; //指针位置右移 return b&255; //该数组字节为byte,通过&与运算将其提升为int型并补上24个0返回 } else if (count>0) //计数器大于0,对数组继续读取就可以了 { byte b = buf[pos]; count--; pos++; return b&0xff; //16进制的255 } } public static void main(String[] args) { System.out.println("Hello World!"); } }
缓冲区中read和write的特点
在计算机中数据都是以二进制存取的,而字节是以byte型存取的如果刚好有连续的8个1,直接转成int型就还是-1,而在缓冲区中的read时,就意外满足了-1的控制条件
从而导致读写操作无法进行,所以字节流缓冲区的read方法必须避免这种情况的发生生
byte: -1 ---> int : -1; 00000000 00000000 00000000 11111111 255 11111111 11111111 11111111 11111111 11111111 -->提升了一个int类型 那不还是-1吗?是-1的原因是因为在8个1前面补的是1导致的。 那么我只要在前面补0,即可以保留原字节数据不变,又可以避免-1的出现。 怎么补0呢? 通过&运算补0 11111111 11111111 11111111 11111111 &00000000 00000000 00000000 11111111 ------------------------------------ 00000000 00000000 00000000 11111111结论:
字节流的读一个字节的read方法为什么返回值类型不是byte,而是int。
因为有可能会读到连续8个二进制1的情况,8个二进制1对应的十进制是-1;那么就会数据还没有读完,就结束的情况。因为我们判断读取结束是通过结尾标记-1来确定的。
所以,为了避免这种情况将读到的字节进行int类型的提升。并在保留原字节数据的情况前面了补了24个0,变成了int类型的数值。
而在write写入数据时,只写该int类型数据的最低8位。