Java网络编程 流

来源:互联网 发布:itunes软件安装目录 编辑:程序博客网 时间:2024/06/15 20:37

流的概述

网络程序的很大一部分工作都是简单的输入和输出(I/O),而Java的I/O建立于流(stream)。

  • 输入流读取数据,输出流写入数据。
  • 过滤器(filter)流可以串链到输入流或输出流上。读/写数据时,过滤器流可以修改数据(例如,通过加密或压缩),或只是提供额外的方法,将读/写的数据转换为其他格式。
  • 阅读器(reader)和书写器(writer)可以串链到输入流和输出流上,允许程序读/写文本(字符)而不是字节。

注意:这种划分不是互斥的,比如过滤器可能串链输入输出流,而阅读器可能是纯粹的阅读器,也可以是有过滤性质的阅读器。

流是同步的,即当程序(确切地讲是线程)请求一个流读/写一段数据时,在做任何其他操作前,它要等待所读/取的数据,即要发生阻塞。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

当你知道如何使用这些超类,也就知道如何使用它的子类。

  1. OutputStream的基本方法是write(int b),写入1字节数据。这个方法接受一个0到255之间的整数作为参数。

    注意,虽然这个方法接受一个int作为参数,但它实际上会写入一个无符号字节。Java没有无符号字节数据类型,所以这里要使用int来代替。

  2. 如果有多字节要发送,则一次全部发送不失为一个好主意。使用write(byte[] data)write(byte[] data, int offset, int length)通常比一次写入data数组中的1字节要快得多。

  3. 除了网络硬件缓存,流还可以在软件中得到缓冲,即直接用Java代码缓存。一般说来,这可以通过把BufferedOutputStream或BufferedWriter串链到底层上来实现。因此,在写入数据完成后,刷新(flush())输出流非常重要。

    • 如果输出流有一个1024字节的缓冲区,那么这个流在发送缓冲区中的数据之前会等待更多的数据到大。需要刷新输出时如果未能做到,那么会导致不可预知、不可重现的程序挂起。
    • 应当在关闭流之前立即刷新输出所有流。否则,关闭时留在缓冲区中的数据可能会丢失。
  4. 在一个长时间运行的程序中,如果未能关闭(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

子类的实例可以透明地作为其超类的实例来使用,即多态的作用。

  1. 基本方法read()从输入流的源中读取1字节数据。正常情况会返回一个int结果,因此你可能需要把0到255之间的一个有符号字节转换为无符号字节。而流的结束通过返回-1来表示。read()会阻塞其后面任何代码的执行,直到有1字节的数据可供读取。
  2. 可以从流中读取的多字节的数据填充一个指定的数组:read(byte[] input)read(byte[] input, int offset, int length)。第一个方法尝试填充指定的数组input。第二个方法尝试填充指定的input中从offset开始连续length字节的子数组。读取多字节的方法返回实际读取的字节数(而不会马上阻塞等待后面的字节),而流的结束也通过返回-1来表示。但如果length是0,那么它不会注意流的结束,而是返回0。

    循环读取可能会产生一个bug,这种情况是因为没有考虑数组所有字节永远不会到达的情况。因此你需要判断返回是否-1来跳出循环读取,或通过available()方法来确定不阻塞的情况下有字节可以读取。

  3. 如果不想等待所需的全部字节都立即返回,可以使用available()方法来确定不阻塞的情况下有字节可以读取。在流的最后,available()会返回0。

  4. 在少数情况下,你可能希望跳过数据不进行读取。skip()方法会完成这项任务。与读取文件相比,在网络连接中它的用处不大。网络连接是顺序的,一般情况下很慢,所以与跳过数据(不读取)相比,读取数据并不会耗费太长时间。

  5. 与输出流一样,一旦结束对输入流的操作,应当调用它的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

警告:PrintStream是有害的,网络程序员应当像躲避瘟疫一样避开它!

  1. 第一个问题是Println()的输出是与平台有关的。根据系统不同,分隔的符号不一样。
  2. 第二个问题是PrintSteam假定使用所在平台的默认编码方式。
  3. 第三个问题是PrintStream吞掉了所有异常。

数据流

DataInputStream和DataOutputStream类提供了一些方法,可以用二进制格式读/写Java的基本数据类型和字符串。

DataOutputStream

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

最后三个方法需要注意一下:

  1. writeChars()写入2字节的字符,但writeBytes()方法写入每个字符的低位字节。writeChars和wirteBytes都不会对输出流的字符串的长度编码,因此你无法真正区分原始字符和作为字符串一部分的字符。
  2. writeUTF()方法则包括了字符串的长度,由于与大多数非Java软件有点不兼容,所以应当只用于与其他使用DataInputStream读取字符串的Java程序进行数据交换。为了与所有其他软件交换UTF-8文本,应当使用有适当编码的InputStreamReader,而不是writeUTF()readUTF()

所有数据都以big-endian格式写入。

DataInputStream

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

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

InputStreamReader是Reader的最重要的具体子类。它根据指定的编码方式将这些字节转换为字符。

    public InputStreamReader(InputStream in)    public InputStreamReader(InputStream in, String encoding) throws UnsupportedEncodingException

构造函数指定要读取的输入流和所用的编码方式,如果没有指定,就使用平台默认的编码方式。

过滤器阅读器和书写器

我们还可以对阅读器和书写器进行过滤,形成过滤阅读器和书写器。
有很多子类可以完成特定的字符过滤工作:

  • BufferedReader
  • BufferedWriter
  • LineNumberReader
  • PushbackReader
  • PrintWriter

BufferedReader和BufferedWriter

    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
  1. 如果没有设置大小,则使用默认的大小8192字符。
  2. newLine()向输出插入一个与平台有关的行分隔符字符串,由于网络协议一般会指定所需的行结束符,所以网络编程中不要使用这个方法,而应当显式地写入协议所需的行结束符。大多数情况下,所需的结束符都是回车/换行对。

PrintWriter

PrintWriter类用于取代PrintSteam类,它能正确地处理多字节集和国际化文本。但对于网络编程来说,仍然不太适合。很遗憾,PrintWriter也存在困扰PrintStream类的平台依赖性和错误报告信息量小等问题。

总结:对于网络编程,传输字节常用的应该是BufferedOutputStream和BufferedInputStream。传输字符常用的应该是BufferedReader和BufferedWriter。

1 0
原创粉丝点击