Java IO库简介

一、流的概念

Java API中,可以从其中读入一个字节序列的对象称作输入流,而可以向其中写入一个字节序列的对象称作输出流。这些字节序列的来源地和目的地可以是文件,而且通常都是文件,但是也可以是网络连接,甚至是内存块。编程语言的I/O类库中使用流这个抽象概念,它代表任何有能力产出数据的数据源对象或者是有能力接收数据的接收端对象。

简单的理解:流是数据和数据处理过程的统称。

流操作关心三部分内容:数据源、目标以及过程。

这些数据源包括:

1)      字节数组

2)      String对象

3)      文件

4)      “管道”,工作方式与实际管道相似,即,从一段输入,从另一端输出。

5)      一个由其他种类的流组成的序列,以便我们可以将它们收集合并到一个流内。

6)      其他数据源,如Internet连接等。

二、流的分类

1.  按流的方向:输入流和输出流

可以从其中读入一个字节序列的对象称作输入流,而可以向其中写入一个字节序列的对象称作输出流。入和出都是相对于程序本身来说的(不是相当于流本身,因为程序中的“入”,是将流中的数据读取到程序中,对流来说此时是“出”),比如,在程序中使用一个文件保存数据,进行的操作是向流中写入数据保存到文件,此时的流是输出流从流中读取数据到程序的流称为输入流


上面的概念中也反映了该类流的特点:对输入流只能进行读操作,对输出流只能进行写操作。

1.  按数据单位:字节流和字符流

在电脑磁盘上,所有的数据都是以字节的形式传输和保存,包括图片、视频、文档等数据。一个字节占用8位二进制(8bit),而一个字符根据编码方式的不同,占用的存储空间是不同的。有句话很适合现在:基本的才是万能的。所以字节流能够处理任何类型的数据,而字符流只能处理字符类型的数据。

2.  按功能:节点流和过滤流

节点流是使用原始的流类进行的操作,而过滤流是在节点流的基础上进行修饰以获得更多的功能,这里使用了装饰器模式。

三、输入流和输出流层次结构

字节流的顶级父类是抽象类InputStream和OutputStream,输入输出流的层次结构如下图:

Reader和Writer的层次结构如下图:


Java类库中的I/O类分成输入和输出两部分,可以在JDK的文档里的类层次结构中查看到。通过继承,任何自InputStream或Reader派生而来的类都含有名为read()的基本方法,用于读取单个字节或字节数组。同样,任何自OutputStream或Writer类派生未来的类都含有名为write()的基本方法,用于写单个字节或者字节数组。但是,我们通常不会用到这些方法,他们之所以存在是因为别的类可以使用它们,以便提供更有用的接口。因此,我们很少使用单一的类来创建流对象,而是通过叠合多个对象来提供所期望的功能(这就是装饰器设计模式)。实际上,Java“流”类库让人迷惑的主要原因就在于:创建单一的结果流,却需要创建多个对象。

FilterInputStream和FilterOutputStream是用来提供装饰器类接口以控制特定输入流额输出流的两个类,他们的名字不是很直观。FilterInputStream和FilterOutputStream分别自I/O类库中的积累InputStream和OutputStream派生而来,这两个类是装饰器的必要条件(以便能为所有正在被修饰的对象提供通用接口)。

FilterInputStream类能够完成两件完全不同的事情。其中,DataInputStream允许我们读取不同的基本类型数据以及String对象(所有方法都以“read”开头,例如readByte()、readFloat()等等)。搭配相应的DataInputStream,我们就可以通过数据“流”将基本类型的数据从一个地方迁移到另一个地方。

其他FilterInputStream类则是在内部修改InputStream的行为方式:是否缓冲,是否保留它所读过的行(允许我们查询行数或设置行数),以及是否把单一字符推回输入流等等。最后两个类看起来更像是为了创建一个编译器(它们被添加进来可能是为了对“用Java构建编译器”实验提供支持),因此我们在一般编程中不会用到它们。

我们几乎每次都要对输入 进行缓冲——不管我们正在连接的是什么I/O设备,所以,I/O类库把无缓冲输入(而不是缓冲输入)作为特殊情况(或只是方法调用)就显得更加合理了。

四、Java IO分类介绍

4.1 字节流

4.1.1字节输入流

InputStream表示字节输入流的所有类的超类,

FileInputStream 从文件系统中的某个文件中获得输入字节。

