Java网络编程的Java流介绍

前言

网络程序所做的很大一部分工作都是简单的输入输出:将数据字节从一个系统移动到另一个系统。Java的I/O建立于流(stream)之上。输入流读取数据,输出流写入数据。过滤器流(filter)流可以串联到输入或输出流上。读写数据时过滤器可以修改数据(加密或压缩),或者只是提供额外的方法,将读/写的数据转换为其他格式。阅读器(reader)和书写器(writer)可以串链到输入流和输出流上,允许程序读/写文本而不是字节。

输出流

Java的基本输出流类是:java.io.OutputStream;

这个类中提供了写入数据所需的基本方法,如下:

public abstract void write(int b) throws IOException;
public void write(byte b[]) throws IOException
public void write(byte b[], int off, int len) throws IOException
public void flush() throws IOException
public void close() throws IOException

但是我们平时使用它的子类来实现向某种特定介质写入数据。例如:FileOutputStream等,它的子类都是通过装饰模式来实现一些特定的功能的。OutputStream的基本方法是write(int b)。这个方法接受一个0到255之间的整数作为参数,将对应的字节写入到输出流中。虽然此方法接受一个int作为参数,但它实际上会写入一个无符号字节,因为java没有无符号字节数据类型,所以这要使用int来代替。无符号字节和有符号字节之间唯一的真正区别在于解释。它们都由8个二进制组成,write方法将int写入一个网络连接时,线缆上只会放8个二进制位。如果将一个超出0~255的int传入write方法,将协议这个数的最低字节,其他3个字节将被忽略。因为每次写入一个字节效率不高,所以就又提供了两个可以传入字节数组的方法,write(byte[])、write(byte b[],int off,int len)。

与网络硬件中缓存一样,流还可以在软件中得到缓冲,即直接用java代码缓存。在写入数据完成后,刷新(flush)输出流非常重要。因为flush()方法可以强迫缓冲的流发送数据,即使缓冲区还没有满,以此来打破流一直等待着缓冲区满了才会发送数据的状态。

最后,当结束一个流操作时,要通过调用它的close()方法将其关闭。关闭流会释放与整个流关联的所有资源,如果流来自网络连接,这个连接也会被关闭。长时间未关闭一个流,可能会泄漏文件句柄、网络端口和其他资源。所以在Java6以及更早的版本中,是在一个finally块中关闭流。但是Java7引入了try width resources 可以简化关闭流的操作,只需要把流定义在try的参数中即可。

如下所示:

try(OutputStream out = new FileOutputStream("D:/temp/test.txt")){
     // 处理输出流

}catch (IOException e){
    e.printStackTrace();
}

因为Java会对try块参数表中 声明的所有AutoCloseable对象自动调用close()。Java中的流相关的类基本上都直接或间接的实现了AutoCloseable接口。

输入流

Java的基本输出流类是:java.io.InputStream;

这个类提供了将数据读取为原始字节所需要的基本方法。如下:

public abstract int read() throws IOException;
public int read(byte b[]) throws IOException
public int read(byte b[], int off, int len) throws IOException
public long skip(long n) throws IOException 
public int available() throws IOException 
public void close() throws IOException

 InputStream的基本方法是没有参数的read()方法。此方法从输入流的源中读取1字节数据,作为一个0到255的int返回,流的结束通过返回-1来表示。read()方法会等待并阻塞其后任何代码的执行,直到有1字节的数据可供读取。输入和输出可能很慢,所以如果成行在做其他重要工作,要尽量将I/O放在单独的线程中。

一次读取1字节的效率也不高,因此,有两个重载的read()方法,可以用从流中读取的多字节的数据填充一个指定的数组:read(byte[] input)和read(byte[] input, int offset,int length)。当read的时候如果遇到IOException或网络原因只读取到了一部分,这个时候就会返回实际读取到的字节数。

例如:

int bytesRead = 0;
int bytesToRead = 1024;
byte[] input = new byte[bytesToRead];
while (bytesRead<bytesToRead){
       bytesRead += in.read(input,bytesRead,bytesToRead - bytesRead);
}

上面这段代码就是没有考虑到有可能流会中断导致读取的数据永远读不出来,所以要防止这种事情出现需要先测试read()的返回值,然后再增加到byteRead中

如下所示:

int bytesRead = 0;
int bytesToRead = 1024;
byte[] input = new byte[bytesToRead];
while (bytesRead<bytesToRead){
  int result = in.read(input,bytesRead,bytesToRead - bytesRead);
  if(result == -1) break;
  bytesRead += result;
}

可以使用available()方法来确定不阻塞的情况下有多少字节可以读取。它会返回可读取的最少字节数。事实上还能读取更多字节,至少可以读取available()建议的字节数。

如下:

int bytesAvailable = in.available();
byte[] input = new byte[bytesAvailable];
int bytesRead = in.read(input,0,bytesAvailable);
//读取到数据后,去执行其他部分

在少数情况下,你可能希望跳过数据不进行读取。skip()方法会完成这项任务。

与输出流一样,一旦结束对输入流的操作,应当调用close()方法将其关闭。这会释放这个流关联的所有资源。

InputStream类中还有3个不经常用的方法,

public synchronized void mark(int readlimit)
public synchronized void reset() throws IOException
public boolean markSupported()

