标签:
网络程序的很大一部分工作都是简单的输入和输出(I/O),而Java的I/O建立于流(stream)。
注意:这种划分不是互斥的,比如过滤器可能串链输入输出流,而阅读器可能是纯粹的阅读器,也可以是有过滤性质的阅读器。
流是同步的,即当程序(确切地讲是线程)请求一个流读/写一段数据时,在做任何其他操作前,它要等待所读/取的数据,即要发生阻塞。Java还支持非阻塞I/O,非阻塞I/O要快得多,在后面的博文再谈论该用法。
OutputSteam的具体子类用于向某种特定介质写入数据。
Java的基本输出流是java.io.OutputSteam
:
public abstract class OutputStream
基本方法:
public abstract void write(int b) throws IOException
public void write(byte[] data) throws IOException
public void write(byte[] data, int offset, int length) throws IOException
public void flush() throws IOException
public void close() throws IOException
当你知道如何使用这些超类,也就知道如何使用它的子类。
OutputStream的基本方法是write(int b)
,写入1字节数据。这个方法接受一个0到255之间的整数作为参数。
注意,虽然这个方法接受一个int作为参数,但它实际上会写入一个无符号字节。Java没有无符号字节数据类型,所以这里要使用int来代替。
如果有多字节要发送,则一次全部发送不失为一个好主意。使用write(byte[] data)
或write(byte[] data, int offset, int length)
通常比一次写入data数组中的1字节要快得多。
除了网络硬件缓存,流还可以在软件中得到缓冲,即直接用Java代码缓存。一般说来,这可以通过把BufferedOutputStream或BufferedWriter串链到底层上来实现。因此,在写入数据完成后,刷新(flush()
)输出流非常重要。
在一个长时间运行的程序中,如果未能关闭(close()
)一个流,则可能会泄露文件句柄。
在Java6和更早版本中,解决做法是在一个finally块中关闭流。这个技术称为释放模式(dispose pattern),不仅适用于流,还可以用于socket、通道、JDBC连接和语句。
Java7引入了”带资源的try“构造(try with rsources),可以更简洁地完成流的清理:
try(OutputStream out = new FileOutputStream( "/tmp/data.txt" )) {
//处理输出流...
}catch(IOException ex){
System.err.println(ex.getMessage());
}
在一个长时间运行的程序中,如果未能关闭一个流,则可能会泄露文件句柄、网络端口和其他资源。现在不再需要finally子句来完成清理,Java会try块参数表中声明的所有AutoCloseable对象自动调用close()
。
InputStream的具体子类从某种特定介质中读取数据。
Java的基本输入流是java.io.InputStream
:
public abstract class InputStream
基本方法:
public abstract void read() throws IOException
public void read(byte[] input) throws IOException
public void read(byte[] input, int offset, int length) throws IOException
public void available() throws IOException
public void close() throws IOException
子类的实例可以透明地作为其超类的实例来使用,即多态的作用。
read()
从输入流的源中读取1字节数据。正常情况会返回一个int结果,因此你可能需要把0到255之间的一个有符号字节转换为无符号字节。而流的结束通过返回-1来表示。read()会阻塞其后面任何代码的执行,直到有1字节的数据可供读取。可以从流中读取的多字节的数据填充一个指定的数组:read(byte[] input)
和read(byte[] input, int offset, int length)
。第一个方法尝试填充指定的数组input。第二个方法尝试填充指定的input中从offset开始连续length字节的子数组。读取多字节的方法返回实际读取的字节数(而不会马上阻塞等待后面的字节),而流的结束也通过返回-1来表示。但如果length是0,那么它不会注意流的结束,而是返回0。
循环读取可能会产生一个bug,这种情况是因为没有考虑数组所有字节永远不会到达的情况。因此你需要判断返回是否-1来跳出循环读取,或通过
available()
方法来确定不阻塞的情况下有字节可以读取。
如果不想等待所需的全部字节都立即返回,可以使用available()
方法来确定不阻塞的情况下有字节可以读取。在流的最后,available()
会返回0。
在少数情况下,你可能希望跳过数据不进行读取。skip()
方法会完成这项任务。与读取文件相比,在网络连接中它的用处不大。网络连接是顺序的,一般情况下很慢,所以与跳过数据(不读取)相比,读取数据并不会耗费太长时间。
与输出流一样,一旦结束对输入流的操作,应当调用它的closes()
方法将其关闭。
InputStream类还有3个不太常用的方法,为了重新读取数据,要用mark()方法标记流的当前位置。在以后某个时刻,可以用reset()方法把流重置到之前标记的位置。这些方法是:
public void mark(int readAdeadLimit)
public void reset() throws IOException
public boolean markSupported()
不是所有输入流都支持标记,在尝试使用标记和重置之前,要用markSuppoerted()
方法检查返回是否为true。java.io中仅有两个始终支持标记的输入流是BufferedInputStream和ByteArrayInputStream。而其它输入流如果先串链到缓冲的输入流时才支持标记。
Java提供了很多过滤器流,可以附加到原始流中,在原始字节和各种格式之间来回转换。
将过滤器串联在一起
- 过滤器同其构造函数与流连接。
- 过滤器无法与流断开连接。
- 有时可能需要会使用链中多个过滤器的额外方法,这种情况需要保存和使用各个底层流的引用。
- 应当只使用链中最后一个过滤器进行实际的读/写,无论如何你都不应该从其他的过滤器中读取数据。
BufferedOutputSteam和BufferedInputSteam都有两个构造函数:
public BufferedInputStream(InputStream in)
public BufferedInputStream(InputStream in, int bufferSize)
public BufferedOutputStream(OutputStream out)
public BufferedOutputStream(OutputStream out, int bufferSize)
第一个参数是底层流。第二个参数指定缓冲区中的字节数。否则输入流的缓冲区大小设置为2048字节,输出流的缓冲区大小设置为512字节。
警告:PrintStream是有害的,网络程序员应当像躲避瘟疫一样避开它!
Println()
的输出是与平台有关的。根据系统不同,分隔的符号不一样。DataInputStream和DataOutputStream类提供了一些方法,可以用二进制格式读/写Java的基本数据类型和字符串。
DataOutputStream类提供了下面11种方法,可以写入特定的Java数据类型:
public final void writeBoolean(boolean v) throws IOException
public final void writeByte(int v) throws IOException
public final void writeBytes(String s) throws IOException
public final void writeChar(int v) throws IOException
public final void writeChars(String s) throws IOException
public final void writeDouble(double v) throws IOException
public final void writeFloat(float v) throws IOException
public final void writeInt(int v) throws IOException
public final void writeLong(long v) throws IOException
public final void writeShort(int v) throws IOException
public final void writeUTF(String str) throws IOException
最后三个方法需要注意一下:
writeChars()
写入2字节的字符,但writeBytes()
方法写入每个字符的低位字节。writeChars和wirteBytes都不会对输出流的字符串的长度编码,因此你无法真正区分原始字符和作为字符串一部分的字符。writeUTF()
方法则包括了字符串的长度,由于与大多数非Java软件有点不兼容,所以应当只用于与其他使用DataInputStream读取字符串的Java程序进行数据交换。为了与所有其他软件交换UTF-8文本,应当使用有适当编码的InputStreamReader,而不是writeUTF()
和readUTF()
。所有数据都以big-endian格式写入。
DataInputStream提供了9个对应的方法(writeBytes()
和writeChars()
没有相应的读取方法,这要通过一次读取1字节和字符来处理):
public final boolean readBoolean() throws IOException
public final boolean readByte() throws IOException
public final boolean readChar() throws IOException
public final boolean readShort() throws IOException
public final boolean readInt() throws IOException
public final boolean readLong() throws IOException
public final boolean readFloat() throws IOException
public final boolean readDouble() throws IOException
public final boolean readUTF() throws IOException
此外,DataInputStream提供了两个方法,可以读取无符号字节和无符号短整数,并返回等价的int。Java没有这些数据类型,但在读取C程序写入的二进制数据时会遇到:
public final int readUnsignedByte() throws IOException
public final int readUnsignedShort() throws IOException
DataInputStream还有通常的多字节read()方法,可把数据读入一个数组,并返回读取的字节数。
public final int read(byte[] input) throws IOException
public final int read(byte[] intput, int offset, int length) throws IOException
public final void readFully(byte[] input) throws IOException
public final void readFully(byte[] input, int offset, int length) throws IOException
readFully会重复地从底层输入流向一个数组读取数据,直到读取了所请求的字节数为止。如果不能读取都足够的数据,就会抛出IOException异常。
最后DataInputStream还提供了流行的readLine()方法:
public final String readLine() throws IOException
不过,任何情况下都不要使用这个方法,不仅是因为它已被废弃,而且它还有bug,是因为在大多数情况下它不能正确地将非ASCII字符转换为字节。这个任务现在由BufferedReader类的readLine()
方法来处理,不过这两个方法都存在同一个隐含的bug:它们并不总能把一个回车(\r)识别为行结束。
Java提供了一个基本上完整的镜像,用来处理字符而不是字节。
Reader和Writer最重要的具体子类是InputStreamReader和OutputStreamReader。
Writer类是java.io.OutputStream
类的映射,与OutputStream类似,Writer类从不直接使用;相反,会通过它的某个子类以多态方式使用。
protected Writer()
protected Writer(Object lock)
public abstract void write(char[] text, int offset, int length) throws IOException
public void write(int c) throws IOException
public void write(char[] text) throws IOException
public void write(String s) throws IOException
public void write(String s, int offset, int length) throws IOException
public abstract void flush() throws IOException
public abstract void close() throws IOException
书写器可以缓冲,有可能直接串链到BufferedWriter,也有可能间接链入。
OutputStreamWriter是Writer的最重要的具体子类。它根据指定的编码方式将这些字符转换为字节,并写入底层输出流。
public OutputStreamWriter(OutputStream out, String encoding) throws UnsupportedEncodingException
还有一个返回对象的编码方式的方法:
public String getEncoding()
Reader类是java.io.InputStream
类的镜像。与InputStream和Writer类似,Reader类从不直接使用,只通过其子类来使用。
protected Reader()
protected Reader(Object lock)
public abstract int read(char[] text, int offset, int length)
public abstract int read() throws IOException
public abstract int read(char[] text) throws IOException
public boolean ready()
public void mark(int readAdeadLimit)
public void reset() throws IOException
public boolean markSupported()
public void close() throws IOException
Reader类有一个ready()
方法,它与InputStream的available()
的用途相同,但语义不尽相同。ready()只返回一个boolean,指示阅读器是否可以无限地阻塞。问题在于,编码格式不同,导致字符数量不同,因此在实际缓冲区读取之前,很难说有多少个字符在缓冲区等待。
InputStreamReader是Reader的最重要的具体子类。它根据指定的编码方式将这些字节转换为字符。
public InputStreamReader(InputStream in)
public InputStreamReader(InputStream in, String encoding) throws UnsupportedEncodingException
构造函数指定要读取的输入流和所用的编码方式,如果没有指定,就使用平台默认的编码方式。
我们还可以对阅读器和书写器进行过滤,形成过滤阅读器和书写器。
有很多子类可以完成特定的字符过滤工作:
public BufferedReader(Reader in, int bufferSize)
public BufferedReader(Reader in)
public BufferedWriter(Writer out)
public BufferedWriter(Writer out, int bufferSize)
public void newLine() throws IOException
newLine()
向输出插入一个与平台有关的行分隔符字符串,由于网络协议一般会指定所需的行结束符,所以网络编程中不要使用这个方法,而应当显式地写入协议所需的行结束符。大多数情况下,所需的结束符都是回车/换行对。PrintWriter类用于取代PrintSteam类,它能正确地处理多字节集和国际化文本。但对于网络编程来说,仍然不太适合。很遗憾,PrintWriter也存在困扰PrintStream类的平台依赖性和错误报告信息量小等问题。
总结:对于网络编程,传输字节常用的应该是BufferedOutputStream和BufferedInputStream。传输字符常用的应该是BufferedReader和BufferedWriter。
标签:
原文地址:http://blog.csdn.net/sinat_24229853/article/details/51960125