ByteArrayInputStream 包含一个内部缓冲区,该缓冲区包含从流中读取的字节。内部计数器跟踪read 方法要提供的下一个字节。关闭ByteArrayInputStream 无效。此类中的方法在关闭此流后仍可被调用,而不会产生任何IOException

PipedInputStream管道输入流应该连接到管道输出流;管道输入流提供要写入管道输出流的所有数据字节。通常,数据由某个线程从PipedInputStream 对象读取,并由其他线程将其写入到相应的PipedOutputStream。不建议对这两个对象尝试使用单个线程,因为这样可能死锁线程。管道输入流包含一个缓冲区,可在缓冲区限定的范围内将读操作和写操作分离开。如果向连接管道输出流提供数据字节的线程不再存在,则认为该管道已损坏

FilterInputStream 包含其他一些输入流,它将这些流用作其基本数据源,它可以直接传输数据或提供一些额外的功能。

ObjectInputStream 对以前使用 ObjectOutputStream 写入的基本数据和对象进行反序列化。

SequenceInputStream 表示其他输入流的逻辑串联。它从输入流的有序集合开始,并从第一个输入流开始读取,直到到达文件末尾,接着从第二个输入流读取,依次类推,直到到达包含的最后一个输入流的文件末尾为止。

BufferedInputStream 为另一个输入流添加一些功能,即缓冲输入以及支持mark 和reset 方法的能力。在创建BufferedInputStream 时,会创建一个内部缓冲区数组。在读取或跳过流中的字节时,可根据需要从包含的输入流再次填充该内部缓冲区,一次填充多个字节。mark 操作记录输入流中的某个点,reset 操作使得在从包含的输入流中获取新字节之前,再次读取自最后一次mark 操作后读取的所有字节。

DataInputStream数据输入流允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型。应用程序可以使用数据输出流写入稍后由数据输入流读取的数据。

PushbackInputStream 为另一个输入流添加性能,即“推回 (push back)”或“取消读取 (unread)”一个字节的能力。

4.2.2字节输出流

OutputStream此抽象类是表示输出字节流的所有类的超类。

ByteArrayOutputStream此类实现了一个输出流,其中的数据被写入一个 byte 数组。缓冲区会随着数据的不断写入而自动增长。可使用toByteArray() 和toString() 获取数据。关闭ByteArrayOutputStream 无效。

FileOutputStream文件输出流是用于将数据写入 File 或FileDescriptor 的输出流。

FilterOutputStream此类是过滤输出流的所有类的超类。

ObjectOutputStream 将 Java 对象的基本数据类型和图形写入 OutputStream。

PipedOutputStream可以将管道输出流连接到管道输入流来创建通信管道。管道输出流是管道的发送端。

BufferedOutputStream该类实现缓冲的输出流。通过设置这种输出流,应用程序就可以将各个字节写入底层输出流中,而不必针对每次字节写入调用底层系统。

DataOutputStream数据输出流允许应用程序以适当方式将基本 Java 数据类型写入输出流中。然后,应用程序可以使用数据输入流将数据读入。

4.2 字符流

4.2.1字符输入流

Reader 用于读取字符流的抽象类。

BufferedReader从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。

LineNumberReader跟踪行号的缓冲字符输入流。此类定义了方法setLineNumber(int) 和 getLineNumber(),它们可分别用于设置和获取当前行号。

CharArrayReader此类实现一个可用作字符输入流的字符缓冲区。

InputStreamReader 是字节流通向字符流的桥梁:它使用指定的 charset 读取字节并将其解码为字符。它使用的字符集可以由名称指定或显式给定,或者可以接受平台默认的字符集。

FileReader用来读取字符文件的便捷类。此类的构造方法假定默认字符编码和默认字节缓冲区大小都是适当的。

StringReader其源为一个字符串的字符流。

PipedReader传送的字符输入流。

FilterReader用于读取已过滤的字符流的抽象类。

PushbackReader允许将字符推回到流的字符流 reader。

4.2.2字符输出流

Writer写入字符流的抽象类。

BufferedWriter将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。

CharArrayWriter此类实现一个可用作Writer 的字符缓冲区。缓冲区会随向流中写入数据而自动增长。可使用 toCharArray() 和 toString() 获取数据。在此类上调用 close() 无效。

OutputStreamWriter是字符流通向字节流的桥梁:可使用指定的 charset 将要写入流中的字符编码成字节。它使用的字符集可以由名称指定或显式给定,否则将接受平台默认的字符集。

         FileWriter用来写入字符文件的便捷类。

PipedWriter传送的字符输出流。

