码迷,mamicode.com
首页 > 编程语言 > 详细

Java IO

时间:2015-10-13 22:43:18      阅读:168      评论:0      收藏:0      [点我收藏+]

标签:

 

  掌握IO是一件极其重要的事情。

 

  一、概览

  “流”(stream)有方向:流进(input stream)和流出(output stream)。

  “流”有流动的最小单位:①有基于一个字节(single-byte)流动的InputStream和OutputStream家族;②也有基于两个字节流动(two-byte)的Reader和Writer家族。

  

  为什么会有两大家族呢?

  1、基于single-byte流动的有两个最基本的抽象类(abstract classes):InputStream和OutputStream。稍后我们会看到以这两个抽象类作为父类,衍生了一个庞大的IO家族。

  2、由于基于single-byte的流不方便处理那些用Unicode编码方式存储的字符characters信息。从而java的IO系统中又出现了另外的一个基于Reader和Writer抽象类,用于处理characters信息的家族。

  

  二、读写bytes

  抽象类InputStream中有一个抽象读方法:

abstract int read();

  每次调用这个方法就会从流中读取一个byte并返回读取到的byte值;如果遇到输入流的末尾,则返回-1。

  这个抽象类还重载了其它的read方法,但都是在底层调用了上面这个读取单字节的抽象的read()方法。该抽象类还有如下方法:

  ①、abstract int read();

  ②、int read(byte[] b),最大读取b.length个字节数据;

  ③、int read(byte[] b, int off, int len),最大读取len个字节数据到b字节数组中,从off位置开始存放;

  ④、long skip(long n),在输入流中跳过n个字节,返回实际跳过的字节数。当遇到末尾的时候实际跳过的数据可能小于n;

  ⑤、int available(),返回在不阻塞的情况下流中的可以读取的字节数;

  ⑥、void close(),关闭流;

  ⑦、void mark(int readlimit),在输入流的当前位置打一个标记(注:不是所有的流都支持这一特性);

  ⑧、void reset(),返回到最后一个标记处。随后调用read方法会从最后一个标记处重新读取字节数据。如果当前没有标记,则不会有任何变化;

  ⑨、boolean markSupported(),判断当前流是否支持标记操作;

 

  对应的,抽象类OutputStream中也有一个抽象的写方法:

abstract void write(int b);

  OutputStream类有如下方法:

  ①、abstract void write(int b);

  ②、void write(byte[] b),将b中存放的所有数据都写入到流中;

  ③、void write(byte[], int off, int len),将b字节数组中从off位置开始的len个字节数据写入到流中;

  ④、void close(),关闭和flush输出流;

  ⑤、void flush,对输出流做flush操作,也就是说,将所有输出流中缓存的数据都写入到实际的目的地;

 

  上面抽象的read()和write()方法都会阻塞,直到byte读写成功为止。这就意味着,如果在读写过程中,如果当前流不可用,那么当前线程就会被阻塞。为解决阻塞的问题InputStream类提供了一个avaliable()方法,可以检测当前可读的字节数。所以,下面这段代码永远不会被阻塞:

int bytesAvailable = in.available();
if(bytesAvailable > 0){
     byte[] data = new byte[bytesAvailable];
     in.read(data);         
}

  当我们读写完毕以后,应该要调用close()函数来关闭流。这样做,一方面可以释放掉流所持有的系统资源。另外一方面,关闭一个输出流也会将暂存在流中的数据flush到目标文件中去:输出流会持有一个buffer,在其buffer没有满的时候是不会实际将数据传递出去的。特别的,如果你没有关闭一个输出流,那么很有可能会导致最后那些存放在buffer中的数据没有被实际的传递出去。当然,我们也可以通过调用flush()方法手动的将buffer中的数据flush出去。

   

  三、结合stream filters

  先来看一下第一个家族:

技术分享

  什么叫Combining Stream Filter呢?我们逐一的解释。

  我们从第一个层面上看(直接继承自InputStream或OutputStream的这些类),FileInputStream能够让你得到一个附着在磁盘文件上的输入流,FileOutputStream能够得到一个对磁盘文件的输出流。比如用下面的方式:

FileInputStream fin = new FileInputStream("employee.dat");
FileOutputStream fout = new FileOutputStream("employee.dat");

  和InputStream、OutputStream抽象类一样,FileInputStream和FileOutputStream也只提供基于byte的读写方法

  但是,我们如果能够得到一个DateInputStream,那么我们就可以从流中读取numeric types了,比如我们可以从流中读取一个double类型的数据:

DataInputStream din = ...
double s = din.readDouble();

  现在我们可以YY一下,要是能够直接向file中读写numeric types该多好!!!你当然可以做得到,就像下面这样:

FileInputStream fin = new FileInputStream("employee.dat");
DataInputStream din = new DataInputStream(fin);
Double s = din.readDouble();

  看,你做到了。只要将两个层面上的流结合起来,就可以了。java使用了一种很好的机制将对底层和对上层的操作分开,这样既方便了流向底层写byte,也方便了我们使用我们习惯的numeric types类型。

  再介绍一对很重要的流,它对提高读写效率有很大的帮助:BufferedInputStream和BufferedOutputStream,他们分别为输入和输出流提供了一个缓冲区。比如在上面的流中添加一个缓冲区,让它更快一些:

