day19(上)_IO流2(BufferedReaer,BufferedWriter,BufferedInputStream,BufferedOutputStream)
1.IO总结:
IO总结:
/* 2.分析类中常用方法总结: InputStream和OutputStream常用方法: ①int available() 返回文本中的字节数. ②public abstract int read()throws IOException 从输入流中读取数据的下一个字节。 返回 0 到 255 范围内的 int 字节值。 如果因为已经到达流末尾而没有可用的字节,则返回值 -1。 public abstract void write(int b)throws IOException 将指定的字节写入此输出流。 write 的常规协定是:向输出流写入一个字节。 要写入的字节是参数 b 的八个低位。b 的 24 个高位将被忽略。 Q1.read为什么不返回byte,而write传入的参数为int而不是byte?不是读取一个字节/写入一个字节吗? 这是因为文件在计算机中以二进制补码存储, 在未读到文件结尾前读到1111 1111 1111(-1),导致读取字节提前结束. 解决方法: 在read方法中: 为了避免上面那种全为1的情况,对此类型提升为int (32bit) 但是即使提升,发生符号扩展(补1)也就是 byte: 11111111 int:11111111 11111111 11111111 11111111 &255:00000000 00000000 00000000 11111111 还是-1,那么这时候在不改变原byte值的情况下,让其&255改变了 前24bit,而不改变后8bit,此时int值为255 返回数据范围的是: 00000000 00000000 00000000 00000000(0) | 00000000 00000000 00000000 11111111(255) 在write方法中: 写入的是:00000000~11111111(int中byte值) 内部发生类型强制类型转换->截断后8bit ③ public int read(byte[] b)throws IOException 从输入流中读取一定数量的字节,并将其存储在缓冲区数组 b 中。 以整数形式返回实际读取的字节数,如果因为流位于文件末尾而没有可用的字节, 则返回值 -1; public void write(byte[] b)throws IOException 将 b.length个字节从指定的 byte 数组写入此输出流。 ④ 关于刷新: public void flush()throws IOException//OutputStream中flush 刷新此输出流并强制写出所有缓冲的输出字节。 flush 的常规协定是:如果此输出流的实现已经缓冲了以前写入的任何字节, 则调用此方法指示应将这些字节立即写入它们预期的目标。 对于字节写入流来说可以不用flush也会写入文件,因为 如果没有使用具体制定缓冲区,不论什么数据都以字节操作, 对字节这个最小单位操作,直接写入目的地. public void flush()throws IOException//BufferedOutputStream中flush 刷新此缓冲的输出流。这迫使所有缓冲的输出字节被写出到底层输出流中。该方法内部实现细节: protected byte buf[]; protected OutputStream out; private void flushBuffer() throws IOException { if (count > 0) { out.write(buf, 0, count);//buf内部定义的字节缓冲区 //2.将buf中的数据写入到了输出流中 count = 0; } } public synchronized void flush() throws IOException { flushBuffer();//1.首先刷新了缓冲区 out.flush();//3.把流中的数据刷新到文本中 } ⑤ 关闭缓冲区实际上关闭的是使用缓冲区的流对象: //BufferedInputStream.java public void close() throws IOException { byte[] buffer; while ( (buffer = buf) != null) { if (bufUpdater.compareAndSet(this, buffer, null)) { InputStream input = in; in = null; if (input != null) input.close();//关闭的是 使用缓冲区的输入字节流对象 //关闭此输入流并释放与该流关联的所有系统资源。 return; } // Else retry in case a new buf was CASed in fill() } } */
/* Reader和Writer常用方法总结: ①public int read()throws IOException 读取单个字符。 返回: 作为整数读取的字符, 范围在 0 到 65535 之间 (0x0000-0xffff)(2byte范围), 如果已到达流的末尾,则返回 -1 public void write(int c)throws IOException 写入单个字符。要写入的字符包含在给定整数值的 16(2Byte)个低位中, 16 高位被忽略。 ②public int read(char[] cbuf) throws IOException 将字符读入数组。 返回:读取的字符数,如果已达到流的末尾,则返回-1 public void write(char[] cbuf)throws IOException 字符数组中内容写入流中③public void write(String str)throws IOException 写入字符串。 ④FileReader与InputStreamReader(转换流) 当操作文件,用字节读取流关联文件: public FileReader(String fileName)throws FileNotFoundException //内部实现细节: public FileReader(String fileName) throws FileNotFoundException { super(new FileInputStream(fileName));//内部创建了字节读取流对象 } //传给了转换流 public InputStreamReader(InputStream in) { super(in); try { sd = StreamDecoder.forInputStreamReader(in, this, (String)null); // ## check lock object } catch (UnsupportedEncodingException e) { // The default encoding should always be available throw new Error(e); } } 同理FileWriter和OutputStreamWriter //内部实现细节: public FileWriter(String fileName) throws IOException { super(new FileOutputStream(fileName)); } public OutputStreamWriter(OutputStream out) { super(out); try { se = StreamEncoder.forOutputStreamWriter(out, this, (String)null); } catch (UnsupportedEncodingException e) { throw new Error(e); } } ⑤flush: public abstract void flush()throws IOException 刷新该流的缓冲。如果该流已保存缓冲区中各种 write() 方法的所有字符, 则立即将它们写入预期目标。 字符流必须刷新/关闭(关闭之前会刷新),把流中的数据刷到文本中. */ /* BufferedReader,BufferedWriter,LineNumberReader 从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。 缓冲区内部使用数组来实现. BufferedReader: public String readLine()throws IOException 读取一个文本行。 通过下列字符之一即可认为某行已终止: 换行 ('\n')、回车 ('\r') 或回车后直接跟着换行。 返回: 包含该行内容的字符串,不包含任何行终止符,如果已到达流末尾,则返回 null掌握readLine方法的原理 BufferedWriter: public void newLine()throws IOException 写入一个行分隔符。行分隔符字符串由系统属性 line.separator 定义, 并且不一定是单个新行 ('\n') 符。 注意:在不同平台下,行分隔符可能不同,例如:windows:"\r\n" linux:"\n"
该方法是一个跨平台方法 LineNumberReader: 默认情况下,行编号从 0 开始。 该行号随数据读取在每个行结束符处递增, 并且可以通过调用 setLineNumber(int) 更改行号。 如果行编号从0开始,文本第一行为1,第二行为2...... close方法:底层依然关闭的是使用该缓冲的流对象,并且释放用到的系统底层资源 */
2.字节流的读取和写入方法:
package filestream; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; class FileStreamDemo{ //方法一:一个字节一个字节读 public static void read_1(){ FileInputStream fis=null; try{ //fis=new FileInputStream("fos.txt"); fis=new FileInputStream("TestUnicode.txt"); int ch=0; System.out.println("read_1:"); while((ch=fis.read())!=-1) System.out.println(ch); } catch(IOException e){ e.printStackTrace(); } finally{ try{ if(fis!=null) fis.close(); } catch(IOException e){ e.printStackTrace(); } } } /* 方法二: public int read(byte[] b)throws IOException */ public static void read_2(){ FileInputStream fis=null; try{ fis=new FileInputStream("fos.txt"); int bytes=0; byte[] b=new byte[1024]; System.out.print("read_2:"); while((bytes=fis.read(b))!=-1) System.out.println(bytes+"..."+new String(b,0,bytes));//只输出读取到的字节数 } catch(IOException e){ e.printStackTrace(); } finally{ try{ if(fis!=null) fis.close(); } catch(IOException e){ e.printStackTrace(); } } } /* public int available()throws IOException 该方法返回了文本中字节数. 例如:abcd,返回4 注意一点: abcd e abcd后有回车,返回7->\r\n各占1byte 这种方法有个缺陷: 如果返回值过大(读取1G(1024*1024)文件) 那么此时new的数组过大,可能发生内存溢出在使用时注意 */ public static void read_3(){ FileInputStream fis=null; try{ fis=new FileInputStream("fos.txt"); int bytes=0; byte[] b=new byte[fis.available()];//定义一个刚刚好的缓冲区 fis.read(b);//一次性把数据读到b中 System.out.println("read_3:"+new String(b)); } catch(IOException e){ e.printStackTrace(); } finally{ try{ if(fis!=null) fis.close(); } catch(IOException e){ e.printStackTrace(); } } }//写入方法: public static void write(){ FileOutputStream fos=null; FileInputStream fis=null; try{ fis=new FileInputStream("1.txt"); fos=new FileOutputStream("fos.txt"); int aByte=0; while((aByte=fis.read())!=-1) fos.write(aByte); //fos.write("abcd".getBytes());//将字符串转成字节数组写入 /* 这里即使不刷新/关闭,abcd依然会写入到fos.txt中, 字符流:例如读汉字(2byte),先读一个字节临时存储(底层用的字节流的缓冲区),再读一个->查表->汉字 字节流:如果没有使用具体制定缓冲区,不论什么数据都以字节操作, 对字节这个最小单位操作,直接写入目的地. */ } catch(IOException e){ e.printStackTrace(); } finally{ try{ if(fos!=null) fos.close();//依然需要关闭此输出流并释放与此流有关的所有系统资源} catch(IOException e){ e.printStackTrace(); } } } public static void main(String[] args){ write(); read_1(); read_2(); read_3(); } }
3.多媒体文件(mp3,电影...)拷贝
/* FileInputStream 用于读取诸如图像数据之类的原始字节流 FileOutputStream 用于写入诸如图像数据之类的原始字节的流。 复制图片: 思想: 1.创建字节读取流对象关联一个图片文件 2.创建字节写入流对象创建一个图片文件,用于存储获取到的图片数据 3.通过循环读写,完成数据存储 4.关资源 */ package copypic; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; class CopyPicture{ public static void main(String[] args){ FileInputStream fis=null; FileOutputStream fos=null; try{ fis=new FileInputStream("c:\\Screen.png"); fos=new FileOutputStream(".\\ScreenCopy.png"); byte[] bbuf=new byte[1024]; int bytes; while((bytes=fis.read(bbuf))!=-1) fos.write(bbuf,0,bytes); } catch(IOException e){ e.printStackTrace(); } finally{ try{ if(fis!=null) fis.close(); } catch(IOException e){ e.printStackTrace(); } try{ if(fos!=null) fos.close(); } catch(IOException e){ e.printStackTrace(); } } } } /* 不要用字符流拷贝媒体文件 读取数据和写入数据可能发生不一致 导致写入的编码和原编码不一致,拷贝失败. */
4.字符流和字节流的缓冲区
①BufferedWriter与BufferedReader
/*BufferedWriter为什么没有空构造函数?这是因为缓冲区的出现是为了提高流的操作效率而出现的 所以在创建缓冲区之前,必须要先有流对象 现实生活中例子: 用蓄水池(缓冲区)储水(流对象),如果还没有水呢,要蓄水池干啥用呢
?先有水
->再有蓄水池 水和水杯也是. 比喻: 水一滴一滴的滴(FileReader的read()) 我想要喝水滴一滴喝一滴(write())很不爽 那么我用个杯子(缓冲区)接的差不多() 一饮而尽很爽~ */package bufferedwriter; import java.io.FileWriter; import java.io.BufferedWriter; import java.io.IOException; class BufferedWriterDemo{ public static void main(String[] args){ FileWriter fw=null; BufferedWriter bufw=null; try{ //创建一个字符写入流对象 fw=new FileWriter("buff.txt"); //为了提高字符写入流效率,加入缓冲技术 //其实缓冲技术原理在缓冲里面封装了数组 bufw=new BufferedWriter(fw);//public BufferedWriter(Writer out) //创建一个使用默认大小 输出缓冲区(8KB)的 缓冲字符输出流。 for(int i=0;i<4;++i){ bufw.write("coding"+i);//写入缓冲区 bufw.newLine(); bufw.flush();//写一次刷一次.假如停电,未刷新此时数据可能还在流(内存)中,还没有写到硬盘中. } } catch(IOException e){ e.printStackTrace(); } finally{ try{ //其实关闭缓冲区,就是在关闭缓冲区中的流对象 //真正调用底层资源,进行读写的是流对象 bufw.close();//在关闭之前也会刷新缓冲 /* 源码: public void close() throws IOException { synchronized (lock) { if (out == null) { return; } try { flushBuffer();//刷新缓冲 } finally { out.close();//关闭使用缓冲的流对象 out = null; cb = null; } } } */ } catch(IOException e){ e.printStackTrace(); } } } } /* 关于newLine方法 在windows里面换行为"\r\n"; 而linux里面为:"\n" 为了实现跨平台性,保证我们把同一个程序拿到不同平台下都能换行. BufferedWriter中提供了一个newLine方法. 相当于在windows版的JDK源文件中: 封装了write("\r\n"); linux版的 封装了write("\n"); *//*
关于缓冲区理解:
如果是边读边写,就会很慢,也伤硬盘。
缓冲区(字节数组/字符数组)就是内存里的一块临时存储数据的区域,把数据先存到缓冲区中,然后写入硬盘.
*//* BufferedReader(缓冲字符输入流): 从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和 行 的高效读取。 */ package bufferedreader; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; class BufferedReaderDemo{ public static void main(String[] args){ //创建一个字符读取流对象和文件相关联 BufferedReader bufr=null; try{ //依然为了提高效率,加入缓冲技术, //将字符读取流对象作为参数传递给缓冲区对象的构造函数 bufr=new BufferedReader(new FileReader("buff.txt")); String readStr; while((readStr=bufr.readLine())!=null)//从缓冲区中一行一行读,直到文件流末尾 System.out.println(readStr);//打印字符串。如果参数为null
,则打印字符串"null"
。否则,//按照平台的默认字符编码将字符串的字符转换为字节,并完全以write(int)方法的方式写入这些字节。 } catch(IOException e){ e.printStackTrace(); } finally{ try{ if(bufr!=null) bufr.close(); } catch(IOException e){ e.printStackTrace(); } } } }利用字符流缓冲区复制文件:
/* 通过缓冲区复制一个.java文件 其实就是BufferedReader与BufferedWriter综合运用 */ package copy; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; class CopyTest{ public static void readWrite(BufferedReader bufr,BufferedWriter bufw){ try{ String line=null; while((line=bufr.readLine())!=null){ bufw.write(line); bufw.newLine(); bufw.flush(); } } catch(IOException e){ e.printStackTrace(); } } public static void copy(String file,String destPath){ BufferedReader bufr=null; BufferedWriter bufw=null; try{ bufr=new BufferedReader(new FileReader(file)); if(file.indexOf('\\')==-1){//当前目录下的文件,也就是说可以不用写 .\\文件 bufw=new BufferedWriter(new FileWriter(destPath+file)); readWrite(bufr,bufw); } else{//需要获取路径中的文件名 String[] fileName = file.split("\\\\"); bufw=new BufferedWriter(new FileWriter(destPath+fileName[fileName.length-1])); readWrite(bufr,bufw); } } catch(IOException e){ e.printStackTrace(); } finally{ try{ if(bufr!=null) bufr.close(); } catch(IOException e){ throw new RuntimeException("读取关闭失败"); } try{ if(bufw!=null) bufw.close(); } catch(IOException e){ throw new RuntimeException("写入关闭失败"); } } } public static void main(String[] args){ copy("3_CopyTest.java","d:\\"); } } /* readLine原理: 画个示意图. 拷贝过程原理图 */readLine原理图:
以上拷贝过程示意图:
②BufferedInputStream与BufferedOutputStream
/* 字节流两个缓冲区: BufferedInputStream BufferedInputStream为另一个输入流添加一些功能, 即缓冲输入以及支持 mark 和 reset 方法的能力。 在创建BufferedInputStream 时,会创建一个内部缓冲区数组。 BufferedOutputStream 该类实现缓冲的输出流。 通过设置这种输出流,应用程序就可以将各个字节写入底层输出流中, 而不必针对每次字节写入调用底层系统。 */ package copymp3; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; class ByteBuffered{ /* 方法一:使用int read() 从此输入流中读取下一个数据字节。 返回一个 0 到 255 范围内的 int 字节值 void write(int b) 将指定的字节写入此缓冲的输出流。 */ public static void copy_1()throws IOException{ BufferedInputStream bis=new BufferedInputStream(new FileInputStream("E:\\SONG\\Bandari - 爱尔兰风笛.mp3")); BufferedOutputStream bos=new BufferedOutputStream(new FileOutputStream(".\\copy.mp3")); int aByte; while((aByte=bis.read())!=-1) bos.write(aByte); bis.close(); bos.close(); } public static void main(String[] args)throws IOException{ long start,end; start=System.currentTimeMillis(); copy_1(); end=System.currentTimeMillis(); System.out.println((end-start)+"ms");//计算下拷贝花费时间 /* 这里可以回顾下设计模式:模板方法模式(继承(下)中已阐述) */ } }