PrintWriter向文本输出流打印对象的格式化表示形式。此类实现在 PrintStream 中的所有 print 方法。它不包含用于写入原始字节的方法,对于这些字节,程序应该使用未编码的字节流进行写入。        

StringWriter一个字符流,可以用其回收在字符串缓冲区中的输出来构造字符串。关闭 StringWriter 无效。

FilterWriter用于写入已过滤的字符流的抽象类。

五、示例代码

1.缓冲输入文件

[java]  view plain  copy
 print ?
  1. public static String read(String filename) throws IOException{  
  2.     BufferedReader in = new BufferedReader(new FileReader(filename));  
  3.     String s;  
  4.     StringBuilder sb = new StringBuilder();  
  5.     while((s = in.readLine())!=null){  
  6.         sb.append(s + "\n");  
  7.     }  
  8.     in.close();  
  9.     return sb.toString();  
  10. lt;span style="white-space:pre"> </span>}  

2.      从内存输入

[java]  view plain  copy
 print ?
  1. <span style="white-space:pre">    </span>public static void memoryInput(String filename) throws IOException{  
  2.         StringReader in = new StringReader(BufferedInputFile.read(filename));  
  3.         int c;  
  4.         while((c = in.read())!=-1){  
  5.             System.out.print((char)c);  
  6.         }  
  7.     }  

3.格式化的内存输入

[java]  view plain  copy
 print ?
  1. <span style="white-space:pre">    </span>public static void formatMemoryInput(String filename) throws IOException{  
  2.         try{  
  3.         DataInputStream in = new DataInputStream(new ByteArrayInputStream(BufferedInputFile.read(filename).getBytes()));  
  4.         while(true){  
  5.             System.out.print((char)in.readByte());  
  6.         }  
  7.         }catch(IOException e){  
  8.             System.out.println("End of stream");  
  9.         }  
  10.     }  

4.一次一个字节的读取文件

[java]  view plain  copy
 print ?
  1. public static void testEOF(String filename) throws IOException{  
  2.     DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream(filename)));  
  3.     while(in.available() != 0){  
  4.         System.out.print((char)in.readByte());  
  5.     }  
  6.     in.close();  
  7. }  

5.基本的文件输出

[java]  view plain  copy
 print ?
  1. public static void basicFileOut(String filename , String fileout) throws IOException{  
  2.     BufferedReader in = new BufferedReader(new StringReader(BufferedInputFile.read(filename)));  
  3.     PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(fileout)));  
  4.     int lineCount = 1;  
  5.     String s;  
  6.     while((s = in.readLine())!=null){  
  7.         out.println(lineCount++ + ":" + s);  
  8.     }  
  9.     out.close();  
  10.     System.out.println(BufferedInputFile.read(fileout));  
  11. }  

6. 文本文件输出的快捷方式

[java]  view plain  copy
 print ?
  1. public static void fileOutputForShortcut(String filename , String fileout) throws IOException{  
  2.     BufferedReader in = new BufferedReader(new StringReader(BufferedInputFile.read(filename)));  
  3.     //Here is the shortcut  
  4.     PrintWriter out = new PrintWriter(fileout);  
  5.     int lineCount = 1;  
  6.     String s;  
  7.     while((s = in.readLine())!=null){  
  8.         out.println(lineCount++ + ":" + s);  
  9.     }  
  10.     out.close();  
  11.     System.out.println(BufferedInputFile.read(fileout));  
  12. }  

7.存储和恢复数据

PrintWriter可以对数据进行格式化,以便人们的阅读。但是为了输出可供另一个“流”恢复的数据,我们需要用DataOutputStream写入数据,并用DataInputStream恢复数据。当然,这些流可以是任意形式,但在下面的示例中使用的是一个文件,并且对于读和写都进行了缓冲处理。注意DataInputStream和DataOutputStream是面向字节的,因此要使用InputStream和OutputStream。
[java]  view plain  copy
 print ?
  1.     public static void storingAndRecoveringData(String outfilename) throws IOException{  
  2.         DataOutputStream out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(outfilename)));  
  3.         out.writeDouble(Math.PI);  
  4.         out.writeUTF("这是π的值。");  
  5.         out.writeDouble(3.1415926);  
  6.         out.writeUTF("这是π的近似值。");  
  7.         out.close();  
  8.         DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream(outfilename)));  
  9.         System.out.println(in.readDouble());  
  10.         System.out.println(in.readUTF());  
  11.         System.out.println(in.readDouble());  
  12.         System.out.println(in.readUTF());  
  13.     }  
  14. /*3.141592653589793 
  15. 这是π的值。 
  16. 3.1415926 
  17. 这是π的近似值。*/  

