一、java.io 的描述
通过数据流、序列化和文件系统提供系统输入和输出。IO流用来处理设备之间的数据传输
二、流
流是一个很形象的概念,当程序需要读取数据的时候,就会开启一个通向数据源的流,这个数据源可以是文件,内存,或是网络连接。类似的,当程序需要写入数据的时候,就会开启一个通向目的地的流。这时候你就可以想象数据好像在这其中“流”动一样。Java对数据的操作是通过流的方式实现的。Java用户操作流的对象都在IO包中。
三、Java流输入输出原理
Java把这些不同来源和目标的数据都统一抽象为数据流。Java语言的输入输出功能是十分强大而灵活的,美中不足的是看上去输入输出的代码并不是很简洁,因为你往往需要包装许多不同的对象。
在Java类库中,IO部分的内容是很庞大的,因为它涉及的领域很广泛:标准输入输出,文件的操作,网络上的数据流,字符串流,对象流,zip文件流。
1.Java流的分类
按流向分:输入流(InputStream,Reader): 程序可以从中读取数据的流;输出流(OutputStream,Writer): 程序能向其中写入数据的流。
按数据传输单位分:字节流(InputStream,OutputStream): 以字节为单位传输数据的流;字符流(Reader,Writer): 以字符为单位传输数据的流
按功能分:节点流: 用于直接操作目标设备的流;过滤流: 是对一个已存在的流的链接和封装,通过对数据进行处理为程序提供功能强大、灵活的读写功能。
字符流和字节流
字符流的由来: 因为数据编码的不同,有了对字符进行高效操作的流对象。本质其实就是基于字节流读取时,去查了指定的编码码表。 字节流和字符流的区别:
读写单位不同:字节流以字节(Byte-8bit)为单位,字符流以字符(Char)为单位,根据码表映射字符,一次可能读多个字节。
处理对象不同:字节流能处理所有类型的数据(如图片、avi等),而字符流只能处理字符(文本)类型的数据。
结论:只要是处理纯文本数据,就优先考虑使用字符流(Reader,Writer)。 除此之外都使用字节流(OutputStream,InputStream)。
流操作基本规律:可以通过三个明确来完成:
1.明确源和目的
源:输入流(InputStream,Reader);目的:输出流(OutputStream,Writer)。
2.操作的数据是否纯文本?是:字符流(Reader,Writer);不是:字节流(InputStram,OutputStream)。
3.当体系明确后,再明确使用哪个具体的流对象
通过设备来进行区分,源设备:内存,硬盘,键盘;目的设备:内存,硬盘,控制台
输入流和输出流
对输入流只能进行读操作,对输出流只能进行写操作,程序中需要根据待传输数据的不同特性而使用不同的流。
Java IO流对象
(1).输入字节流InputStreamIO 中输入字节流的继承关系
InputStream 是所有的输入字节流的父类,它是一个抽象类。
ByteArrayInputStream、StringBufferInputStream、FileInputStream 是三种基本的介质流,它们分别从Byte 数组、StringBuffer、和本地文件中读取数据。PipedInputStream 是从与其它线程共用的管道中读取数据。
ObjectInputStream 和所有FilterInputStream 的子类都是装饰流(装饰器模式的主角)。
(2).输出字节流OutputStream
OutputStream 是所有的输出字节流的父类,它是一个抽象类。
ByteArrayOutputStream、FileOutputStream 是两种基本的介质流,它们分别向Byte 数组、和本地文件中写入数据。PipedOutputStream 是向与其它线程共用的管道中写入数据,
ObjectOutputStream 和所有FilterOutputStream 的子类都是装饰流。
(3).字节流的输入与输出的对应
图中蓝色的为主要的对应部分,红色的部分就是不对应部分。紫色的虚线部分代表这些流一般要搭配使用。从上面的图中可以看出Java IO 中的字节流是极其对称的。“存在及合理”我们看看这些字节流中不太对称的几个类吧!
LineNumberInputStream 主要完成从流中读取数据时,会得到相应的行号,至于什么时候分行、在哪里分行是由改类主动确定的,并不是在原始中有这样一个行号。在输出部分没有对应的部分,我们完全可以自己建立一个LineNumberOutputStream,在最初写入时会有一个基准的行号,以后每次遇到换行时会在下一行添加一个行号,看起来也是可以的。好像更不入流了。
PushbackInputStream 的功能是查看最后一个字节,不满意就放入缓冲区。主要用在编译器的语法、词法分析部分。输出部分的BufferedOutputStream 几乎实现相近的功能。
StringBufferInputStream 已经被Deprecated,本身就不应该出现在InputStream 部分,主要因为String 应该属于字符流的范围。已经被废弃了,当然输出部分也没有必要需要它了!还允许它存在只是为了保持版本的向下兼容而已。
SequenceInputStream 可以认为是一个工具类,将两个或者多个输入流当成一个输入流依次读取。完全可以从IO 包中去除,还完全不影响IO 包的结构,却让其更“纯洁”――纯洁的Decorator 模式。
PrintStream 也可以认为是一个辅助工具。主要可以向其他输出流,或者FileInputStream 写入数据,本身内部实现还是带缓冲的。本质上是对其它流的综合运用的一个工具而已。一样可以踢出IO 包!System.out 和System.out 就是PrintStream 的实例!
(4).字符输入流Reader
Reader 是所有的输入字符流的父类,它是一个抽象类。Reader 里的构造方法是私有的,只能由子类访问,在读取流的时候要刷新流。
FileReader、CharReader、StringReader 是三种基本的介质流,它们分别将从文本文件,Char 数组、String中读取数据。PipedReader 是从与其它线程共用的管道中读取数据。
BufferedReader 很明显就是一个装饰器,它和其子类负责装饰其它Reader 对象。
FilterReader 是所有自定义具体装饰流的父类,其子类PushbackReader 对Reader 对象进行装饰,会增加一个行号。
InputStreamReader 是一个连接字节流和字符流的桥梁,它将字节流转变为字符流。FileReader 可以说是一个达到此功能、常用的工具类,在其源代码中明显使用了将FileInputStream 转变为Reader 的方法。我们可以从这个类中得到一定的技巧。Reader 中各个类的用途和使用方法基本和InputStream 中的类使用一致。
(5).字符输出流Writer
Writer 是所有的输出字符流的父类,它是一个抽象类。Writer里的构造方法是私有的,只能由子类访问,在读取流的时候不用刷新流。使用完流后要记得关闭资源。
CharArrayWriter、StringWriter 是两种基本的介质流,它们分别向Char 数组、String 中写入数据。PipedWriter 是向与其它线程共用的管道中写入数据,
BufferedWriter 是一个装饰器为Writer 提供缓冲功能。它的出现是为了提高效率的。使用步骤:首先创建一个字符写入流对象FileReader,如FileReader fw=new FileReader("filename");其次,为了提高字符写入流效率,加入缓冲技术BufferedWriter,只要将需要被提高效率的流对象作为参数传递给缓冲区的构造函数即可,如BufferedWriter bufw=new BufferedWriter(fw);然后,调用BufferedWriter的Wirte()方法开始些数据;最后,关闭缓冲区,其实就是关闭流资源。
PrintWriter 和PrintStream 极其类似,功能和使用也非常相似。
OutputStreamWriter 是OutputStream 到Writer 转换的桥梁,它的子类FileWriter 其实就是一个实现此功能的具体类(具体可以研究一SourceCode)。FileWriter的一个构造方法FileWriter(String boolean)的第二个参数代表是否要覆盖已有文件,如果为true,则不覆盖原文件,功能和使用和OutputStream 极其类似
(6).字符流的输入与输出的对应
(7).字符流与字节流转换
转换流是字符流和字节流之间的桥梁
可对读取到的字节数据经过指定编码转换成字符
可对读取到的字符数据经过指定编码转换成字节
何时使用转换流?
当字节和字符之间有转换动作时;
流操作的数据需要编码或解码时。
具体的对象体现:
InputStreamReader:字节到字符的桥梁
OutputStreamWriter:字符到字节的桥梁
这两个流对象是字符体系中的成员,它们有转换作用,本身又是字符流,所以在构造的时候需要传入字节流对象进来。
(8).File类
File类是对文件系统中文件以及文件夹进行封装的对象,可以通过对象的思想来操作文件和文件夹。 File类保存文件或目录的各种元数据信息,包括文件名、文件长度、最后修改时间、是否可读、获取当前文件的路径名,判断指定文件是否存在、获得当前目录中的文件列表,创建、删除文件和目录等方法。
(9).RandomAccessFile类
该对象并不是流体系中的一员,其封装了字节流,同时还封装了一个缓冲区(字符数组),通过内部的指针来操作字符数组中的数据。 该对象特点:该对象只能操作文件,所以构造函数接收两种类型的参数:a.字符串文件路径;b.File对象。
该对象既可以对文件进行读操作,也能进行写操作,在进行对象实例化时可指定操作模式(r,rw)
注意:该对象在实例化时,如果要操作的文件不存在,会自动创建;如果文件存在,写数据未指定位置,会从头开始写,即覆盖原有的内容。 可以用于多线程下载或多个线程同时写数据到文件。
2.java.io常用类
JDK所提供的所有流类位于java.io包中,都分别继承自以下四种抽象流类。
InputStream:继承自InputStream的流都是用于向程序中输入数据的,且数据单位都是字节(8位)。
OutputStream:继承自OutputStream的流都是程序用于向外输出数据的,且数据单位都是字节(8位)。
Reader:继承自Reader的流都是用于向程序中输入数据的,且数据单位都是字符(16位)。
Writer:继承自Writer的流都是程序用于向外输出数据的,且数据单位都是字符(16位)。
3.装饰类(包装类)
定义:动态地给一个对象添加一些额外的增强功能的职责。使用装饰类模式比用生成子类的方式达到功能的扩展显得更为灵活,通常可以使用继承来实现功能的拓展,如果这些需要拓展的功能的种类很繁多,那么势必生成很多子类,增加系统的复杂性,同时,使用继承实现功能拓展,我们必须可以预见这些拓展功能。这些功能在编译时就确定了,是静态的。在不必改变原类文件和使用继承的情况下,动态地拓展一个对象的功能,即装饰这个对象。它是通过创建一个包装对象,也就是装饰类来包裹真实的对象来实现的。
实现方式:通常继承某个基类,然后将这个基类的对象作为构造方法的参数传过来,然后利用多态对该对象进行功能增强。也即是装饰该类型的对象。
示例:一个程序员通过不断的学习,可以变成作家和科学家,这也是一个功能(能力)增强的例子
package com.ItHeima.WeekAct; /** * 装饰类的演示 * @author zsy * */ public class DecoratorDemo { /** * 被装饰的基类 * @author 支胜勇 * */ interface Person { public void sutdy(); } /** * 程序员 * @author 支胜勇 * */ static class Programmer implements Person { public void sutdy() { System.out.println("我能写程序"); } } /** * 作家 * @author 支胜勇 * */ static class Writer implements Person { //被装饰者 private Person person; public Writer(Person person) { this.person = person; } public void sutdy() { //作家装饰者做的职责 System.out.println("我开始学习写作"); //被装饰者做的职责 person.sutdy(); //作家装饰者做的职责 System.out.println("我能写作了。"); } } /** * 科学家 * @author 支胜勇 * */ static class Scientist implements Person { //被装饰者 private Person person; public Scientist(Person person) { this.person = person; } public void sutdy() { //科学家装饰者做的职责 System.out.println("我开始学习科学技术。"); //被装饰者做的职责 person.sutdy(); //科学家装饰者做的职责 System.out.println("我能做科学研究了。"); } } public static void main(String[] args) { //只能写程序 Person programmer = new Programmer (); programmer.sutdy(); System.out.println(" "); //通过学习以后,不但能写程序,而且还能写作 programmer = new Writer(programmer); programmer.sutdy(); System.out.println(" "); //通过学习,除了写程序、写作外,还能搞科研 programmer = new Scientist(programmer); programmer.sutdy(); } }
运行结果:
4.代码演示
/*1、FileInputStream和FileOutputStream节点流,用于从文件中读取或往文件中写入字节流。如果在构造FileOutputStream时,文件已经存在,则覆盖这个文件。—————————————————————————— - Demo2:将字符串写入特定文件,注意write方法只接收字符数组。—————————————————————————— - */ import java.io. * public class StreamDemo { public static void main(String[] args) throws Exception { FileOutputStream fos = new FileOutputStream(" 1.txt "); /**/ /* * 注意:FileOutputStream的write方法接收字符数组,不能接收String字符串, * 所以要用String的getBytes方法生成一个字符数组 */ fos.write(" http://www.cnblogs.com ".getBytes()); fos.close(); } } /*————————————————————- String的构造方法的API:————Java API: ————String public String(byte[] bytes, int offset, int length)构造一个新的String,方法是使用指定的字符集解码字节的指定子数组。新的String的长度是一个字符集函数,因此不能等于该子数组的长度。当给定字节在给定字符集中无效的情况下,该构造方法无指定的行为。当需要进一步控制解码过程时,应使用CharsetDecoder类。参数:bytes–要解码为字符的字节offset–要解码的首字节的索引length–要解码的字节数抛出:IndexOutOfBoundsException–如果offset和length参数索引字符超出bytes数组的范围从以下版本开始:JDK1.1—————————————————————————— - —————————————————————————— - Demo3:将字符串写入特定文件,注意write方法只接收字符数组。然后通过文件输出流读取数据,注意使用String特定的构造方法。—————————————————————————— - */ import java.io. * public class StreamDemo { public static void main(String[] args) throws Exception { FileOutputStream fos = new FileOutputStream(" 1.txt "); /**/ /* * 注意:FileOutputStream的write方法接收字符数组,不能接收String字符串, * 所以要用String的getBytes方法生成 一个字符数组 */ fos.write(" http://www.cnblogs.com ".getBytes()); fos.close(); // 使用String的这个构造方法: // String(byte[] bytes, int offset, int length) FileInputStream fis = new FileInputStream( " 1.txt " ); byte [] buf = new byte [ 100 ]; int len = fis.read(buf); // 使用String的这个构造方法: // String(byte[] bytes, int offset, int length) System.out.println( new String(buf, 0 , len)); fis.close(); // 使用完后记得关闭文件流 } }
import java.io. * ; public class Test { public static void main(String[] args) { try { FileInputStream fis = new FileInputStream("E:/from.txt"); FileOutputStream fos = new FileOutputStream("E:/to.txt"); byte[] buffer = new byte[1024]; while (true) { //循环读取源文件,每次读取1k大小的字节放入目标文件 int temp = fis.read(buffer, 0, buffer.length); if (temp == -1) break; //读取到文件尾部 fos.write(buffer, 0, temp); } } catch(Exception e) { System.out.println(e); } finally { try { fis.close(); fos.close(); } catch(Exception e) { System.out.println(e); } } } } /*Java的IO流设计使用了一种经典的设计模式,装饰者模式,将这些IO流分为两大类:节点流:进行最基本的IO操作。比如FileReader,InputStreamReader处理流:在最基本的IO操作基础上,增加了一些其它操作。比如BufferedReader,在FileReader的基础上,提供了一次读取一行的操作,readline(),例子代码:*/ import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.util.ArrayList; public class RecordTime { public static void main(String[] args) { try { BufferedReader br = new BufferedReader(new FileReader("/home/time.txt")); String line = null; String time = null; ArrayList < Integer > timeList = new ArrayList < Integer > (); int totalTime = 0; int minTime = 0; int maxTime = 0; line = br.readLine(); while (line != null) { if (line.startsWith("time=")) { time = line.substring(5); timeList.add(Integer.parseInt(time)); } line = br.readLine(); } br.close(); for (int i = 0; i < timeList.size(); i++) { System.out.println(timeList.get(i)); totalTime += timeList.get(i); if (i == 0) { minTime = timeList.get(i); maxTime = timeList.get(i); } else if (timeList.get(i) < minTime) { minTime = timeList.get(i); } else if (timeList.get(i) > maxTime) { maxTime = timeList.get(i); } } System.out.println("Avarage time is " + totalTime / timeList.size()); System.out.println("Min time is " + minTime); System.out.println("Max time is " + maxTime); BufferedWriter bw = new BufferedWriter(new FileWriter("/home/time_record.txt")); bw.write("Avarage time is " + totalTime / timeList.size()); bw.newLine(); bw.write("Min time is " + minTime); bw.newLine(); bw.write("Max time is " + maxTime); bw.newLine(); bw.flush(); bw.close(); timeList.clear(); } catch(FileNotFoundException e) { e.printStackTrace(); } catch(IOException e) { e.printStackTrace(); } } }