一、IO流的概述
1、常识
IO流用来处理设备之间的数据传输。
Java对数据的操作是通过流的方式。
Java用于操作流的对象都在IO包中(java.io)。 在编写IO相关代码是,需先导入java.io.*;或者其中相关的类。
2、分类
流按流向分为:输入流,输出流。
流按操作数据分为两种:字节流字符流。
字节流的抽象基类: InputStream ,OutputStream。
字节流可以都所有类型文件进行操作,但是不会自动对字节和字符进行转换。字符流专门对文档文件读写,自动进行字符和字节间的转换,但不支持非文档文件(图片、视频等)。
3、使用注意
需要先导入IO包中的类
需要对IO异常进行处理
需要在异常处理的finally中对流进行关闭(close()方法)
注:由这四个类派生出来的子类名称都是以其父类名作为子类名的后缀。
• 如:InputStream的子类FileInputStream。• 如:Reader的子类FileReader。
IO流是用于操作数据的,而数据的最常见体现形式是:文件。可先以操作文件为主来学习字符流。
说明:FileWriter是专门文档进行写的类,Writer的子类。 该类的后缀名是父类名,前缀名是该流对象的功能。
用法:
创建流对象,建立数据存放文件:
//在指定目录下创建文件,如果该目录下已有同名文件,将被覆盖。
FileWriter fw = new FileWriter("目标文件路径");
//传递一个true参数,代表不覆盖已有的文件。并在已有文件的末尾处进行数据续写。
FileWriter fw = new FileWriter(目标文件路径",true);
调用流对象的写入方法,将数据写入流
fw.write(“text”); 可接收char[ ] , String , int 类型的数据
将流的缓冲刷新文件中。(刷新前数据会暂时保存在缓冲区中,刷新后才会保存到文件中)
fw.flush();
关闭流资源,并将流中的数据刷新到文件中
fw.close();
例1:在硬盘上,创建一个文件并写入一些文字数据。(暂时未做异常处理)
import java.io.*; class FileWriterDemo { public static void main(String[] args) throws IOException { //创建一个FileWriter对象。而且该文件会被创建到指定目录下。如果该目录下已有同名文件,将被覆盖。 //其实该步就是在明确数据要存放的目的地。 FileWriter fw = new FileWriter("demo.txt"); //调用write方法,将字符串写入到流中。 fw.write("abcde"); //关闭流资源,但是关闭之前会刷新一次内部的缓冲中的数据。 //和flush区别:flush刷新后,流可以继续使用,close刷新后,会将流关闭。 fw.close(); } }
对例1做异常处理:
import java.io.*; class FileWriterDemo2 { public static void main(String[] args) { //需定义在try外部,否则finally中无法调用 FileWriter fw = null; try { fw = new FileWriter("demo.txt"); fw.write("abcdefg"); } catch (IOException e) { System.out.println("catch:"+e.toString()); } //必须在finally中关闭流,释放资源。 finally { try { //需先判断,否则如果fw没有new 成功,关闭会出异常。 if(fw!=null) fw.close(); } catch (IOException e) { System.out.println(e.toString()); } } } }
说明:FileReader是专门文档进行读的类,Reader的子类。
用法:
建立一个流对象,将已存在的一个文件加载进 流。
FileReader fr = new FileReader(“Test.txt”);
创建一个临时存放数据的数组。 长度一般为1024的倍数
char[] ch = new char[1024];
调用流对象的读取方法将流中的数据读入到数组中。
fr.read(ch); 每次读取都会返回读取成功的字符个数。读到文件末尾返回-1 —— 优先考虑此方法,高效且便捷。
read(); 一次读一个字符,而且会自动往下读。读到文件末尾返回-1.
例2:打印这个.java文件。
//打印这个.java文件。 import java.io.*; class FileReaderText { public static void main(String[] args) { FileReader fr = null; int num = 0; try { fr = new FileReader("FileReaderText.java"); //定义数组来装读进来的字符,长度一般为1024的倍数 char[] ch = new char[1024]; while((num = fr.read(ch)) != -1) { /*应用了String的构造函数: String(char[] value, int offset, int count) 分配一个新的 String,它包含取自字符数组参数一个子数组的字符。 */ System.out.print(new String(ch,0,num)); sum++; } } catch (IOException e) { System.out.print(e.toString()); } finally { try { if(fr!=null) fr.close(); } catch (IOException e) { System.out.println(e.toString()); } } } }
3、BufferedReader & BufferedWriter
说明:BufferedReader 和BufferedWriter类是是为字符流(Writer、 Reader)提供缓冲区的装饰类,用于提高字符流的操作效率。
用法:
在创建BufferedReader 和BufferedWriter对象时,需在构造方法中传入相应的字符流对象。如:
BufferedReader bufr = new BufferedReader(new FileReader("目标文件路径"));
BufferedWriter bufw = new BufferedWriter(new FileWriter("目标文件路径"));
BufferedReader 和BufferedWriter也提供了read、write、close等方法,作用、使用方式与FileReader、FileWriter中的相似。
bufr.read(); bufr.close;
bufw.wirte(); bufw.close;
注:缓冲区中的close()方法关闭的其实就是流,与其相关联流中的close()一样。缓冲区对象调用close()方法关闭流后,流不需要再调用。
特有方法:
bufr.readLine(); 一次读取文件的一行,若读到文件尾部返回null。(每次读取都是换行符之前的数据,不包括换行符)
bufw.newLine(); 在流当前的位置下添加换行符,此方法会根据不同的系统添加不同的换行符( \r\n 或者 \n )
例3:通过缓冲区复制一个.java文件。
/* 通过缓冲区复制一个.java文件。 */ import java.io.*; class CopyTextByBuf { public static void main(String[] args) { //在try外部定义读、写缓冲区对象 BufferedReader bufr = null; BufferedWriter bufw = null; try { //为读、写缓冲区构造对象 bufr = new BufferedReader(new FileReader("CopyTextByBuf.java")); bufw = new BufferedWriter(new FileWriter("CopyTextByBuf_New.txt")); String line = null; //复制方式是通过一行一行的读取目标文档文件,然后写进新的路径下。读一行,写一行。 while((line=bufr.readLine())!=null) { bufw.write(line); bufw.newLine();//因为bufr.readLine()返回的数据不包括换行,所以需要手动添加。 bufw.flush();//每次都刷新进文件,避免突然停止而前功尽弃。 } } catch (IOException e) { throw new RuntimeException("读写失败"); } finally { //两个缓冲区都必须调用close()方法,关闭与其相关联的流。 //需分开try,否则很可能前一个关闭失败,后一个还没有关闭就跳过了。 try { if(bufr!=null) bufr.close(); } catch (IOException e) { throw new RuntimeException("读取关闭失败"); } try { if(bufw!=null) bufw.close(); } catch (IOException e) { throw new RuntimeException("写入关闭失败"); } } } }
LineNumberReader是BufferedReader的子类,通过特有方法setLineNumber()、getLineNumber() 可以实现带行号输出文件等功能。使用方式与BufferedReader类似。
例4:带行号打印"LineNumberReaderDemo.java"文件。(为演示方便,暂不做异常处理。现实使用不可忽略)
import java.io.*; class LineNumberReaderDemo { public static void main(String[] args)throws IOException { FileReader fr = new FileReader("LineNumberReaderDemo.java"); LineNumberReader lnr = new LineNumberReader(fr); String line = null; //设置行号从101开始计算,如果不设置则默认从1开始计算 lnr.setLineNumber(100); while((line=lnr.readLine())!=null) { System.out.println(lnr.getLineNumber()+":"+line); } lnr.close(); } }
三、字节流(InputStream OutputStream)
FileInputStream 、 FileOutputStream 分别与FileReader 、 FileWriter 的使用方式和功能相似。但FileInputStream 、 FileOutputStream是针对字节来读写文件的,而这两个类可以操作所有类型的文件。
具体区别:
1、FileOutputStream 与FileWriter 的使用方式和功能相似。但FileOutputStream 中的write()方法只对byte类型的数据 或者 数组进行操作,无法操作char、String等类型。同时,write()方法会直接将数据写入文件中,不需要再调用flush()方法。
2、FileInputStream 与FileReader的使用方式和功能相似。但FileInputStream 中的read()方法只是返回一个字节的数据,或存入byte数组中。且FileInputStream 有一方法available(),该方法可获取文件的长度。(可用于定义一个“刚刚好”的缓冲区数组,但是当文件过大时容易溢出,慎用!推荐使用定义1024整数倍长度的数组)
例5:通过字节流复制非文档文件
/* 复制一个图片 思路: 1,用字节读取流对象和图片关联。 2,用字节写入流对象创建一个图片文件。用于存储获取到的图片数据。 3,通过循环读写,完成数据的存储。 4,关闭资源。 */ import java.io.*; class CopyByBStream { public static void main(String[] args) { //在try外部定义FileOutputStream FileInputStream类的对象,方便在finally中使用 FileOutputStream fos = null; FileInputStream fis = null; try { fos = new FileOutputStream("c:\\2.bmp"); fis = new FileInputStream("c:\\1.bmp"); //定义长度为1024的byte数组 byte[] buf = new byte[1024]; int len = 0; //不断的将文件中数据存入数组中,在写入文件中 while((len=fis.read(buf))!=-1) { //将数组buf中下标为0-len之间(包头不包尾)的数据写入文件中,保证写入的为有效数据。 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("写入关闭失败"); } } } }
2、BufferedInputStream & BufferedOutputStream
BufferedOutputStream 与 BufferedInputStream是OutputStream 与 InputStream的装饰类。关系和用法相似于 BufferedReader 与 Reader 、BufferedWriter 与 Writer的关系。
例6:通过字符流缓冲区复制文件。
/* 演示mp3的复制,并统计消耗的时间。通过缓冲区。 BufferedOutputStream BufferedInputStream */ import java.io.*; class Copy { public static void main(String[] args) { //统计复制所用的时间 long start = System.currentTimeMillis(); copy(); long end = System.currentTimeMillis(); System.out.println((end-start)+"毫秒"); } //通过字节流的缓冲区完成复制。 public static void copy() { BufferedInputStream bufis = null; BufferedOutputStream bufos = null; try { bufis = new BufferedInputStream(new FileInputStream("c:\\0.mp3")); bufos = new BufferedOutputStream(new FileOutputStream("c:\\1.mp3")); int by = 0; //复制过程 while((by=bufis.read())!=-1) { bufos.write(by); } } catch (IOException e) { throw new RuntimeException("读写文件失败"); } finally { try { if(bufos!=null) bufos.close(); } catch (IOException e) { throw new RuntimeException("关闭output失败"); } try { if(bufis!=null) bufis.close(); } catch (IOException e) { throw new RuntimeException("关闭input失败"); } } } }
四、扩展—装饰类
简单介绍:
1、以前是通过继承将每一个子类都具备缓冲功能。那么继承体系会复杂,并不利于扩展。
2、现在优化思想。单独描述一下缓冲内容。将需要被缓冲的对象。传递进来。也就是,谁需要被缓冲,谁就作为参数传递给缓冲区。这样继承体系就变得很简单。优化了体系结构。
3、装饰模式比继承要灵活。避免了继承体系臃肿。而且降低了类于类之间的关系。
4、装饰类因为增强已有对象,具备的功能和已有的是相同的,只不过提供了更强功能。所以装饰类和被装饰类通常是都属于一个体系中的。