为了重新读取数据,要用mark()方法标记流的当前位置,在以后某个时刻可以用reset()方法把流重置到之前标记的位置。在尝试使用标记和重置之前,要坚持markSupported()方法是否返回true。如果返回true,那么这个流确实支持标志和重置,否则,mark()会什么都不做,而reset()将抛出一个IOException异常。

过滤器流

过滤器由两个版本:过滤器流(filte stream)以及阅读器(reader)和书写器(writer)

每个过滤器输出流都有与java.io.OutputStream相同的write()、close()和flush()方法。每个过滤器输入流都有与java.io.InputStream相同的read()、close()和available()方法。

过滤器通过其构造函数与流连接。

FileInputStream iin = new FileInputStream("test.txt");
BufferedInputStream bin = new BufferedInputStream(iin);

这种情况下如果混合调用连接到同一个源的不同流,这可能会违反过滤器流的一些隐含约定。大多数情况下应当只使用链中最后一个过滤器进行实际的读/写。

可以用如下方式:

InputStream iin = new FileInputStream("test.txt");
iin = new BufferedInputStream(iin);

缓冲流

BufferedOutputStream类将写入的数据存储在缓冲区中,直到缓冲区满了或者执行了flush方法。然后将数据一次全部写入底层输出流。在网络连接中,缓冲网络输出通常会带来巨大的性能提升。

BufferedInputStream类也有一个作为缓冲区的保护字节数组,当调用某个流的read()方法时,它首先尝试从缓冲区获得请求的数据。当缓冲区没有数据时,流才从底层的源中读取数据。这时,它会读取尽可能多的数据存入缓冲区,而不管是否马上需要所有这些数据。不会立即用到的数据可以在以后调用read()时读取。当从本地磁盘中读取文件时,从底层流中读取几百字节的数据与读取1字节数据几乎一样快。因此,缓冲可以显著提升性能。

BufferedOutputStream有两个构造函数,BufferedInputStream也是有两个构造函数:

public BufferedInputStream(InputStream in)
public BufferedInputStream(InputStream in, int size) 

public BufferedOutputStream(OutputStream out)
public BufferedOutputStream(OutputStream out, int size) 

PrintStream

PrintStream类是大多数程序员都会遇到的第一个过滤器输出流,因为System.out就是一个PrintStream。还可以使用下面两个构造函数将其他输出流串链到打印流:

public PrintStream(OutputStream out)
public PrintStream(OutputStream out, boolean autoFlush)

如果autoFlush参数为true,那么每次写入1字节数组或换行,或者调用println()方法时,都会刷新输出流。除了平常的write()、flush()和close()方法,PrintStream还有9个重载的print()方法和10个重载的println方法:

public void print(boolean b)
public void print(char c)
public void print(int i)
public void print(long l)
public void print(float f)
public void print(double d)
public void print(char s[])
public void print(String s) 
public void print(Object obj)
public void println()
public void println(boolean x)
public void println(char x) 
public void println(int x)
public void println(long x) 
public void println(float x) 
public void println(double x) 
public void println(char x[])
public void println(String x)
public void println(Object x) 

每个print()方法都将其参数以可见的方式转换为一个字符串,再用默认的编码方式把字符串写入底层输出流。println()方法也完成相同操作,但会在所写的行末尾追加一个与平台有关的行分隔符。

在网络编程中应尽量避免使用PrintStream。

PrintStream第一个问题时println()输出是与平台有关的。

PrintStream第二个问题是会假定使用所在平台的默认编码方式。

PrintStream第三个问题时会吞掉了所有异常。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Java技术分享

XML文件约束与DTD的简单介绍

我们编写文档来约束一个XML文档的书写规范,这称之为XML约束。

310100
来自专栏cnblogs

knockout源码分析之执行过程

一、执行流程 ? 二、主要类分析 2.1. 在applyBindings中,创建bindingContext,然后执行applyBindingsToNodeAn...

228100
来自专栏Java帮帮-微信公众号-技术文章全总结

IO流总结

IO流总结 1.什么是IO Java中I/O操作主要是指使用Java进行输入,输出操作. Java所有的I/O机制都是基于数据流进行输入输出,这些数...

49770
来自专栏Java技术栈

通往大神之路,Java面试题前200页。

基本概念 操作系统中 heap 和 stack 的区别 什么是基于注解的切面实现 什么是 对象/关系 映射集成模块 什么是 Java 的反射机制 什么是 AC...

48160
来自专栏极客编程

ECMAScript 6教程 (一)

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出 原文连接,博客地址为 http://www.cnblogs.co...

9820
来自专栏闵开慧

java中获得文件大小代码

  根据指定文件创建FileInputStream,调用available方法返回文件大小,容量为byte                 File file ...

30070
来自专栏微信公众号:Java团长

Java面试题:百度前200页都在这里了

transient变量有什么特点 super什么时候使用 public static void 写成 static public void会怎样 说明一下pub...

12720
来自专栏风中追风

高效编程之HashMap的entryset和keyset比较

最近看了一点spring的源码,甚是苦涩;对spring稍微有了点整体的认识,但对很多细节的地方还是懵逼啊。。。太多不懂了的,只能慢慢去读,先把简单的不懂的解决...

387100
来自专栏Android群英传

Kotlin Primer·第三章·Kotlin 与 Java 混编

16720
来自专栏java技术学习之道

百度"Java面试题"前200页都在这里了

14720

扫码关注云+社区

领取腾讯云代金券