1.输入/输出流
在Java API中,可以从其中读入一个字节序列的对象称做输入流,而可以向其中写入一个字节数列的对象称作输出流。
这些字节序列的来源地和目的地可以是文件,而且通常都是文件,但是也可以是网络连接,甚至是内存块。
抽象类InputStream和OutputStream构成了输入/输出类层次结构的基础。
因为面向字节的流不便于处理以Unicode形式存储的信息(回忆一下,Unicode中每个字符都使用了多个字节来表示),所以从抽象类Reader和Writer中继承出来了一个专门用于处理Unicode字符的单独的类层次结构。这些类拥有的读入和写出操作都是基于两字节的Char值的(即,Unicode码元),而不是基于byte值的。
1)读写字节
InputStream类有一个抽象方法:
abstract int read()
这个方法将读入一个字节,并返回读入的字节,或者在遇到输入源结尾时返回-1。
在设计具体的输入流类时,必须覆盖这个方法以提供适用的功能,例如,在FIleInputStream类中,这个方法将从某个文件中读入一个字节,而System.in(它是InputStream的一个子类的预定义对象)却是从“标准输入”中读入信息,即控制台或重定向文件。
InputStream类还有若干个非抽象的方法,它们可以读入一个字节数组,或者跳过大量的字节。这些方法都要调用抽象的read方法,因此,各个子类都只需覆盖这一个方法。
OutputStream类定义了下面的抽象方法:
abstract void write(int b)
它可以向某个输出位置写出一个字节。
read和write方法在执行时都将堵塞,直至字节确实被读入或写出。这就意味着如果流不能被立即访问,那么当前的线程将被阻塞。这使得在这两个方法等待指定的流变为可用的这段时间里,其他的线程就有机会去执行有用的工作。
available方法使我们可以去检查当前可读入的字节数量,这意味着像下面这样的代码片段就不可能被阻塞。
int byteAvailable =in.available(); if(byteAvailable >0){ byte[] data=new byte[bytesAvailable]; in.read(data); }
当你完成对输入/输出流的读写时,应该通过调用close方法来关闭它,这个调用会释放掉十分有限的操作系统资源。
关闭一个输出流的同时还会冲刷用于该输出流的缓冲区:所有被临时置于缓冲区中,以便用更大的包的形式传递的字节在关闭输出流时都将被送出。特别是,如果不关闭文件,那么写出字节的最后一个包可能将永远也得不到传递。当然,我们还可以用flush方法认为地冲刷这些输出。
java.io.InputStream:
abstract int read()
从数据中读入一个字节,并返回该字节。这个read方法在碰到输入流的结尾时返回-1.
int read(byte[] b)
读入一个字节数组,并返回实际读入的字节数,或者在碰到输入流的结尾时返回-1.这个read方法最多读入b.length个字节。
int read(byte[] b,int off, int len)
读入一个字节数组。这个read方法返回实际读入的字节数,或者在碰到输入流的结尾时返回-1.
long skip(long n)
在输入流中跳过n个字节,返回实际跳过的字节数(如果碰到输入流的结尾,则可能小于n)。
int available()
返回在不阻塞的情况下可获取的字节数(回忆一下,阻塞意味着当前线程失去它对资源的占用)。
void close()
关闭这个输入流。
void mark(int readlimit)
在输入流的当前位置打一个标记(并非所有流都支持这个特性)。如果从输入流中已经读入的字节多于readlimit个,则这个流允许忽略这个标记。
void reset()
返回到最后一个标记,随后对read的调用将重新读入这些字节。如果当前没有任何标记,则这个流不被重置。
boolean markSupported()
如果这个流支持打标记,则返回true。
java.io.OutputStream:
abstract void write(int n)
写出一个字节的数据
void write(byte[] b)
void write(byte[] b,int off,int len)
写出所有字节或者某个范围的字节到数组b中。
void close()
冲刷并关闭输出流
void flush()
冲刷输出流,也就是将所有缓冲的数据发送到目的地。
2)完整的流家族
还有4个附加的接口:Closeable,Flushable,Radable和Appendable。
前两个接口非常简单,它们分别拥有下面的方法:
void close() throws IOException
和
void flush()
InputStream,OutputStream,Reader和Writer都实现了Closeable接口。
注:java.io.Closeable接口扩展了java.lang.AutoCloseable接口。因此,对任何Closeable进行操作时,都可以使用try-with-resource语句。
而OutputStream和Writer还实现了Flushable接口。
Readable接口只有一个方法:
int read(CharBuffer cb)
CharBuffer类拥有按顺序和随机地进行读写访问的方法,它表示一个内存中的缓冲区或者一个内存映像的文件。
Appendable接口有两个用于添加单个字符和字符序列的方法:
Appendable append(char c)
Appendable append(CharSequence s)
CharSequence接口描述了一个char值序列的基本属性,String,CharBuffer,StringBuilder和StringBuffer都实现了它。
在流类的家族中,只有Writer实现了Appendable。
3)组合输入/输出流过滤器
FileInputStream和FileOutputStream可以提供附着在一个磁盘文件上的输入流和输出流,而你只需向其构造器提供文件名或文件的完整路径名。例如:
FileInputStream fin=new FileInputStream("employee.dat");
这行代码可以查看在用户目录下名为"employee.dat"的文件。
注:由于反斜杠字符在Java字符串中是转义字符,因此要确保在Windows风格的路径中使用\(例如,C:\Windows\win.ini)。在Windows中,还可以使用单斜杠字符(C:/WIndows/win.ini),因为大部分Windwos文件处理的系统调用都会将以斜杠解释成文件分隔符。但是,并不推荐这样做,因此,对于可移植的程序来说,应该使用程序所运行平台的文件分隔符,我们可以通过常量字符串java.io.File.separator获得它。
与抽象类InputStream和OutputStream一样,这些类只支持在字节级别上的读写。也就是说,我们只能从fin对象中读入字节和字节数组。
如果我们只有DataInputStream,那么我们就只能读入数值类型。但是正如FIleInputStream没有任何读入数值类型的方法一样,DataInputStream也没有任何从文件中获取数据的方法。
Java使用了一种灵巧的机制来分离这两种职责。某些输入流(例如FileInputStream和由URL类的openStream方法返回的输入流)可以从文件和其他更外部的位置上获取字节,而其他的输入流(如DataInputStream)可以将字节组装到更有用的数据类型中。
Java程序员必须对二者进行组合。例如,为了从文件中读入数字,首先需要创建一个FileInputStream,然后将其传递给DataInputStream的构造器:
FileInputStream fin=new FileInputStream("employee.dat"); DataInputStream din=new DataInputStream(fin); double x=din.readDouble();
FilterInputStream和FilterOutputStream类的子类用于向处理字节的输入/输出流添加额外的功能。
你可以通过嵌套过滤器来添加多重功能。例如,输入流在默认情况下是不被缓冲区缓存的,也就是说,每个对read的调用都会请求操作系统再分发一个字节。相比之下,请求一个数据块并将其置于缓冲区中会显得更加高效。如果我们想使用缓冲机制,以及用于文件的数据输入方法,那么就需要使用下面这种构造器序列:
DataInputStream din=newDataInputStream( new BufferedInputStream( new FileInputStream("employee.dat")));
注意,我们把DataInputStream置于构造器链的最后,这是因为我们希望使用DataInputStream的方法,并且希望它们能够带缓冲机制的read方法。
有时当多个输入流链接在一起时,你需要跟踪各个中介输入流(intermediate input stream)。例如,当读入输入时,你经常需要预览下一个字节,以了解它是否是你想要的值。Java提供了用于此目的的PushbackInputStream:
PushbackInputStream pbin=new PushbackInputStream( new BufferedInputStream( new FileInputStream("employee")));
现在你可以预读下一个字节:
int b=pbin.read();
并且在它并非你所期望的值时将其推回流中。
if(b!='<') pbin.unread(b);
但是读入和推回是可应用于可回推(pushback)输入流的仅有的方法。如果你希望能够预先浏览并且还可以读入数字,那么你就需要一个既是可回推输入流,又是一个数据输入流的引用。
DataInputStream din=new DataInputStream( pbin=new PushbackInputStream( new BufferedInputStream( new FileInputStream("employee.dat"))));
2.文本输入与输出
在保存数据时,可以选择二进制格式或文本格式。
在存储文本字符串时,需要考虑字符编码(character encoding)方式。在Java内部使用的UTF-16编码方式。但是,许多程序都希望文本按照其他的编码方式编码。在UTF-8这种在互联网上最常用的编码方式。
OutputStreamWriter类将使用选定的字符编码方式,把Unicode码元的输出流转换为字节流。而InputStreamReader类将包含字节(用某种字符编码方式表示的字符)的输入流转换为可以产生Unicode码元的读入器。
例如,下面的代码就展示了如何让一个输入读入器可以从控制台读入键盘敲击信息,并将其转换为Unicode:
Reader in=new InputStreamReader(System.in);
这个输入流读入器会假定使用主机系统的默认字符编码方式。你应该总是在InputStreamReader的构造器中选择一种具体的编码方式。例如。
Reader in=new InputStreamReader(new FileInputStream("data.txt"),StandardCharsets.UTF_8);
1)如果写出文本输出
对于文本输出,可以使用PrintWriter。这个类拥有以文本格式打印字符串和数字的方法,它还有一个将PrintWriter链接到FileWriter的便捷方法,下面的语句: