Java IO(Input Output)流总结
Last modified:2013-06-13 08:01:44
***********************************************
IO流总结一:
IO流是处理数据之间数据传输的。
Java对数据的操作是通过流的方式。
Java中用于操作IO流的对象都放在java.io包中。
流的分类:按照操作数据分为:字符流和字节流。
按照流向分为:输入流和输出流。
输入流: 输出流:
字符流:
Reader Writer
|--BufferedReader |-- BufferedWriter
|--inputStreamReader |--OutputStreamWriter
|--FileReader |--FileWriter
字节流:
InputStream OutputStream
|--FileInputStream |--FileOutputStream
InputStream OutputStream
|--FilterInputStream |--FilterOutputStream
|--BufferedInputStream |--BufferedOutputStream
在计算机中存储的都是1和0的序列。也就是二进制的有序序列,不论是文本、音乐或者是视频。
那么为什么要在流中定义字节流和字符流呢?
这个与我们的字符编码方式有关。我们都知道世界上有很多种的语言,比如:ASCII、GB2312、GBK、UTF-8和Unicode等。
如果一个文本文件使用GB2312编码的方式存储的,如果我们用Unicode的编码方式来读取,那么就会出现乱码。所以字符流是一种特殊的流,在java中就定义了专门处理字符的流对象。
当我们拿到一个API文档我们应该如何使用呢?
1,确定要使用的功能。
2,查阅API看有没有相关的功能类。
3,如果没有,就分析需要如何自定义一个出来,并且要使用到那些相关功能的类,这些类在API中有没有定义。
4,不论有或者没有需要自定义一个,我们都要先查阅相关功能类的根类,那么查阅一个API的时候我们要注意一些什么呢?
5,找到相关功能根类,先看一下它是一个类,还是接口,还是抽象类,如果是接口或者是抽象类我们就不能直接使用这个类,而要使用这个类的子类。
6,但是,我们不用急于先看它有哪些子类,我们先看一下它到底暴露了什么字段、构造函数和方法。
7,在查看暴露的信息时,我们要注意几点:
a. 如果是static修饰的,说明是静态的,我们不用new对象也可以直接使用。
b. 确定使用的方法返回数据的类型,是void呢、String呢、int呢、还是其他的。查找的时候就重点找返回这些类型的方法。(注意:如果我们使用的类是一个使用单例设计模式设计的,那么他就没有构造函数,我们就一般可以通过静态的getIstance()方法获取相应的对象,这时我们就要找返回值是对象类型的方法。)
8,如果在根类中找到了需要的方法,但是根类又不能创建对象,那么我们就看看,继承这个根类的子类有哪些,一般子类都继承了父类的方法,所以子类可以直接调用父类的方法,并且子类又定义了一些自身特别的方法。
9,找到需要的类创建对象,或者找到相关功能的对象自定义一个需要的类。
下面我们按照以上的方法来阐述IO流的学习:
字节流:
字节流的根类有:读取流(Reader)、写入流(Writer)
根类都是abstract(抽象)的,我们不能直接创建对象,但是我们可以看一看父类都定义了什么方法。
Reader:
int read()
读取单个字符。
int read(char[] cbuf)
将字符读入数组。
abstract int read(char[] cbuf, int off, int len)
将字符读入数组的某一部分。
int read(CharBuffer target)
试图将字符读入指定的字符缓冲区。
abstract void close()
关闭该流并释放与之关联的所有资源。
FileReader:Reader的子类,可以创建对象,专门用来操作字符数据流的。
Writer:
void write(char[] cbuf)
写入字符数组。
abstract void write(char[] cbuf, int off, int len)
写入字符数组的某一部分。
void write(int c)
写入单个字符。
void write(String str)
写入字符串。
void write(String str, int off, int len)
写入字符串的某一部分。
abstract void close()
关闭此流,但要先刷新它。
abstract void flush()
刷新该流的缓冲。
写入流(Writer)在每次调用write()方法的时候都要flush()(刷新)一次,当然Writer也不能创建对象,我们可以使用他的子类FileWriter,FileWriter是专门处理字符写入流的类。
FileReader 和 FileWriter 为我们提供了操作字符数据流的一般方法,其中比较高效的就是自定义一个数组,用这个数组作为临时存储区,每次读取一块(装满数组)数据,然后再将这一块数据写入相应的目的地。这样就提高了效率,如果每次读取一个字符然后写入一个字符,就很低效,是不可取的。
自定一个数组为我们提高了效率,但是每次使用都要写很多代码,所以开发者就将这个数组封装为了一个特殊的对象。那就是缓冲区!BufferedReader(字符读取缓冲区)和BufferedWriter(字符写入缓冲区)。他是用的是装饰设计模式,是再不改变原来已定义类的基础上增强类的功能。
补充:BufferedReader的子类LineNumberReader,增加了int getLineNumber() :获得当前行号的功能,我们可以在使用缓冲区的同时读取行号。
装饰设计模式:
当想要对已有的对象进行功能增强时,可以定义类,将已有对象传入,基于已有的功能,并提供加强功能。那么自定义的该类称为装饰类。
字符流学习完了,那么如果要学习字节流就会简单很多,我们通过查阅API知道:很多方法都似曾相识。所以,总结一下就是下面几点:
A. 字节流分为:InputStream(读取流)和OutputStream(写入流)
B. 字节流和字符流的功能基本相同,只不过传入的参数由字符流中的字节char,变成了字节中的byte。
C. 字节流也具有缓冲区:
BufferedInputStream(字节读取缓冲区)和BufferedOutputStream(字节写入缓冲区)。方法与字符缓冲区相似。
下面自定义一个BufferedInputStream:
import java.io.*; class MyBufferedInputStream { private InputStream in; private byte[] buf = new byte[1024*4]; private int pos = 0,count = 0; MyBufferedInputStream(InputStream in) { this.in = in; } //一次读一个字节,从缓冲区(字节数组)获取。 public int myRead()throws IOException { //通过in对象读取硬盘上数据,并存储buf中。 if(count==0) { count = in.read(buf); if(count<0) return -1; pos = 0; byte b = buf[pos]; count--; pos++; return b&255; } else if(count>0) { byte b = buf[pos]; count--; pos++; return b&0xff; } return -1; } public void myClose()throws IOException { in.close(); } }
结论:
字节流的读一个字节的read方法为什么返回值类型不是byte,而是int。
因为有可能会读到连续8个二进制1的情况,8个二进制1对应的十进制是-1.
那么就会数据还没有读完,就结束的情况。因为我们判断读取结束是通过结尾标记-1来确定的。
所以,为了避免这种情况将读到的字节进行int类型的提升。
并在保留原字节数据的情况前面了补了24个0,变成了int类型的数值。
而在写入数据时,只写该int类型数据的最低8位。
明白了BufferedReader类中特有方法readLine的原理后,
可以自定义一个类中包含一个功能和readLine一致的方法。
来模拟一下BufferedReader
import java.io.*; class MyBufferedReader extends Reader { private Reader r; MyBufferedReader(Reader r) { this.r = r; } //可以一次读一行数据的方法。 public String myReadLine()throws IOException { //定义一个临时容器。原BufferReader封装的是字符数组。 //为了演示方便。定义一个StringBuilder容器。因为最终还是要将数据变成字符串。 StringBuilder sb = new StringBuilder(); int ch = 0; while((ch=r.read())!=-1) { if(ch=='\r') continue; if(ch=='\n') return sb.toString(); else sb.append((char)ch); } if(sb.length()!=0) return sb.toString(); return null; } /* 覆盖Reader类中的抽象方法。 */ public int read(char[] cbuf, int off, int len) throws IOException { return r.read(cbuf,off,len) ; } public void close()throws IOException { r.close(); } public void myClose()throws IOException { r.close(); } } class MyBufferedReaderDemo { public static void main(String[] args) throws IOException { FileReader fr = new FileReader("buf.txt"); MyBufferedReader myBuf = new MyBufferedReader(fr); String line = null; while((line=myBuf.myReadLine())!=null) { System.out.println(line); } myBuf.myClose(); } } import java.io.*; class MyLineNumberReader extends MyBufferedReader { private int lineNumber; MyLineNumberReader(Reader r) { super(r); } public String myReadLine()throws IOException { lineNumber++; return super.myReadLine(); } public void setLineNumber(int lineNumber) { this.lineNumber = lineNumber; } public int getLineNumber() { return lineNumber; } } /* class MyLineNumberReader { private Reader r; private int lineNumber; MyLineNumberReader(Reader r) { this.r = r; } public String myReadLine()throws IOException { lineNumber++; StringBuilder sb = new StringBuilder(); int ch = 0; while((ch=r.read())!=-1) { if(ch=='\r') continue; if(ch=='\n') return sb.toString(); else sb.append((char)ch); } if(sb.length()!=0) return sb.toString(); return null; } public void setLineNumber(int lineNumber) { this.lineNumber = lineNumber; } public int getLineNumber() { return lineNumber; } public void myClose()throws IOException { r.close(); } } */ class MyLineNumberReaderDemo { public static void main(String[] args) throws IOException { FileReader fr = new FileReader("copyTextByBuf.java"); MyLineNumberReader mylnr = new MyLineNumberReader(fr); String line = null; mylnr.setLineNumber(100); while((line=mylnr.myReadLine())!=null) { System.out.println(mylnr.getLineNumber()+"::"+line); } mylnr.myClose(); } }
io流有很多实例,那么在实际操作中如何确定使用哪个io流的实例呢?
1,
源:键盘录入。
目的:控制台。
2,需求:想把键盘录入的数据存储到一个文件中。
源:键盘。
目的:文件。
3,需求:想要将一个文件的数据打印在控制台上。
源:文件。
目的:控制台。
流操作的基本规律:
最痛苦的就是流对象有很多,不知道该用哪一个。
通过三个明确来完成。
1,明确源和目的。
源:输入流。InputStream Reader
目的:输出流。OutputStream Writer。
2,操作的数据是否是纯文本。
是:字符流。
不是:字节流。
3,当体系明确后,在明确要使用哪个具体的对象。
通过设备来进行区分:
源设备:内存,硬盘。键盘
目的设备:内存,硬盘,控制台。
1,将一个文本文件中数据存储到另一个文件中。复制文件。
源:因为是源,所以使用读取流。InputStream Reader
是不是操作文本文件。
是!这时就可以选择Reader
这样体系就明确了。
接下来明确要使用该体系中的哪个对象。
明确设备:硬盘。上一个文件。
Reader体系中可以操作文件的对象是 FileReader
是否需要提高效率:是!。加入Reader体系中缓冲区 BufferedReader.
FileReader fr = new FileReader("a.txt");
BufferedReader bufr = new BufferedReader(fr);
目的:OutputStream Writer
是否是纯文本。
是!Writer。
设备:硬盘,一个文件。
Writer体系中可以操作文件的对象FileWriter。
是否需要提高效率:是!。加入Writer体系中缓冲区 BufferedWriter
FileWriter fw = new FileWriter("b.txt");
BufferedWriter bufw = new BufferedWriter(fw);
练习:将一个图片文件中数据存储到另一个文件中。复制文件。要按照以上格式自己完成三个明确。
---------------------------------------
2,需求:将键盘录入的数据保存到一个文件中。
这个需求中有源和目的都存在。
那么分别分析
源:InputStream Reader
是不是纯文本?是!Reader
设备:键盘。对应的对象是System.in.
不是选择Reader吗?System.in对应的不是字节流吗?
为了操作键盘的文本数据方便。转成字符流按照字符串操作是最方便的。
所以既然明确了Reader,那么就将System.in转换成Reader。
用了Reader体系中转换流,InputStreamReader
InputStreamReader isr = new InputStreamReader(System.in);
需要提高效率吗?需要!BufferedReader
BufferedReader bufr = new BufferedReader(isr);
目的:OutputStream Writer
是否是存文本?是!Writer。
设备:硬盘。一个文件。使用 FileWriter。
FileWriter fw = new FileWriter("c.txt");
需要提高效率吗?需要。
BufferedWriter bufw = new BufferedWriter(fw);
**************
扩展一下,想要把录入的数据按照指定的编码表(utf-8),将数据存到文件中。
目的:OutputStream Writer
是否是存文本?是!Writer。
设备:硬盘。一个文件。使用 FileWriter。
但是FileWriter是使用的默认编码表。GBK.
但是存储时,需要加入指定编码表utf-8。而指定的编码表只有转换流可以指定。
所以要使用的对象是OutputStreamWriter。
而该转换流对象要接收一个字节输出流。而且还可以操作的文件的字节输出流。FileOutputStream
OutputStreamWriter osw =
new OutputStreamWriter(new FileOutputStream("d.txt"),"UTF-8");
需要高效吗?需要。
BufferedWriter bufw = new BufferedWriter(osw);
所以,记住。转换流什么使用。字符和字节之间的桥梁,通常,涉及到字符编码转换时,
需要用到转换流。
练习:将一个文本数据打印在控制台上。要按照以上格式自己完成三个明确。
class TransStreamDemo2 { public static void main(String[] args) throws IOException { System.setIn(new FileInputStream("PersonDemo.java")); System.setOut(new PrintStream("zzz.txt")); //键盘的最常见写法。 BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in)); BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out)); String line = null; while((line=bufr.readLine())!=null) { if("over".equals(line)) break; bufw.write(line.toUpperCase()); bufw.newLine(); bufw.flush(); } bufr.close(); } }