• 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第三个问题,会吞掉了所有异常。

    数据流

    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 writeShort(int v) throws IOException
    public final void writeChar(int v) throws IOException
    public final void writeInt(int v) throws IOException
    public final void writeLong(long v) throws IOException
    public final void writeFloat(float v) throws IOException
    public final void writeDouble(double v) throws IOException 
    public final void writeBytes(String s) throws IOException
    public final void writeChars(String s) throws IOException 
    public final void writeUTF(String str) throws IOException

    前面的8个方法,都按照实际参数的类型长度来写数据的,最后三个方法有些特别,writeChars()方法只是对String参数迭代处理,将各个字符按顺序写为一个2字节的big-endian Unicode字符。writeBytes()方法迭代处理String参数,但只是写入每个字符的低字节。

    writeChars和writeBytes都不会对输出流的字符串的长度编码。因此,你无法真正区分原始字符和作为字符串一部分的字符。writeUTF()方法则包括了字符串的长度。它将字符串本身用Unicode UTF-8编码的一个变体进行编码。

    除了这些写入二进制数字和字符串的方法,DataOutputStream当然还有所有OutputStream类都有的平常的write()、flush()、和close()方法。

    DataInputStream与DataOutputStream是互补的。DataOutputStream写入的每一种数据格式,DataInputStream都可以读取。此外DataInputStream还有通常read()、available()、skip()和close()方法,以及读取整个字节数组和文本行的方法。所以DataInputStream的内容就不写了。

    书写器

    Writer是以字符流的方式书写数据,它是一个抽象类,有两个保护类型的构造函数。与OutputStream类似,Writer类从不直接使用;相反,会通过他的某个子类以多态方式使用。它有5个write()方法,另外还有flush()和close()方法。

    protected Writer()
    protected Writer(Object lock) 
    abstract public void write(char cbuf[], int off, int len) throws IOException
    public void write(int c) throws IOException
    public void write(char cbuf[]) throws IOException 
    public void write(String str) throws IOException 
    public void write(String str, int off, int len) throws IOException 
    bstract public void flush() throws IOException
    abstract public void close() throws IOException

    abstract public void write(char cbuf[], int off, int len)方法是基础方法,其他四个write()都是根据它实现的。子类至少覆盖整个方法以及flush()和close(),
    但是为了提供更高效的实现方法,大多数子类还覆盖了其他一些write()方法。

    例如:给定一个Writer对象w,可以这样写入字符串“Jimoer”:

    char[] jimoer = {'J','i','m','o','e','r'};
    w.write(jimoer,0,jiomer.length);

    也可以用其他write()方法完成同样的任务:

    Writer w = new OutputStreamWriter(out);
    w.write(jiomer);
    for(int i=0;i<jimoer.length;i++){
         w.write(jiomer[i]);
    }
    w.write("Jimoer");
    w.write("Jimoer",0,5);

    所有这些例子表述都是同样的事情,只不过方式有所不同。

    书写器可以缓冲,有可能直接串链到BufferedWriter,也有可能直接链入。为了强制将一个写入提交给输出介质,需要调用flush()方法。

    OutputStreamWriter

    OutputStreamWriter是Writer的最重要的具体子类。OutputStreamWriter会从Java程序中接收字符。会根据指定的编码方式将这些字符转换为直接,并写入底层输出流。

    构造函数指定了要写入的输出流和使用的编码方式:

    public OutputStreamWriter(OutputStream out, String charsetName)
            throws UnsupportedEncodingException
        {
            super(out);
            if (charsetName == null)
                throw new NullPointerException("charsetName");
            se = StreamEncoder.forOutputStreamWriter(out, this, charsetName);
        }

    除了构造函数,OutputStream只有通常的Writer方法,还有一个返回对象编码方式的方法:

    public String getEncoding()

    阅读器

    Reader是一个抽象类,从不直接使用,只通过子类来使用。有三个read()方法,另外还有skip()、close()、ready()、mark()、reset()和markSupported()方法:

    protected Reader()
    protected Reader(Object lock) 
    abstract public int read(char cbuf[], int off, int len) throws IOException
    public int read() throws IOException 
    public int read(char cbuf[]) throws IOException
    public long skip(long n) throws IOException
    public boolean ready() throws IOException
    public boolean markSupported()
    public void mark(int readAheadLimit) throws IOException 
    public void reset() throws IOException 
    abstract public void close() throws IOException

    这里面的方法大多数都与Writer中方法能对应上,read()方法以int型(从0到65,535)返回一个单一的Unicode字符或读到流结束时返回-1。

    InputStreamReader是Reader的最重要的具体子类。InputStreamReader从其底层输入流中读取字节。然后根据指定的编码发那个还是将字节转为字符,并返回这些字符。

    构造函数如下:

    public InputStreamReader(InputStream in) 
    public InputStreamReader(InputStream in, String charsetName)
            throws UnsupportedEncodingException
    public InputStreamReader(InputStream in, Charset cs) 
    public InputStreamReader(InputStream in, CharsetDecoder dec)

    如果没有指定编码方式,就使用平台的默认编码方式。如果指定了一个位置的编码方式,会抛出UnsupportedEncodingException异常。

    过滤阅读器和书写器

    InputStreamReader和OutputStreamWriter类就相当于输入和输出流之上的装饰器,把面向字节的接口改为面向字符的接口。完成之后就可以将其他面向字符的过滤器放在使用java.io.FilterReader和java.io.FilterWriter类的阅读器或书写器上。

    BufferedReader和BufferedWriter也有与阅读器和书写器关联的常用方法,如read()、ready()、write()和close()。这两个类都有两个构造函数,可以将BufferedReader或BufferedWriter串链到一个底层阅读器或书写器,并设置缓冲区的大小。如果没有设置大小,则使用默认的大小8192字符:

    public BufferedReader(Reader in, int sz) 
    public BufferedReader(Reader in)
    public BufferedWriter(Writer out)
    public BufferedWriter(Writer out, int sz) 

    BufferedReader类还有一个readLine()方法,它读取一行文本,并作为一个字符串返回:

    public String readLine() throws IOExceptioin

    这个方法可以替代DataInputStream中国已经废弃的readLine()方法,它与该方法的行为基本相同。主要区别在于,通过BufferedReader串链到InputStreamReader,可以用正确的字符集读取行,而不是采用平台的默认编码方式。

    BufferedWriter类增加了一个其超类所没有的新方法,名为newLine(),也用于写入一行:

    public void newLine() throws IOException

    这个方法向输出插入一个与平台有关的行分隔符字符串。

    PrintWriter

    PrintWriter类用户取代Java1.0的PrintStream类,它能正确地处理多字节字符集和国际化文本。除了构造函数,PrintWriter类也有与PrintStream几乎相同的方法集。

    public PrintWriter (Writer out)
    public PrintWriter(Writer out,boolean autoFlush) 
    public PrintWriter(OutputStream out)
    public PrintWriter(OutputStream out, boolean autoFlush) 
    public PrintWriter(String fileName) throws FileNotFoundException 
    private PrintWriter(Charset charset, File file) throws FileNotFoundException
    public PrintWriter(String fileName, String csn)
            throws FileNotFoundException, UnsupportedEncodingException
    public PrintWriter(File file) throws FileNotFoundException 
    public PrintWriter(File file, String csn)
            throws FileNotFoundException, UnsupportedEncodingException
    public void flush() 
    public void close()
    public boolean checkError() 
    public void write(int c) 
    public void write(char buf[], int off, int len)
    public void write(char buf[])
    public void write(String s, int off, int len) 
    public void write(String s)
    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) 
    public PrintWriter printf(String format, Object ... args) 

    这些方法的行为大多数与PrintStream中相同。只有4个write()方法有所例外,它们写入字符而不是字节。

  • 相关阅读:
    学习使用资源文件[4] 用资源中的图片做背景、使用 LoadFromResourceID
    WinAPI: ShellExecute 打开外部程序或文件
    学习使用资源文件[8] 关于 HInstance
    学习使用资源文件[3] 用 Image 显示资源中的图片
    薛定谔之猫_百度百科
    美国创业公司招聘工程师
    Two star programming
    vector 自定义排序
    Data Structures, Algorithms, & Applications in Java Suffix Trees
    Three Star Programmer
  • 原文地址:https://www.cnblogs.com/jimoer/p/9944171.html
Copyright © 2020-2023  润新知