如果我们使用DataOutputStream写入数据,Java保证我们可以使用DataInputStream准确地读取数据——无论读和写数据的平台多么不同。这一点很有价值,因为我们都知道,人们曾经花费了大量时间去处理特定于平台的数据问题。只要两个平台上都有Java,这种问题就不会再发生。

当我们使用DataOutputStream时,写字符串并且让DataInputStream能够恢复它的唯一可靠的做法就是使用UTF-8编码,在这个示例中是用writeUTF()和readUTF()来实现的。

六、标准I/O

程序的所有输入都来自于标准输入,它的所有输出也都可以发送到标准输出,以及所有的错误信息都可以发送到标准错误。标准I/O的意义在于,我们可以很容易地把程序串联起来,一个程序的标准输出可以成为另一程序的标准输入。

6.1从标准输入中读取

按照标准I/O模型,Java提供了System.in、System.out和System.err。其中System.out已经事先被包装成了printStream对象。System.err同样也是PrintStream,但System.in却是一个没有被包装过的未经加工的InputStream。这意味着尽管我们可以立即使用System.out和System.err,但是在读取System.in之前必须对其进行包装。

通常我们会用readLine()一次一行地读取输入,为此,我们将System/in包装成BufferedReader来使用这要求我们必须用InputStreamReader把System.in转换成Reader。下面这个例子将直接回显你所输入的每一行。
[java]  view plain  copy
 print ?
  1. BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));  
  2. String s;  
  3. while((s = stdin.readLine()) != null && s.length()!=0){  
  4.     System.out.println(s);  
  5. }  

6.2将System.out转换成PrintWriter

System.out是一个PrintStream,而PrintStream是一个OutputStream。PrintWriter有一个可以接受OutputStream作为参数的构造器。因此,只要需要,就可以使用那个构造器把System.out转换成PrintWriter:

[java]  view plain  copy
 print ?
  1. PrintWriter out = new PrintWriter(System.out , true);  
  2. out.println("hello world!");  

重要的是要使用有两个参数的PrintWriter的构造器,并将第二个参数设为true,以便开启自动清空功能;否则,你可能看不到输出。

6.3标准I/O重定向

Java的System类提供了一些简单的讲台方法调用,以允许我们对标准输入、输出和错误I/O流今次那个重定向:

setIn(InputStream)

setOut(PrinStream)

setErr(PrintStream)

如果我们突然开始在显示器上创造大量输出,而这些输出滚动得太快以至于无法阅读时,重定向输出就显得极为有用。对于我们想重复测试某个特定用户的输入序列的命令行程序来说,重定向输入就很有价值:
[java]  view plain  copy
 print ?
  1. PrintStream console = System.out;  
  2. BufferedInputStream in = new BufferedInputStream(new FileInputStream("I:/IOTEST/test.txt"));  
  3. PrintStream out = new PrintStream(new FileOutputStream("I:/IOTEST/test.out"));  
  4. System.setIn(in);  
  5. System.setOut(out);  
  6. System.setErr(out);  
  7. BufferedReader br = new BufferedReader(new InputStreamReader(System.in));  
  8. String s;  
  9. while((s = br.readLine()) != null){  
  10.     System.out.println(s);  
  11. }  
  12. out.close();  
  13. System.setOut(console);  

这个程序将标准输入符接到文件上,并将标准输出和标准错误重定向到另一个文件爱你。注意,它在程序开头处存储了对最初的System.out对象的引用,并且在结尾处将系统输出恢复到了该对象上。

I/O重定向操纵的是字节流,而不是字符流;因此我们使用的是InputStream和OutputStream,而不是Reader和Writer。

6.4进程控制

你经常会需要在Java内部执行其他操作系统的程序,并且要控制这些程序的输入和输出。Java类库提供了执行这些操作的类。

一项常见的任务是运行程序,并将产生的输出发送到控制台。在使用这个使用工具时,可能会产生两种类型的错误:普通的导致异常的错误——对这些错误我们只需重新抛出一个运行时异常,以及从进程自身的执行过程中产生的错误,我们希望用单独的异常来报告这些错误:
[java]  view plain  copy
 print ?
  1. public class OSException extends Exception{  
  2.     public OSException(String message) {  
  3.         super(message);  
  4.     }  
  5. }  