FileInputStream fin = new FileInputStream("employee.dat");
BufferedInputStream bin = new BufferedInputStream(fin);
DataInputStream din = new DataInputStream(bin);
Double s = din.readDouble();

  有了上面的分层介绍以后,你当然会很明白为什么要将BufferedInputStream放在中间层,而不是很杀马特的将其放在最外层了。你可知道,BufferedInputStream和BufferedOutputStream只提供对byte的读写方法。还有以下两个例子:

//1、可以利用pin.unread(b)来跳跃,利用din.readLong()等读取numeric types
PushbackStream pin = null;
DataInputStream din = new DataInputStream(
      pin = new PushbackStream(new FileInputStream("employee.dat")));

//2、对zip的操作
ZipInputStream zin = new ZipInputStream(new FileInputStream("employee.dat"));
DataInputStream din = new DataInputStream(zin);

  理解到这里,我们可以放心的相信一件事情了:关闭流的时候,只需要关闭最外层的流即可。因为,它自己会一层一层的往里面调用close()方法。

  

  四、读写character

  字符相对来说比java基本类型的数据难处理。我们知道,字符有很多种编码方式。比如,ASCII编码占用1个字节长度,每个Unicode占用2个字节长度。为了方便处理文本形式的流,JDK单独开辟了另外一个专门的IO家族——Reader和Writer。类似于前面的InputStream和OutputStream,这两个类分别有一个抽象的读/写方法:

abstract int read();  //返回一个0~65535之间的整数,遇到流末尾则返回-1。
abstract void write(int c);

 

  五、读写文本(text)

  当你向保存一个数据的时候,你有两种选择保存数据的方式:二进制和文本格式。比如说,整数1234用二进制保存的时候,它是这样的 00 00 04 D2(in hex);如果采用文本格式,则它会被保存为字符串“1234”的形式。

  尽管,对二进制数据的读写很快速而且高效,但是二进制不方便于人的阅读。当我们保存一个一个文本字符串的时候,我么需要考虑到字符的编码方式。如果用UTF-16的编码方式,则“1234”将会保存为 00 31 00 32 00 33 00 34(in hex);而采用ISO8859-1编码,则会保存为 31 32 33 34(in hex)。举个例子:

InputStreamReader in = new InputStreamReader(System.in);

这个InputStreamReader会将从控制台读取到的数据用系统默认的编码方式进行编码。当然,也可以用InputStreamReader(new FileInputStream("kernel.dat"),"ISO8859_5")的方式明确指定哪种编码方式。

  因为,我们有很多地方需要将一个file绑定到reader或者是writer上面;所以,JDK给我们提供了一对方便的读写类FileReader和FileWriter。比如说下面两种定义是等价的:

//方便的定义方式
FileWriter out = new FileWriter("output.txt");

//等价的定义方式
FileWriter out = new FileWriter(new FileOutputStream("output.txt"));

  

  1、怎样写Text

  对于文本的输出,有一个方便的类PrintWriter。因为,这个类提供了文本格式的写字符串和写数字的方法,其print方法有很多种重载方式。同时,我们还可以很方便的将PrintWriter和FileWriter联系起来,下面的两种方式是等价的:

//定义PrintWriter的便捷方式
PrintWriter out = new PrintWriter("out.txt");

//等价的定义方式
PrintWriter out = new PrintWriter(new FileWriter("out.txt"));

//联想到FileWriter我们还可以得出一种等价方式
PrintWriter out = new PrintWriter(new FileWriter(new FileOutputStream("out.txt")));

  还需要注意的一点就是,PrintWriter自带了一个缓冲器,默认情况下只有在缓冲区填满的时候才会将数据flush到目的地。PrintWriter的构造器有两种:

//默认情况下 autoFlush是关闭的,缓冲区慢才会将数据传递出去
PrintWriter out = new PrintWriter(Writer out);

//可以指定autoFlush为true。这样,无论何时调用print函数,都会立刻flush缓冲区
PrintWriter out = new PrintWriter(Writer out, boolean autoFlush);

  注意,PrintWriter有如下的构造函数:

PrintWriter(Writer out)
PrintWriter(Writer out, boolean autoFlush)

PrintWriter(String fileName)
PrintWriter(File file)

//这个很强大,可以直接对输出流做打印
PrintWriter(OutputStream out)
PrintWriter(OutputStream out, boolean autoFlush)

//还有一个很有意思的printf函数。这个对调整格式很方便
void printf(String format, Object... args)

 

  2、怎样读Text

  如我们所知道的,对二进制数据的读写很方便的可以使用DataInputStream和DataOutputStream对。上面也说了,写Text有一个很好用的PrintWriter。那么,读Text呢?还会有想二进制这么方便吗?比如说,我想读取一个Double类型的数据: r.readDouble()。答案:不好意思,没有!!

  就目前来讲,有两种方式:①、Scanner类可用,也提供了不少方法;②、BufferedReader in = new BufferedReader(new FileReader("employee.txt"));可用,用它来读取一行,然后自行分解去吧。但是,BufferedReader么有读取numeric这么方便的方法。

  其实,也可想而知,文本嘛,就没有所谓的Double啊,Integer啊什么的区别了,所有的都是“文本”了,只是它长得像数字罢了。

 

   最后,来看一下Writer和Reader家族:

技术分享

 

Java IO

标签:

原文地址:http://www.cnblogs.com/lj95801/p/4872955.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!