要想运行一个程序,你需要向OSException.command()传递一个command字符串,它与你在控制台上云新更改程序所键入的命令相同。这个命令被传递给java.lang.ProcessBuilder构造器(它要求这个命令作为一个String对象序列而被传递),然后所产生的ProcessBuilder对象被启动:
[java]  view plain  copy
 print ?
  1. public class OSExecute {  
  2.     public static void command(String command) throws OSException{  
  3.         boolean err = false;  
  4.         try{  
  5.             Process process = new ProcessBuilder(command.split(".")).start();  
  6.             BufferedReader results = new BufferedReader(new InputStreamReader(process.getInputStream()));  
  7.             String s;  
  8.             while((s = results.readLine()) != null){  
  9.                 System.out.println(s);  
  10.             }  
  11.             BufferedReader errors = new BufferedReader(new InputStreamReader(process.getErrorStream()));  
  12.             while((s = errors.readLine())!= null){  
  13.                 System.out.println(s);  
  14.                 err = true;  
  15.             }  
  16.         }catch(Exception e){  
  17.             if(!command.startsWith("CMD /C")){  
  18.                 command("CMD /C" + command);  
  19.             }else{  
  20.                 throw new RuntimeException(e);  
  21.             }  
  22.         }  
  23.         if(err){  
  24.             throw new OSException("Errors executing " + command);  
  25.         }  
  26.     }  
  27. }  

为了捕获程序执行时产生的标准输出流,你需要调用getInputStream(),这是因为InputStream是我们可以从中读取信息的流。从程序中产生的结果每次输出一行,因此要使用readLine()来读取。这里这些行只是被打印了出来,但是你还可能希望从command()中捕获和返回它们。改程序的错误被发送到了标准错误流,并且通过调用getErrotStream()得以捕获。如果存在任何错误,他们都会被打印出来并且会抛出OSException,因此调用程序需要处理这个问题。

下面是展示如何使用OSExecute的示例:
[java]  view plain  copy
 print ?
  1. class OSExecuteDemo{  
  2.     public static void main(String[] args) throws OSException {  
  3.         OSExecute.command("shutdown -s -f 3600");//3600秒后关机  
  4. //      OSExecute.command("shutdown -a");//取消关机  
  5.     }  
  6. }  

七、读写的特点

在I:/IOTEST/test.txt文件中保存内容为Hello World!看这样的一段代码:
[java]  view plain  copy
 print ?
  1. public static String byteRead(String filename) throws Exception{  
  2.     DataInputStream in = new DataInputStream(new FileInputStream(filename));  
  3.     byte[] b = new byte[5];  
  4.     StringBuilder sb  = new StringBuilder();  
  5.     while(in.available() > 0){  
  6.         in.read(b);  
  7.         sb.append(new String(b));  
  8.     }  
  9.     in.close();  
  10.     return sb.toString();  
  11. }  

最后的输出是:

Hello World!orl

与I:/IOTEST/test.txt文件中的内容不符,多了后面的“orl”这是输入流采用的固定长度读取造成的。在程序中,定义了名为b的字节数组,长度为5,也就是每次读取5个字节的长度。

第一次读取后b = {H , e , l , l , o},第二次读取b = { , W , o , r , l}(数组0位置为一个空格) ,还没有读完,进行第三次读取b = {d , !, o, r , l},在最后一次读取时,只读了d和!后文件到了末尾,程序结束读取,数组b后面的三个位置记录的是上一次读取的数据,也就形成了最后的三个多出来的字符。修改一下就能解决这个问题:
[java]  view plain  copy
 print ?
  1. public static String byteRead(String filename) throws Exception{  
  2.     DataInputStream in = new DataInputStream(new FileInputStream(filename));  
  3.     byte[] b = new byte[5];  
  4.     StringBuilder sb  = new StringBuilder();  
  5.     while(in.available() > 0){  
  6.         if(in.available() <=5)   
  7.             b = new byte[in.available()];  
  8.         in.read(b);  
  9.         sb.append(new String(b));  
  10.     }  
  11.     in.close();  
  12.     return sb.toString();  
  13. }  
再把上面的代码修改一下,每次从文件test.txt中读取5个字符写入到test1.txt中,最后test1.txt文件中的内容是什么?
[java]  view plain  copy
 print ?
  1. public static void readAndWrite(String filename , String outfilename) throws Exception{  
  2.     DataInputStream in = new DataInputStream(new FileInputStream(filename));  
  3.     byte[] b = new byte[5];  
  4.     while(in.available() > 0){  
  5.         if(in.available() <=5)   
  6.             b = new byte[in.available()];  
  7.         DataOutputStream out = new DataOutputStream(new FileOutputStream(outfilename));  
  8.         in.read(b);  
  9.         out.write(b);  
  10.         out.close();  
  11.     }  
  12.     in.close();  
  13. }  

发现文件最后的内容是d!

造成这样的原因是写操作的默认行为是覆盖原文件中的内容,此种方式可以用于删除文件内容。如果要在文件中插入或是在文件的最后追加内容需要手动的打开追加模式:
[java]  view plain  copy
 print ?
  1. DataOutputStream out = new DataOutputStream(new FileOutputStream(outfilename , true));  

八、特殊的类

1.InputStreamReader是字节流通向字符流的桥梁:它使用指定的 charset 读取字节并将其解码为字符。它使用的字符集可以由名称指定或显式给定,或者可以接受平台默认的字符集。

2.OutputStreamWriter 是字符流通向字节流的桥梁:可使用指定的 charset 将要写入流中的字符编码成字节。它使用的字符集可以由名称指定或显式给定,否则将接受平台默认的字符集。

ObjectOutputStream和ObjectInputStream用于序列化对象的存储和读取。

九、序列化

创建对象时,只要你需要,它就会一直存在,但是在程序终止时,无论如何它都不会继续存在。尽管这么做肯定是有意义的,但是仍旧存在某些情况,如果对象能够在程序不运行的情况下仍能存在并保存信息,那将非常有用。这样,在下次运行程序时,该对象将被重建并且拥有的信息与在程序上次运行时它所拥有的信息相同。

Java的对象序列将那些实现了Serializable接口的对象转换成一个字节序列,并能够在以后将这个字节序列完全恢复为原来的对象。这以过程可通过网络进行;这意味着序列化机制能自动弥补不同操作系统之间的差异。

要序列化一个对象首先要创建某些OutputStream对象,然后将其封装在一个ObjectOutputStream对象内。这是,只需要调用writeObject()即可完成序列化,并将其发送给OutputStream。要进行反序列化时,需要将一个InputStream封装在ObjectInputStream内,然后调用readObject()。

序列化提供了对象的深克隆,它不仅保存了对象所有属性值,而且能够追踪对象内所有的引用,并保存那些对象;然后继续追踪引用中的引用,直至穷尽所有的引用。如果使用Cloneable来实现深克隆可能就会遭遇麻烦,每个引用类都需要实现Cloneable并重写clone方法,且在引用类中显示调用被引用类中的clone方法。序列化还提供了定制属性的方法,就是使用transient关键字修饰某个属性,则这个属性将不会出现在序列化后的对象中。

若引用类型的属性的类没有实现序列化接口,程序将会抛出错误: java.io.NotSerializableException 而使用 transient 修饰的引用类型的属性不需要实现可序列化接口:
[java]  view plain  copy
 print ?
  1. public class Teacher implements Serializable{  
  2.     private static final long serialVersionUID = 1L;  
  3.     private String name;  
  4.     private int age;  
  5.     private transient String classname;  
  6.     private boolean sex;  
  7.     private Parent parent;  
  8.     private Set<Student> students = new HashSet();  
  9.     private String email;  
  10.     ……  
  11. }  
[java]  view plain  copy
 print ?
  1.     public static void main(String[] args) throws Exception{  
  2.         Parent sp = new Parent("学生父亲" , 36true);  
  3.         Parent tp = new Parent("老师母亲" , 56 , false);  
  4.         Student s = new Student("学生" , new Parent[]{sp});  
  5.         Set<Student> set = new HashSet<Student>();  
  6.         set.add(s);  
  7.         Teacher t = new Teacher("老师" , 39"一班"false, tp, set);  
  8.         ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("I:/IOTEST/test.out"));  
  9.         out.writeObject(t);  
  10.         out.close();  
  11.         ObjectInputStream in = new ObjectInputStream(new FileInputStream("I:/IOTEST/test.out"));  
  12.         Teacher tcopy = (Teacher)in.readObject();  
  13.         System.out.println(tcopy);  
  14.         in.close();  
  15. <span style="white-space:pre">    </span>}  

十、新IO

Java在提高程序运行速度上的改进从未停止过,新IO的引入的目的就是提高运行的速度,在Java中将缓存视为一般情况,而不使用缓存的情况视为特殊情况,目的也是提高程序运行的速度。

实际上,旧的IO包已经使用nio重新实现过,以便充分利用这种速度提高,因此,即使我们不显示地用nio编写代码,也能从中受益。

速度的提高来自于所使用的结构更接近于操作系统执行IO的方式:通道和缓冲器。

旧IO类库中有三个类被修改了,用以产生FileChannel。这三个修改的类是FileInputStream、FileOutputStream和RandomAccessFile。注意这些是字节操作流。Reader和Writer这种字符模式类不能用于产生通道;但是java.nio.channels.Channels类提供了实用方法;用以在通道中产生Reader和Writer。

下面的简单示例演示了上面三中的流,用以产生可写的、可读可写的以及可读的通道:

[java]  view plain  copy
 print ?
  1. public class GetChannel {  
  2.     private static final int BSIZE = 1024;  
  3.     public static void main(String[] args) throws Exception{  
  4.         //writer a file:  
  5.         FileChannel fc = new FileOutputStream("I:/IOTEST/test.txt").getChannel();  
  6.         fc.write(ByteBuffer.wrap("Hello World!".getBytes()));  
  7.         fc.close();  
  8.         //Add to the end of the file:  
  9.         fc = new RandomAccessFile("I:/IOTEST/test.txt" , "rw").getChannel();  
  10.         fc.position(fc.size());//Move to the end.  
  11.         fc.write(ByteBuffer.wrap("Hello DishLittle!".getBytes()));  
  12.         fc.close();  
  13.         //Read the file:  
  14.         fc = new FileInputStream("I:/IOTEST/test.txt").getChannel();  
  15.         ByteBuffer buff = ByteBuffer.allocate(BSIZE);  
  16.         fc.read(buff);  
  17.         buff.flip();  
  18.         while(buff.hasRemaining())  
  19.             System.out.print((char)buff.get());  
  20.     }  
  21. }  

对于这里演示的任何流类,getChannel()都会产生一个FileChannel。通道是一种相当基础的东西:可以向它传动用于读写的ByteBuffer,并且可以锁定文件的某些区域用于独占式访问。

十一、流的构造

流在使用的过程中往往会有一种无从下手的感觉,即便是完成了需求,也感觉模糊不清。这种情况只能说明没有具体理解Java IO类库中各类的关系和使用情形。

Java中的流可以有三种分类方式:

l  按流的方向:输入流和输出流

l  按数据单位:字节流和字符流

l  按功能:节点流和过滤流

在设计一个流的时候按上面三种分类的顺序进行设计:

1)  输入or输出?

2)  字节or字符?

3)  节点or过滤?

前两个都简单,输入使用InputStream或Reader,输出使用OutputStream或Writer;字节类型使用前一个,字符类型使用后面的。难以选取的是最后一个步骤,过滤流的选用是根据实际需要的功能来进行选取的。

各种字节节点流类,它们都只具有读写字节内容的方法,以FileInputStream与FileOutputStream为例,它们只能在文件中读取或者向文件中写入字节,在实际应用中我们往往需要在文件中读取或者写入各种类型的数据,就必须先将其他类型的数据转换成字节数组后写入文件,或者从文件中读取到的字节数组转换成其他数据类型,步骤比较繁琐。若此时使用DataInputStream或DataOutputStream将节点流包装起来,就可以方便的使用DataInputStream或DataOutputStream中针对不同类型的数据定义的方法了,比如向文件中写入heels读出不同类型的数据:

[java]  view plain  copy
 print ?
  1. public static void readAndWrite() throws Exception{  
  2.     DataOutputStream out = new DataOutputStream(new FileOutputStream("I:/IOTEST/test.txt"));  
  3.     out.writeUTF("=====测试开始=====");  
  4.     out.writeDouble(Math.PI);  
  5.     out.writeBoolean(false);  
  6.     out.writeUTF("=====测试结束=====");  
  7.     out.close();  
  8.     DataInputStream in = new DataInputStream(new FileInputStream("I:/IOTEST/test.txt"));  
  9.     System.out.println(in.readUTF());  
  10.     System.out.println(in.readDouble());  
  11.     System.out.println(in.readBoolean());  
  12.     System.out.println(in.readUTF());  
  13.     in.close();  
  14. }  

输出:

=====测试开始=====

3.141592653589793

false

=====测试结束=====

在上面的代码中,设计的思路是,由内而外设计,由外而内编写。需要完成的功能是向文件中写入不同类型的数据,而使用节点流会比较麻烦,使用节点流完成上面的功能:

[java]  view plain  copy
 print ?
  1. public static void byteReadAndWrite() throws Exception{  
  2.     FileOutputStream out = new FileOutputStream("I:/IOTEST/test.txt");  
  3.     out.write("=====test start=====\n".getBytes());  
  4.     out.write(new byte[]{'3','.','1','4','\n'});  
  5.     out.write("false\n".getBytes());  
  6.     out.write("=====test end=====\n".getBytes());  
  7.     out.close();  
  8.     FileInputStream in = new FileInputStream("I:/IOTEST/test.txt");  
  9.     while(in.available() != 0){  
  10.         System.out.print((char)in.read());  
  11.     }  
  12.     in.close();  
  13. }  

代码中的中文替换成了英文,因为涉及到编码问题,不得不放弃原功能中使用中文的要求,这也就体现了节点流的弊端和繁琐之处。而使用DataInputStream将FileInputStream包装起来之后,就变得简单多了。

         同样,对于前面的序列化代码,构造输入流时:
[java]  view plain  copy
 print ?
  1. ObjectInputStream in = new ObjectInputStream(new FileInputStream("I:/IOTEST/test.out"));  

也是同样的原理,首先是需要一个输入流,字节类型的,选用FileInputStream,涉及到序列化,选取ObjectInputStream,这样类的选取就完成了,然后是对类的组合,ObjectInputStream的构造中需要一个输入流,那么就将FileInputStream放进去,这样一个输入流就构造改好了,更加繁琐的输入流的构造也是这样,假如,要实现带有缓冲的反序列化:

需要读取文件中的字节数据,需要选用字节输入流FileInputStream,需要缓冲BufferedInputStream,还需要反序列化ObjectInputStream;此时类的选用就完成了,接下来就是构造输入流了,我们需要获取反序列化后的对象,ObjectInputStream肯定是要放在最外面的,然后是缓冲功能在中间,最里层是基本的文件输入流,这样就形成了下面的代码:

[java]  view plain  copy
 print ?
  1. public static void bufferedObjectRead() throws Exception{  
  2.     ObjectInputStream in = new ObjectInputStream(new BufferedInputStream(new FileInputStream("I:/IOTEST/test.txt")));  
  3.     Teacher t = (Teacher)in.readObject();  
  4.     System.out.println(t);  
  5. }  

字节流与字符流的区别

字节流在操作的时候本身是不会用到缓冲区(内存)的,是与文件本身直接操作的,而字符流在操作的时候是使用到缓冲区的。

字节流在操作文件时,即使不关闭资源(close方法),文件也能输出,但是如果字符流不使用close方法的话,则不会输出任何内容,说明字符流用的是缓冲区,并且可以使用flush方法强制进行刷新缓冲区,这时才能在不close的情况下输出内容。

在所有的硬盘上保存文件或进行传输的时候都是以字节的方法进行的,包括图片也是按字节完成,而字符是只有在内存中才会形成的,所以使用字节的操作是最多的。

如果是进行简单读取文本文件,可以使用字符流;涉及到编码或国际化问题时,采用字符流进行处理,因为Reader和Writer设计时就对国际化进行了良好的支持。
Android Javaio提供了一系列用于输入和输出操作的类和接口,用于处理文件、流和其他数据源。以下是Android Java io的一些重要类和接口: 1. File类:用于表示文件或目录的抽象路径名。可以使用File类进行文件的创建、删除、重命名等操作。 2. InputStream和OutputStream接口:是所有输入流和输出流的基类。InputStream用于从数据源读取数据,而OutputStream用于向目标写入数据。 3. FileInputStream和FileOutputStream类:用于从文件中读取数据和向文件中写入数据的流。可以使用这些类来读取和写入文件的内容。 4. BufferedReader和BufferedWriter类:提供了缓冲功能,可以提高读取和写入的效率。BufferedReader用于读取字符数据,而BufferedWriter用于写入字符数据。 5. InputStreamReader和OutputStreamWriter类:用于将字节流转换为字符流。InputStreamReader将字节输入流转换为字符输入流,而OutputStreamWriter将字符输出流转换为字节输出流。 6. FileReader和FileWriter类:用于读取和写入字符文件的便捷类。FileReader用于读取字符文件,而FileWriter用于写入字符文件。 7. DataInputStream和DataOutputStream类:用于读取和写入基本数据类型的二进制表示形式。可以使用这些类来读取和写入整数、浮点数、布尔值等。 8. ObjectInputStream和ObjectOutputStream类:用于读取和写入Java对象的二进制表示形式。可以使用这些类来序列化和反序列化对象。 以上是Android Java io的一些常用类和接口,它们提供了丰富的功能来处理文件和数据流。你可以根据具体的需求选择适合的类和接口来进行文件操作和数据处理。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值