IO流


文章目录

  • 1、ObjectOutputStream
  • 2、ByteArrayInputStream
  • 3、PipedInputStream
  • 4、StringBufferInputStream
  • 5、FilterInputStream
  • 5.1 DataInputStream
  • 5.2 BufferedInputStream
  • 5.3 PushbackInputStream
  • 5.4 LineNumberInputStream
  • 6、SequenceInputStream
  • 1、InputStreamReader
  • 2、CharArrayReader
  • 3、StringReader
  • 4、PipedReader
  • 5、FilterReader
  • 5.1 PushbackReader
  • 6、BufferedReader
  • 6.1 LineNumberReader


1、ObjectOutputStream

ObjectOutputStream 将 Java 对象的基本数据类型和图形写入 OutputStream。可以使用 ObjectInputStream 读取(重构)对象。通过在流中使用文件可以实现对象的持久存储。如果流是网络套接字流,则可以在另一台主机上或另一个进程中重构对象。

只能将支持 java.io.Serializable 接口的对象写入流中。每个 serializable 对象的类都被编码,编码内容包括类名和类签名、对象的字段值和数组值,以及从初始对象中引用的其他所有对象的闭包。

writeObject 方法用于将对象写入流中。所有对象(包括 String 和数组)都可以通过 writeObject 写入。可将多个对象或基元写入流中。必须使用与写入对象时相同的类型和顺序从相应 ObjectInputstream 中读回对象。

  • ObjectOutputStream 是将一个对象写入文件:如果使用这个类写入对象,这个对象需要序列化
  • ObjectInputStream 是从文件中读一个对象:反序列化

【案例】

public class TestObjectStream {
    public static void main(String[] args) {
        // writeObject();
        readObject();
    }

    static void writeObject() {
        GirlFriend girlFriend = new GirlFriend("张三", 175);
        try (FileOutputStream fos = new FileOutputStream("E:\\c.txt");
             ObjectOutputStream oos = new ObjectOutputStream(fos)) {
            oos.writeObject(girlFriend);
        }catch (IOException e){
            System.out.println(e.getMessage());
        }
    }

    static void readObject() {
        try (FileInputStream fis = new FileInputStream("E:\\c.txt");
             ObjectInputStream ois = new ObjectInputStream(fis);) {
            GirlFriend girlFriend = (GirlFriend) ois.readObject();
            System.out.println(girlFriend);
        } catch (IOException | ClassNotFoundException ex) {
            ex.printStackTrace();
        }
    }
}

@Data
@AllArgsConstructor
class GirlFriend implements Serializable {
    private String name;
    private double height;
}

存储效果

io涉及的类 java io常见类_io涉及的类 java

2、ByteArrayInputStream

ByteArrayInputStream 是字节数组输入流,在内存中创建了一个字节数组,将输入流中读取的数据保存到字节数组的缓存区中.也就是说字节数组输入流将读取数据放到字节数组缓冲区中

内部变量

protected byte buf[];
protected int pos;
protected int mark = 0;
protected int count;
  • buf 是保存字节输入流数据的字节数组
  • pos 是读取数组中的下一个字节的索引,是一个正整数,大小在0到count
  • mark 是标记的索引。可通过 mark() 方法和 reset() 方法进行设置,不设置的时候,调用第一个构造方法时值为0,调用第二个构造方法时 mark 值被设置成 offset
  • count 是字节流的长度。当用第一个构造方法时,长度是字节数组 buflength,当用第二个构造方法时,长度是 offset+lengthbuf.length 的中的较小值

【案例】

public class TestByteArrayStream {
    public static void main(String[] args) {
        byte[] bytes = "abcdefghijklmnopqrst".getBytes();
        ByteArrayInputStream byteStream = new ByteArrayInputStream(bytes);

        //available表示剩余可读字节数
        if (byteStream.available() > 0) {
            System.out.println((char) byteStream.read() + "-------剩余可读字节数=" + byteStream.available());
        }
        for (int i = 0; i < 3; i++) {
            if (byteStream.available() > 0) {
                System.out.println((char) byteStream.read());
            }
        }

        //跳过三个字节
        long skip = byteStream.skip(3);
        System.out.println((char) byteStream.read() + "---------跳过字节数" + skip);
        if (byteStream.markSupported()){
            System.out.println("support mark");
        }

        //现在位置进行标记
        byteStream.mark(0);
        byte[] byteArray = new byte[3];
        byteStream.read(byteArray,0,2);
        System.out.println(new String(byteArray));

        //通过reset方法将指针指到mark位置
        byteStream.reset();
        System.out.println((char)byteStream.read());
    }
}

【源码分析】

public class ByteArrayInputStream extends InputStream {
 
  // 字节数组用于保存流里面的数据
  protected byte buf[];
 
  // 下一个会被读取的字节的索引
  protected int pos;
 
  // 标记的索引
  protected int mark = 0;
 
  // 流数据长度
  protected int count;
 
  // 构造函数--创建一个数据为buf字节数组的输入流
  public ByteArrayInputStream(byte buf[]) {
    this.buf = buf;
    // 初始下一个读取的字节的索引是0
    this.pos = 0;
    // 当使用此构造方法时,count的大小值是buf.length
    this.count = buf.length;
  }
 
  // 构造函数--创建字节输入流,它的数据是buf字节数组中,从offset索引开始,读取的长度为length的数据.
  public ByteArrayInputStream(byte buf[], int offset, int length) {
    this.buf = buf;
    this.pos = offset;
    // 当使用此构造方式时,count的大小是offset+length,buf.length中较小的值
    this.count = Math.min(offset + length, buf.length);
    // 当使用此构造方法时,标记位置的默认位置是offset
    this.mark = offset;
  }
 
  // 读取下一个字节
  public synchronized int read() {
    return (pos < count) ? (buf[pos++] & 0xff) : -1;
  }
 
  // 将数据的读取到字节数组b中,off是将流数据写到数组开始的索引,len是写入长度
  public synchronized int read(byte b[], int off, int len) {
    if (b == null) {
      throw new NullPointerException();
    } else if (off < 0 || len < 0 || len > b.length - off) {
      throw new IndexOutOfBoundsException();
    }
    // 下一个读取字节的索引大于等于count,表示数据已经读完
    if (pos >= count) {
      return -1;
    }
    // len大于剩余可读的字节长度,len将会置为剩余可读字节长度
    int avail = count - pos;
    if (len > avail) {
      len = avail;
    }
    if (len <= 0) {
      return 0;
    }
    System.arraycopy(buf, pos, b, off, len);
    pos += len;
    return len;
  }
 
  // 跳过字节数,返回值是实际跳过的字节数量.
  public synchronized long skip(long n) {
    // 流数据长度count-下一个读取字节索引pos
    long k = count - pos;
    // 跳过字节数小于剩余可读字节数k时,跳过字节数为0,实际跳过字节数为0,是否实际是n
    if (n < k) {
      k = n < 0 ? 0 : n;
    }
    // 将pos加上实际跳过字节数.
    pos += k;
    return k;
  }
 
  // 可读字节数量,流数据长度count-下一个读取字节索引pos
  public synchronized int available() {
    return count - pos;
  }
 
  // 是否支持标记,此方法将返回的true
  public boolean markSupported() {
    return true;
  }
 
  // 标记当前位置。readAheadLimit在此无实际意义
  public void mark(int readAheadLimit) {
    mark = pos;
  }
 
  // 将位置重置到mark()方法标记的位置
  public synchronized void reset() {
    pos = mark;
  }
 
  // 此方法在流已经关闭的情况下,不会抛出异常
  public void close() throws IOException {}
}

3、PipedInputStream

在java中,PipedOutputStreamPipedInputStream 分别是管道输出流和管道输入流。

它们的作用是让多线程可以通过管道进行线程间的通讯。在使用管道通信时,必须将 PipedOutputStreamPipedInputStream 配套使用。

使用管道通信时,大致的流程是:我们在线程A中向 PipedOutputStream 中写入数据,这些数据会自动的发送到与 PipedOutputStream 对应的 PipedInputStream 中,进而存储在 PipedInputStream 的缓冲中;此时,线程B通过读取 PipedInputStream 中的数据。就可以实现,线程A和线程B的通信。

【构造方法】

  • public PipedInputStream():默认缓冲区大小是1024字节
  • public PipedInputStream(PipedOutputStream src):指定与管道输入流关联的管道输出流,默认缓冲区大小是1024字节
  • public PipedInputStream(int pipeSize):指定缓冲区大小是 pipeSize
  • public PipedInputStream(PipedOutputStream src,int pipeSize):指定与“管道输入流”关联的”管道输出流,以及“缓冲区大小”

【实例】

多线程通过 PipedInputStreamPipedOutputStream 进行线程间同通讯,下面例子分别定义三个类:PipedDemo(主线程main类),PipedSender(发送者对象)、PipedReceiver(接收者对象)

【案例】

发送者

class PipedSender extends Thread {

    //定义私有PipedOutputStream对象
    private PipedOutputStream out = new PipedOutputStream();

    public PipedOutputStream getOutputStream() {
        return out;
    }

    @Override
    public void run() {
        // writeOne();
        writeMove();
    }

    /**
     * 写入一段端数据
     */
    private void writeOne() {
        byte[] buffer = "this is a message".getBytes();
        try {
            out.write(buffer);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            System.out.println("Sender:发送成功");
            try {
                if (out != null) {
                    out.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 写入较长数据
     */
    public void writeMove() {
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < 100; i++) {
            sb.append("1234567890");
        }
        sb.append("abcdefghigklmnopqrstuvwxyz");
        String str = sb.toString();
        try {
            out.write(str.getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            System.out.println("Sender:发送成功");
            try {
                if (out != null) {
                    out.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

【接收者】

class PipedReceiver extends Thread {

    //私有PipedInputStream对象
    private PipedInputStream in = new PipedInputStream();

    public PipedInputStream getInputStream() {
        return in;
    }

    @Override
    public void run() {
        // readOne();
        readMove();
    }

    /**
     * 读取一次
     */
    public void readOne() {
        byte[] buffer = new byte[2028];
        int len = 0;
        try {
            len = in.read(buffer);
            System.out.println(new String(buffer, 0, len));
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            System.out.println("Receiver:接收成功");
            try {
                if (in != null) {
                    in.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 读取多次
     */
    public void readMove(){
        byte[] buffer = new byte[1024];
        int len = 0;
        try {
            while (true){
                len = in.read(buffer);
                if (len == -1){
                    break;
                }
                System.out.println(new String(buffer,0,len));
            }
        }catch (IOException e) {
            e.printStackTrace();
        }finally {
            System.out.println("Receiver:接收成功");
            try {
                if (in != null){
                    in.close();
                }
            }catch (IOException ex) {
                ex.printStackTrace();
            }
        }
    }
}

【Main函数】

public class TestPipedStream {
    public static void main(String[] args) {
        PipedSender sender = new PipedSender();
        PipedReceiver receiver = new PipedReceiver();

        PipedOutputStream out = sender.getOutputStream();
        PipedInputStream in = receiver.getInputStream();

        try {
            //下面两条语句是一样的,但只能存在一条语句
            // in.connect(out);
            out.connect(in);
            //分别开启两个线程
            sender.start();
            receiver.start();
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}

4、StringBufferInputStream

此类目前已经被启用,已被 StringReader 替代

5、FilterInputStream

FilterInputStreamFilterOutputStream 是过滤字节输入流和过滤字节输出流。它们的作用是封装其他的基础流,并为它们提供额外的功能。它们的子类分别有:

  • 缓冲流:BufferedInputStreamBufferedOutputStream
  • 数据流:DataInputStreamDataOutputStream
  • 回退流:PushBackInputStreamPushBackOutputStream

装饰者模式和继承

装饰者模式,就是将原有的基础流进行"装饰",那么装饰后的方法要与原先被装饰的基础类要保持一致,也可以在对基础流进行扩展。而继承是继承父类的属性和方法,通过重写父类里面的方法也可以起到"装饰"作用,比如强化或者优化父类里面的一些方法。

两者的区别是装饰者模式可以动态地扩展一个对象,给对象添加额外的功能,而且装饰者和被装饰者之间不会产生耦合。

(一)、实现 BufferedInputStream 的功能,采用继承的方式。

如果采用继承的方式,要实现 BufferedInputStream 的功能,我们就必须继承大部分的字节输入流,Java IO中还有其他的如 DataInputStream 等,如果都使用继承方式,那么Java IO 体系会变得很臃肿,而且类与类之间的耦合度特别高。

(二)、实现 BufferedInputStream 的功能,采用装饰者模式。

通过上面的继承方式,通过装饰者模式可以解决继承带来的问题。

装饰者模式相比继承,没有这么多繁杂的类,而且类与类的之间的耦合性降低,具体做法就是将类 FilterInputStream 提取为一个父类,而其子类就是各个功能的实现类。如果想要基础输入流要某个功能,那么就可以将对应的基础输入流传到对应的子类构造方法中。

【剖析】

protected volatile InputStream in;

而这个类的特殊之处,就是包含了一个InputStream,使得可以在这个InputStream基础上进行多种封装,从而达到装饰的目的。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kacAWM48-1590842795958)(F:\Typora\images\image-20200527222731617.png)]

从源码中可以看出,这个FilterInputStream中有一个域:

protected volatile InputStream in;

这个域是在构造方法中传入的:

protected FilterInputStream(InputStream in) {
    this.in = in;
}

这也就说明了FilterInputStream在实例化的时候,要传一个InputStream类的对象进来。

而且这个类中的read方法并不像FileInputStream进行了实现,而只是一种“伪”实现:

public int read() throws IOException {
    return in.read();
}

io涉及的类 java io常见类_System_02

5.1 DataInputStream

DataInputStream 是数据输入流。它继承于 FilterInputStream

DataInputStream 是用来装饰其它输入流,它“允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型”。应用程序可以使用 DataOutputStream(数据输出流)写入由 DataInputStream(数据输入流)读取的数据。

DataInputStream 函数列表

DataInputStream(InputStream in)
final int     read(byte[] buffer, int offset, int length)
final int     read(byte[] buffer)
final boolean     readBoolean()
final byte     readByte()
final char     readChar()
final double     readDouble()
final float     readFloat()
final void     readFully(byte[] dst)
final void     readFully(byte[] dst, int offset, int byteCount)
final int     readInt()
final String     readLine()
final long     readLong()
final short     readShort()
final static String     readUTF(DataInput in)
final String     readUTF()
final int     readUnsignedByte()
final int     readUnsignedShort()
final int     skipBytes(int count)

DataInputStream 中比较难以理解的函数就只有 readUTF(DataInput in);下面,对这个函数进行详细的介绍,其它的函数请参考源码中的注释。

**readUTF(DataInput in)**源码如下:

public final static String readUTF(DataInput in) throws IOException {
    // 从“数据输入流”中读取“无符号的short类型”的值:
    // 注意:UTF-8输入流的前2个字节是数据的长度
    int utflen = in.readUnsignedShort();
    byte[] bytearr = null;
    char[] chararr = null;

    // 如果in本身是“数据输入流”,
    // 则,设置字节数组bytearr = "数据输入流"的成员bytearr
    //     设置字符数组chararr = "数据输入流"的成员chararr
    // 否则的话,新建数组bytearr和chararr
    if (in instanceof DataInputStream) {
        DataInputStream dis = (DataInputStream)in;
        if (dis.bytearr.length < utflen){
            dis.bytearr = new byte[utflen*2];
            dis.chararr = new char[utflen*2];
        }
        chararr = dis.chararr;
        bytearr = dis.bytearr;
    } else {
        bytearr = new byte[utflen];
        chararr = new char[utflen];
    }

    int c, char2, char3;
    int count = 0;
    int chararr_count=0;

    // 从“数据输入流”中读取数据并存储到字节数组bytearr中;从bytearr的位置0开始存储,存储长度为utflen。
    // 注意,这里是存储到字节数组!而且读取的是全部的数据。
    in.readFully(bytearr, 0, utflen);

    // 将“字节数组bytearr”中的数据 拷贝到 “字符数组chararr”中
    // 注意:这里相当于“预处理的输入流中单字节的符号”,因为UTF-8是1-4个字节可变的。
    while (count < utflen) {
        // 将每个字节转换成int值
        c = (int) bytearr[count] & 0xff;
        // UTF-8的每个字节的值都不会超过127;所以,超过127,则退出。
        if (c > 127) break;
        count++;
        // 将c保存到“字符数组chararr”中
        chararr[chararr_count++]=(char)c;
    }

    // 处理完输入流中单字节的符号之后,接下来我们继续处理。
    while (count < utflen) {
        // 下面语句执行了2步操作。
        // (01) 将字节由 “byte类型” 转换成 “int类型”。
        //      例如, “11001010” 转换成int之后,是 “00000000 00000000 00000000 11001010”
        // (02) 将 “int类型” 的数据左移4位
        //      例如, “00000000 00000000 00000000 11001010” 左移4位之后,变成 “00000000 00000000 00000000 00001100”
        c = (int) bytearr[count] & 0xff;
        switch (c >> 4) {
            // 若 UTF-8 是单字节,即 bytearr[count] 对应是 “0xxxxxxx” 形式;
            // 则 bytearr[count] 对应的int类型的c的取值范围是 0-7。
            case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
                /* 0xxxxxxx*/
                count++;
                chararr[chararr_count++]=(char)c;
                break;

            // 若 UTF-8 是双字节,即 bytearr[count] 对应是 “110xxxxx  10xxxxxx” 形式中的第一个,即“110xxxxx”
            // 则 bytearr[count] 对应的int类型的c的取值范围是 12-13。
            case 12: case 13:
                /* 110x xxxx   10xx xxxx*/
                count += 2;
                if (count > utflen)
                    throw new UTFDataFormatException(
                        "malformed input: partial character at end");
                char2 = (int) bytearr[count-1];
                if ((char2 & 0xC0) != 0x80)
                    throw new UTFDataFormatException(
                        "malformed input around byte " + count);
                chararr[chararr_count++]=(char)(((c & 0x1F) << 6) |
                                                (char2 & 0x3F));
                break;

            // 若 UTF-8 是三字节,即 bytearr[count] 对应是 “1110xxxx  10xxxxxx  10xxxxxx” 形式中的第一个,即“1110xxxx”
            // 则 bytearr[count] 对应的int类型的c的取值是14 。
            case 14:
                /* 1110 xxxx  10xx xxxx  10xx xxxx */
                count += 3;
                if (count > utflen)
                    throw new UTFDataFormatException(
                        "malformed input: partial character at end");
                char2 = (int) bytearr[count-2];
                char3 = (int) bytearr[count-1];
                if (((char2 & 0xC0) != 0x80) || ((char3 & 0xC0) != 0x80))
                    throw new UTFDataFormatException(
                        "malformed input around byte " + (count-1));
                chararr[chararr_count++]=(char)(((c     & 0x0F) << 12) |
                                                ((char2 & 0x3F) << 6)  |
                                                ((char3 & 0x3F) << 0));
                break;

            // 若 UTF-8 是四字节,即 bytearr[count] 对应是 “11110xxx 10xxxxxx  10xxxxxx  10xxxxxx” 形式中的第一个,即“11110xxx”
            // 则 bytearr[count] 对应的int类型的c的取值是15
            default:
                /* 10xx xxxx,  1111 xxxx */
                throw new UTFDataFormatException(
                    "malformed input around byte " + count);
        }
    }
    // The number of chars produced may be less than utflen
    return new String(chararr, 0, chararr_count);
}

说明:

(01) readUTF()的作用,是从输入流中读取UTF-8编码的数据,并以String字符串的形式返回。
(02) 知道了readUTF()的作用之后,下面开始介绍readUTF()的流程:

第1步,读取出输入流中的UTF-8数据的长度。代码如下:

int utflen = in.readUnsignedShort();

UTF-8数据的长度包含在它的前两个字节当中;我们通过readUnsignedShort()读取出前两个字节对应的正整数就是UTF-8数据的长度。

第2步,创建2个数组:字节数组bytearr 和 字符数组chararr。代码如下:

if (in instanceof DataInputStream) {
    DataInputStream dis = (DataInputStream)in;
    if (dis.bytearr.length < utflen){
        dis.bytearr = new byte[utflen*2];
        dis.chararr = new char[utflen*2];
    }
    chararr = dis.chararr;
    bytearr = dis.bytearr;
} else {
    bytearr = new byte[utflen];
    chararr = new char[utflen];
}

第3步,将UTF-8数据全部读取到“字节数组bytearr”中。代码如下:

in.readFully(bytearr, 0, utflen);

注意: 这里是存储到字节数组,而不是字符数组!而且读取的是全部的数据。

第4步,对UTF-8中的单字节数据进行预处理。代码如下:

while (count < utflen) {
    // 将每个字节转换成int值
    c = (int) bytearr[count] & 0xff;
    // UTF-8的单字节数据的值都不会超过127;所以,超过127,则退出。
    if (c > 127) break;
    count++;
    // 将c保存到“字符数组chararr”中
    chararr[chararr_count++]=(char)c;
}

UTF-8的数据是变长的,可以是1-4个字节;在readUTF()中,我们最终是将全部的UTF-8数据保存到“字符数组(而不是字节数组)”中,再将其转换为String字符串。
由于UTF-8的单字节和ASCII相同,所以这里就将它们进行预处理,直接保存到“字符数组chararr”中。对于其它的UTF-8数据,则在后面进行处理。

第5步,对“第4步 预处理”之后的数据,接着进行处理。代码如下:

// 处理完输入流中单字节的符号之后,接下来我们继续处理。
while (count < utflen) {
    // 下面语句执行了2步操作。
    // (01) 将字节由 “byte类型” 转换成 “int类型”。
    //      例如, “11001010” 转换成int之后,是 “00000000 00000000 00000000 11001010”
    // (02) 将 “int类型” 的数据左移4位
    //      例如, “00000000 00000000 00000000 11001010” 左移4位之后,变成 “00000000 00000000 00000000 00001100”
    c = (int) bytearr[count] & 0xff;
    switch (c >> 4) {
        // 若 UTF-8 是单字节,即 bytearr[count] 对应是 “0xxxxxxx” 形式;
        // 则 bytearr[count] 对应的int类型的c的取值范围是 0-7。
        case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
            /* 0xxxxxxx*/
            count++;
            chararr[chararr_count++]=(char)c;
            break;

        // 若 UTF-8 是双字节,即 bytearr[count] 对应是 “110xxxxx  10xxxxxx” 形式中的第一个,即“110xxxxx”
        // 则 bytearr[count] 对应的int类型的c的取值范围是 12-13。
        case 12: case 13:
            /* 110x xxxx   10xx xxxx*/
            count += 2;
            if (count > utflen)
                throw new UTFDataFormatException(
                    "malformed input: partial character at end");
            char2 = (int) bytearr[count-1];
            if ((char2 & 0xC0) != 0x80)
                throw new UTFDataFormatException(
                    "malformed input around byte " + count);
            chararr[chararr_count++]=(char)(((c & 0x1F) << 6) |
                                            (char2 & 0x3F));
            break;

        // 若 UTF-8 是三字节,即 bytearr[count] 对应是 “1110xxxx  10xxxxxx  10xxxxxx” 形式中的第一个,即“1110xxxx”
        // 则 bytearr[count] 对应的int类型的c的取值是14 。
        case 14:
            /* 1110 xxxx  10xx xxxx  10xx xxxx */
            count += 3;
            if (count > utflen)
                throw new UTFDataFormatException(
                    "malformed input: partial character at end");
            char2 = (int) bytearr[count-2];
            char3 = (int) bytearr[count-1];
            if (((char2 & 0xC0) != 0x80) || ((char3 & 0xC0) != 0x80))
                throw new UTFDataFormatException(
                    "malformed input around byte " + (count-1));
            chararr[chararr_count++]=(char)(((c     & 0x0F) << 12) |
                                            ((char2 & 0x3F) << 6)  |
                                            ((char3 & 0x3F) << 0));
            break;

        // 若 UTF-8 是四字节,即 bytearr[count] 对应是 “11110xxx 10xxxxxx  10xxxxxx  10xxxxxx” 形式中的第一个,即“11110xxx”
        // 则 bytearr[count] 对应的int类型的c的取值是15 
        default:
            /* 10xx xxxx,  1111 xxxx */
            throw new UTFDataFormatException(
                "malformed input around byte " + count);
    }
}
public class TestDataInputStream {
    private static final int LEN = 5;

    public static void main(String[] args) {
        testDataOutputStream();
        TestDataInputStream();
    }

    /**
     * DataInputStream的API测试函数
     */
    private static void testDataOutputStream() {
        try {
            File file = new File("H:\\a.txt");
            DataOutputStream out = new DataOutputStream(new FileOutputStream(file));
            out.writeBoolean(true);
            out.writeByte((byte) 0x41);
            out.writeChar((char) 0x4243);
            out.writeShort((short) 0x4445);
            out.writeInt(0x12345678);
            out.writeLong(0x0FEDCBA987654321L);

            out.writeUTF("abcdefghigklmnopqrstuvwxyz严12");

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

    private static void TestDataInputStream() {
        try {
            File file = new File("H:\\a.txt");
            DataInputStream in = new DataInputStream(new FileInputStream(file));

            System.out.println("byteToHexString(0x8F):0x" + byteToHexString((byte) 0x8F));
            System.out.println("charToHexString((char)0x8FCF):0x" + charToHexString((char) 0x8FCF));

            System.out.println("readBoolean():" + in.readBoolean());
            System.out.println("readByte:0x" + byteToHexString(in.readByte()));
            System.out.println("readChar:0x" + charToHexString(in.readChar()));
            System.out.println("readShort:0x" + shortToHexString(in.readShort()));
            System.out.println("readInt:0x" + Integer.toHexString(in.readInt()));
            System.out.println("readLong:0x" + Long.toHexString(in.readLong()));
            System.out.println("in.readUTF() = 0x" + in.readUTF());

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

    //打印byte对应的16进制的字符串
    private static String byteToHexString(byte va1) {
        return Integer.toHexString(va1 & 0xff);
    }

    //打印char对饮的16进制的字符串
    private static String charToHexString(char va1) {
        return Integer.toHexString(va1);
    }

    //打印short对应的16进制的字符串
    private static String shortToHexString(short va1) {
        return Integer.toHexString(va1 & 0xffff);
    }
}

5.2 BufferedInputStream

BufferedInputStream 继承于 FilterInputStream,提供缓冲输入流功能。缓冲输入流相对于普通输入流的优势是,它提供了一个缓冲数组,每次调用 read 方法的时候,它首先尝试从缓冲区里读取数据,若读取失败(缓冲区无可读数据),则选择从物理数据源(譬如文件)读取新数据(这里会尝试尽可能读取多的字节)放入到缓冲区中,最后再将缓冲区中的内容部分或全部返回给用户.由于从缓冲区里读取数据远比直接从物理数据源(譬如文件)读取速度快。

BufferedInputStream提供的API如下:

//构造方法
BufferedInputStream(InputStream in)
BufferedInputStream(InputStream in, int size)

//下一字节是否可读
synchronized int     available()
//关闭
void     close()
//标记, readlimit为mark后最多可读取的字节数
synchronized void     mark(int readlimit)
//是否支持mark, true
boolean     markSupported()
//读取一个字节
synchronized int     read()
//读取多个字节到b
synchronized int     read(byte[] b, int off, int len)
//重置会mark位置
synchronized void     reset()
//跳过n个字节
synchronized long     skip(long n)

内存缓冲的实现

通过构造方法可以看到:初始化了一个byte数组作为内存缓冲区,大小可以由构造方法中的参数指定,也可以是默认的大小。

protected volatile byte buf[];  
private static int defaultBufferSize = 8192;  
public BufferedInputStream(InputStream in, int size) {  
    super(in);  
    if (size <= 0) {  
        throw new IllegalArgumentException(“Buffer size <= 0″);  
    }  
    buf = new byte[size];  
}  
public BufferedInputStream(InputStream in) {  
    this(in, defaultBufferSize);  
}

看完构造函数,大概可以了解其实现原理:通过初始化分配一个byte数组,一次性从输入字节流中读取多个字节的数据放入byte数组,程序读取部分字节的时候直接从byte数组中获取,直到内存中的数据用完再重新从流中读取新的字节。那么从api文档中我们可以了解到BufferedStream大概具备如下的功能:

从api可以了解到BufferedInputStream除了使用一个byte数组做缓冲外还具备打标记,重置当前位置到标记的位置重新读取数据,忽略掉n个数据。这些功能都涉及到缓冲内存的管理,首先看下相关的几个成员变量:

protected int count;  
protected int pos;  
protected int markpos = -1;  
protected int marklimit;

count表示当前缓冲区内总共有多少有效数据;pos表示当前读取到的位置(即byte数组的当前下标,下次读取从该位置读取);markpos:打上标记的位置;marklimit:最多能mark的字节长度,也就是从mark位置到当前pos的最大长度。

从最简单的read()读取一个字节的方法开始看:

public synchronized int read() throws IOException {  
    if (pos >= count) {  
        fill();  
        if (pos >= count)  
        return -1;  
    }  
    return getBufIfOpen()[pos++] & 0xff;  
}

当pos>=count的时候也就是表示当前的byte中的数据为空或已经被读完,他调用了一个fill()方法,从字面理解就是填充的意思,实际上是从真正的输入流中读取一些新数据放入缓冲内存中,之后直到缓冲内存中的数据读完前都不会再从真正的流中读取数据。

【案例】

public class TestBufferedInputStream {

    private static final String FILE_PATH = "H:\\c.txt";
    private static byte[] bytes = new byte[1024];

    public static void main(String[] args) {
        testFile();
        testBuffered();
    }

    public static void testFile() {
        try (FileInputStream in = new FileInputStream(FILE_PATH)) {
            long t = System.currentTimeMillis();
            int c;
            while ((c = in.read(bytes)) != -1) {
                //可以将文件内容输出
            }
            System.out.println("FileInputStream耗时:" + (System.currentTimeMillis() - t));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void testBuffered() {
        try (FileInputStream in = new FileInputStream(FILE_PATH);
             BufferedInputStream buffer = new BufferedInputStream(in)) {
            long t = System.currentTimeMillis();
            int c;
            while ((c = buffer.read(bytes)) != -1) {
                //可以将文件内容输出
            }
            System.out.println("BufferedInputStream耗时::" + (System.currentTimeMillis() - t));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

5.3 PushbackInputStream

回退:给了用户第二次读的机会。

在JAVA IO中所有的数据都是采用顺序的读取方式,即对于一个输入流来讲都是采用从头到尾的顺序读取的,如果在输入流中某个不需要的内容被读取进来,则只能通过程序将这些不需要的内容处理掉,为了解决这样的处理问题,在JAVA中提供了一种回退输入流(PushbackInputStreamPushbackReader),可以把读取进来的某些数据重新回退到输入流的缓冲区之中。

使用InputStream要使用read()方法不断读取,是采用顺序的读取方式。

回退流机制

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XVmxAUY5-1590842795961)(F:\Typora\images\20160810144945557.png)]

回退流同样分为字节流和字符流,此时还是以字节流为准。

PushbackInputStream类的常用方法

public PushbackInputStream(InputStream in)  	//构造方法 将输入流放入到回退流之中。
public int read() throws IOException   			//普通  读取数据。
public int read(byte[] b,int off,int len) throws IOException //普通方法 读取指定范围的数据。
public void unread(int b) throws IOException	//普通方法 回退一个数据到缓冲区前面。
public void unread(byte[] b) throws IOException //普通方法 回退一组数据到缓冲区前面
public void unread(byte[] b,int off,int len) throws IOException//普通方法 回退指定范围的一组数据到缓冲区前面。

【案例】

class TestNext {
    public static void main(String[] args) {
        byte[] bytes = new byte[1024];
        byte[] bytesArray = {'H', 'e', 'l', 'l', 'o'};

        ByteArrayInputStream is = new ByteArrayInputStream(bytesArray);
        PushbackInputStream pis = new PushbackInputStream(is, 10);

        try {
            for (int i = 0; i < bytesArray.length; i++) {
                bytes[i] = (byte) pis.read();
                System.out.print((char) bytes[i]);
            }
            System.out.println();

            byte[] b = {'W', 'o', 'r', 'l', 'd'};

            pis.unread(b);

            for (int i = 0; i < bytesArray.length; i++) {
                bytes[i] = (byte) pis.read();
                System.out.print((char) bytes[i]);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

5.4 LineNumberInputStream

已废弃,推荐使用 LineNumberReader

6、SequenceInputStream

SequenceInputStream 可以将两个或多个其他 InputStream 合并为一个。 首先,SequenceInputStream将读取第一个 InputStream 中的所有字节,然后读取第二个 InputStream 中的所有字节。 这就是它被称为 SequenceInputStream 的原因,因为 InputStream 实例是按顺序读取的。

io涉及的类 java io常见类_io涉及的类 java_03

构造方法

  1. SequenceInputStream(InputStream s1, InputStream s2): 通过两个参数初始化新创建的 SequenceInputStream(将按顺序读取这两个参数,先读取 s1,然后读取 s2)
  2. SequenceInputStream(Enumeration<? extends InputStream> e): 通过枚举对象来初始化新创建的 SequenceInputStream,该参数必须是生成运行时类型为 InputStream 对象的Enumeration 型参数。

【案例】

public static void merge3() throws IOException {
    File inFile1 = new File("H:\\day01.txt");
    File inFile2 = new File("H:\\day02.txt");
    File inFile3 = new File("H:\\day03.txt");
    File outFile = new File("H:\\总结.txt");

    FileOutputStream fileOutputStream = new FileOutputStream(outFile);

    FileInputStream fileInputStream1 = new FileInputStream(inFile1);
    FileInputStream fileInputStream2 = new FileInputStream(inFile2);
    FileInputStream fileInputStream3 = new FileInputStream(inFile3);

    //Vector是一个线程安全的类
    Vector<FileInputStream> v = new Vector<>();
    v.add(fileInputStream1);
    v.add(fileInputStream2);
    v.add(fileInputStream3);

    //通过Vector得到枚举
    Enumeration<FileInputStream> e = v.elements();

    SequenceInputStream inputStream = new SequenceInputStream(e);

    byte[] bytes = new byte[1024];
    int length = 0;
    while ((length = inputStream.read(bytes)) != -1) {
        fileOutputStream.write(bytes, 0, length);
    }
    inputStream.close();
    fileOutputStream.close();
}

1、InputStreamReader

InputStreamReader 是字符流Reader的子类,是字节流通向字符流的桥梁。你可以在构造器重指定编码的方式,如果不指定的话将采用底层操作系统的默认编码方式,例如 GBK 等。要启用从字节到字符的有效转换,可以提前从底层流读取更多的字节,使其超过满足当前读取操作所需的字节。一次只读一个字符。

  • InputStreamReader(Inputstream in) //创建一个使用默认字符集的 InputStreamReader。
  • InputStreamReader(Inputstream in,Charset cs) //创建使用给定字符集的 InputStreamReader。
  • InputStreamReader(InputStream in, CharsetDecoder dec) //创建使用给定字符集解码器的 InputStreamReader。
  • InputStreamReader(InputStream in, String charsetName) //创建使用指定字符集的 InputStreamReader。
public class TestInputStreamReader {
    public static void main(String[] args) {
        try (InputStreamReader reader = new InputStreamReader(new FileInputStream("H:\\a.txt"), "utf-8");
             OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream("H:\\b.txt"), "utf-8")) {
            char[] buf = new char[1024];
            int len = 0;
            while ((len = reader.read(buf)) != -1) {
                writer.write(buf, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

2、CharArrayReader

字符数组输入流 CharArrayReader,与 ByteArrayInputStream 相同之处:用于将自带的内置缓存字符数组中的字符读取到程序中。与 ByteArrayInputStream 不同的是:当调用 CharArrayReaderclose 方法是将内置缓存数组 cbuffer 设为 null、而且 CharArrayReader 会抛出 IOException 异常(ensureOpen方法、即当 cbuffer 为 null 时则认为 CharArrayReader 关闭)。方法与使用功能与 ByteArrayInputStream 很相似,说白了区别就在于一个是从内置字节数组中读取字节、一个是从内置字符数组中读取字符。有一点是与 ByteArrayInputStream 不同的地方就是他们的父类的区别:Reader比InputStream多实现一个Readable接口、这个接口要求提供一个方法、是将字符数组读取到指定的缓存数组中、其实完全可以用read(char[] cbuf, int off, int len)来代替实现

构造函数

public CharArrayReader(char buf[])
public CharArrayReader(char buf[], int offset, int length)

一般方法

void close();					// 关闭此流、
void mark(int readAheadLimit);	// 标记当前流读取的位置
void markSupport();				// 检测此流是否支持标记
int read(); 					//读取一个字符、并以整数形式返回
int read(char[] c,int off,int len)//将buf中len个字符读取到下标从off开始的b中,返回读取的字符个数
boolean ready(); 				//查看CharArrayReader是否可读。
void reset(); 					//将此流开始位置重置到最后一次调用mark是流的读取位置
long skip(long n); 				//丢弃buf中n个字符、返回实际丢弃的字符个数

3、StringReader

一个字符流,其源是一个字符串。

StringReaderStringWriter。这两个类都是 ReaderWriter 的装饰类,使它们拥有了对 String 类型数据进行操作的能力。

【源码】

package java.io;
 
public class StringReader extends Reader {
 
    //内置了一个String类型的变量,用于存储读取的内容。
    //因为Reader只需要读取无需对数据进行改变,所以此时一个String类型变量就已经足够了。
    private String str;
    
    //定义了3个int型变量
    //length表示读取的字符串数据的长度,next表示下一个要读取的位置,mark表示标记的位置。
    private int length;
    private int next = 0;
    private int mark = 0;
 
    /**
     * 一个带一个参数的构造方法,传入的参数是一个String类型数据,通过s初始化内置的str和length属性。
     */
    public StringReader(String s) {
        this.str = s;
        this.length = s.length();
    }
 
    /** 
     * 该方法用于判断当前流是否处于开启状态,本质就是检测内置的str是否被赋值。
     */
    private void ensureOpen() throws IOException {
        if (str == null)
            throw new IOException("Stream closed");
    }
 
    /**
     * 每次读取一个字符的read方法,最终返回读取字符的int值。
     */
    public int read() throws IOException {
        synchronized (lock) {
	    //进行操作前,确保当前流处于开启状态。
            ensureOpen();
	    //如果读取的位置,超过了数据的总长度,那么直接返回-1,表示已无数据可读。
            if (next >= length)
                return -1;
	    //正常情况下通过next索引结合String类型的charAt方法,来从str中取出对应的字符数据。
            return str.charAt(next++);
        }
    }
 
    /**
     * 每次读入多个字符的read方法,最终返回实际读取的字符个数。
     * 该方法有3个参数,第一个参数为一个字符数组,用于存储读取的数据,第二和第三个参数为一个int
     * 变量,分别为开始在数组中存储数据的起点和存储数据的长度。
     */
    public int read(char cbuf[], int off, int len) throws IOException {
        synchronized (lock) {
	    //进行操作前需要先判断当前流是否处于开启状态。
            ensureOpen();
	    //对传入的参数进行安全检测,如果不合法则抛出相应异常。
            if ((off < 0) || (off > cbuf.length) || (len < 0) ||
                ((off + len) > cbuf.length) || ((off + len) < 0)) {
                throw new IndexOutOfBoundsException();
            } else if (len == 0) {
                return 0;
            }
	    //如果下一个读取的位置超过了读取的数据的总长度,表示此时已经无数据可读,此时直接返回-1。
            if (next >= length)
                return -1;
	    //定义了一个int型值n,用来接收length-next和len之间的较小值,一般情况下使用len即可,如果len长度超过了数据的总长度,那么就使用length-next的值。
            int n = Math.min(length - next, len);
	    //使用String类的getChars方法,将指定str从next到next+n位置的数据拷贝到传入cbuf中,拷贝位置从off开始。
            str.getChars(next, next + n, cbuf, off);
	    //数据读取拷贝完毕后,将下一个读取的位置向后移位n位,最后返回n,即实际读取的数据长度。
            next += n;
            return n;
        }
    }
 
    /**
     * 该方法用于跳过指定长度的数据。
     */
    public long skip(long ns) throws IOException {
        synchronized (lock) {
	    //进行操作前先确定当前流是否处于开启状态。
            ensureOpen();
	    //如果当前读取的位置已经位于读取数据的末尾或者已经超过了数据总长度,那么直接返回0,因为此时已经无法再跳过数据进行读取了。
            if (next >= length)
                return 0;
            //定义了一个long型数据n用来存放length-next和ns之间的较小值,一般情况下是ns起作用,如果ns超过了当前未读取的数据总长度,那么使用length-next。
            long n = Math.min(length - next, ns);
	    //这里是为了处理传入的ns是负数的情况,当传入的值为负数时,此时读取位置应当向回移动,在上一布操作中如果传入的ns为负数的话,那么此时的n必定是ns
	    //Math.max(-next,n)则保证了只有只有当读取位置大于回读的数量时才可以回读,所以最多之能回退到数据的起点位置。
            n = Math.max(-next, n);
	    //下一次读取的位置移动n个位置,最终将n返回。
            next += n;
            return n;
        }
    }
 
    /**
     * 该方法用于判断当前流是否处于可读状态。
     */
    public boolean ready() throws IOException {
        synchronized (lock) {
        ensureOpen();
        return true;
        }
    }
 
    /**
     * 该方法用于判断当前流是否支持流标记功能。
     */
    public boolean markSupported() {
        return true;
    }
 
    /**
     * 该方法用于在指定位置留下流标记,与reset方法连用,可以试当前读取位置回退到在流中的标记位置
     */
    public void mark(int readAheadLimit) throws IOException {
	//对传入的参数进行安全检测,标记的位置不能小于0,否则抛出相应的异常。
        if (readAheadLimit < 0){
            throw new IllegalArgumentException("Read-ahead limit < 0");
        }
        synchronized (lock) {
	    //在进行操作前,确定当前流处于开启状态。
            ensureOpen();
	    //使用mark变量记录下当前读取的位置。
            mark = next;
        }
    }
 
    /**
     * 该方法用于将当前读取位置回退到流中的标记位置。
     */
    public void reset() throws IOException {
        synchronized (lock) {
	    //在进行操作前,确定当前流是否处于开启状态。然后将当前读取位置回退到mark处。
            ensureOpen();
            next = mark;
        }
    }
 
    /**
     * close方法,关闭当前流,将内置的str指向null。
     */
    public void close() {
        str = null;
    }
}

【案例】

public class TestStringReader {
    public static void main(String[] args) {
        try (StringReader sr = new StringReader("just a test~");
             StringWriter sw = new StringWriter()) {
            int c = 0;
            while ((c = sr.read()) != -1) {
                sw.write(c);
            }
            System.out.println(sw.getBuffer().toString());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

4、PipedReader

PipedReaderPipedWriter 即管道输入流和输出流,可用于线程间管道通信。它们和 PipedInputStream / PipedOutputStream 区别是前者操作的是字符后者是字节。

PipedReader提供的API:

//构造方法
PipedReader(PipedWriter src)    				//使用默认的buf的大小和传入的pw构造pr
PipedReader(PipedWriter src, int pipeSize)      //使用指定的buf的大小和传入的pw构造pr
PipedReader()       							//使用默认大小构造pr
PipedReader(int pipeSize)       				//使用指定大小构造pr

void close()									//关闭流
void connect(PipedWriter src)					//绑定Writer
synchronized boolean ready()					//是否可读
    
synchronized int read()									//读取一个字符
synchronized int read(char cbuf[], int off, int len)	//读取多个字符到cbuf
    
//Writer调用, 向Reader缓冲区写数据
synchronized void receive(int c)
synchronized void receive(char c[], int off, int len)
synchronized void receivedLast()

[案例]

public class TestPipedWriter {
    public static void main(String[] args) {
        PipedWriter writer = new PipedWriter();
        PipedReader reader = new PipedReader();

        Producer producer = new Producer(writer);
        Consumer consumer = new Consumer(reader);

        try {
            writer.connect(reader);
            producer.start();
            consumer.start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

/**
 * 生产者
 * 写线程
 */
class Producer extends Thread {
    private PipedWriter writer = new PipedWriter();

    public Producer(PipedWriter writer) {
        this.writer = writer;
    }

    @Override
    public void run() {
        try {
            final StringBuffer sb = new StringBuffer();
            sb.append("Hello World");
            writer.write(sb.toString());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

/**
 * 消费者
 * 读取线程
 */
class Consumer extends Thread {
    private PipedReader reader = new PipedReader();

    public Consumer(PipedReader reader) {
        this.reader = reader;
    }

    @Override
    public void run() {
        try {
            char[] cbuf = new char[20];
            reader.read(cbuf, 0, cbuf.length);
            System.out.println("管道流中的数据:" + new String(cbuf));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

5、FilterReader

字符过滤输入流、本事是一个抽象类、为所有装饰类提供一个标准、只是简单重写了父类Reader的所有方法、要求子类必须重写核心方法、和提供具有自己特色的方法、这里没有像字节流那样有很多的子类来实现不同的功能、可能是因为字符流本来就是字节流的一种装饰、所以在这里没有必要再对其进行装饰、只是提供一个扩展的接口而已。。。同样也没有示例。

public abstract class FilterReader extends Reader {
 
    /**底层字符输入流*/
    protected Reader in;
 
    /** 使用传入的底层字符输入流创建FilterReader*/
    protected FilterReader(Reader in) {
		super(in);
		this.in = in;
    }
 
    /**读取一个字符*/
    public int read() throws IOException {
    	return in.read();
    }
 
    /**将字符读取到字符数组cbuf中*/
    public int read(char cbuf[], int off, int len) throws IOException {
    	return in.read(cbuf, off, len);
    }
 
    /**跳过底层输入流中的n个字符*/
    public long skip(long n) throws IOException {
    	return in.skip(n);
    }
 
    /**检测此流是否可以读取 */
    public boolean ready() throws IOException {
    	return in.ready();
    }
 
    /**检测此流是否支持mark*/
    public boolean markSupported() {
    	return in.markSupported();
    }
 
    /**标记此流*/
    public void mark(int readAheadLimit) throws IOException {
    	in.mark(readAheadLimit);
    }
 
    /**重置最后一次mark的位置 */
    public void reset() throws IOException {
    	in.reset();
    }
    /**关闭此流 */
    public void close() throws IOException {
    	in.close();
    }
 
}

在字节流中、我们知道有很多具有特殊功能的类都是 FilterInputStreamFilterOutputStream 这两个类中的子类、他们都具有各自的特色、比如 DataInputStreamDataOutputStreamBufferedInputStreamBufferedOutputStreamPrintStream 等、但是在字符流中他们的设计却不是作为Filterxxx的子类来实现、而是直接作为Writer、Reader的子类出现、至于为什么这样设计、不知道、、可能是对字符的操作比较简单、统一、没有像字节那样有许多地方要做特色处理、当然这样说并不是意味着字符流强大、相反还是字节流比较强大、字符流能完成的字节流都能完成、而反过来就不成立了。

5.1 PushbackReader

PushbackReader 与前面介绍的 PushbackInputStream 非常相似,二者都是数据输入流;PushbackReader 继承于 FilterReader,用来装饰其它输出流,PushbackReader 允许应用程序在读取数据的时候,将一些特定的字符推回到流中,下次读取时将会优先读取上一次被推回的字符。

PushbackReader 内部维护了一个缓冲区用于装载被推回的字符,这个缓冲区的大小是可以指定的。

常用API方法

PushbackReader(Reader in, int size);	// 包装一个输入流为回推流,并指定回推缓冲区大小
PushbackReader(Reader in);				// 包装一个输入流为回推流,默认回推缓冲区大小为1

int read();								// 读取1个字符
int read(char cbuf[], int off, int len);// 将len长度的字符读入到cbuf数组中从off开始的位置

// 将c回推进流中,需要注意的是,
// 在cbuf中存放的回推数据,后回推的数据会在先回推数据的前面
// 也就是说,在读取buf中的数据的时候,后回推的数据会先于先回推的数据被读取
void unread(int c);
void unread(char cbuf[], int off,int len);// 将cbuf中从off位置开始的长度为len的字符回推进流中
void unread(char cbuf[]);				// 将cbuf中的字符回推进流中

boolean ready();						// 是否可读
long skip(long n);						// 跳过n个字符
void mark(int readAheadLimit);			// 默认不支持mark和reset,会抛出IOException异常
void reset();							// 默认不支持mark和reset,会抛出IOException异常
boolean     markSupported();			// 默认不支持mark和reset,返回false
void        close();					// 关闭流

PushbackReader 的源码与 PushbackInputStream 是雷同的,只是将内部维护的字节数据换成了字符数组:

  1. PushbackReader 内部维护了一个缓冲区buf用于装载推回的数据,默认大小为1,可以在初始化PushbackReader的时候指定该缓冲区大小。当缓冲区已满时,如果继续推回数据会抛出提示信息为“Pushback buffer overflow”的 IOException
  2. 在每次推回数据的时候,推回的数据会在缓冲区buf中按照从后往前的顺序填充,即先推回的数据会位于缓冲区buf后面,后推回的数据会位于缓冲区buf的前面,因此在读取数据的时候,会先读取后推回的数据再读取先推回的数据。
  3. 每次读取数据,会先读取缓冲区buf中的数据(如果有),再读取原始流中的数据。
  4. 跳过数据的时候,会先跳过缓冲区buf中的数据(如果有),再跳过原始流中的数据。
  5. PushbackReader 默认是不支持 markreset 操作的。

【案例】

public class TestPushbackReader {
    public static void main(String[] args) throws IOException {
        char[] serial = new char[26];
        for (int i = 0; i < serial.length; i++) {
            serial[i] = (char) ('a' + i);
        }
        System.out.println("original chars:" + new String(serial));

        CharArrayReader charArrayReader = new CharArrayReader(serial);

        PushbackReader pushbackReader = new PushbackReader(charArrayReader, 4);

        System.out.println("splitted results:");

        int data;
        while ((data = pushbackReader.read()) != -1) {
            System.out.print(String.valueOf((char) data));
            if ("gntz".indexOf(data) != -1) {
                pushbackReader.unread('\n');
                pushbackReader.unread(']');
                pushbackReader.unread('*');
                pushbackReader.unread('[');
            } else if ("qw".indexOf(data) != -1) {
                pushbackReader.unread(' ');
            }
        }
        pushbackReader.close();
    }
}

6、BufferedReader

BufferedReader 是缓冲字符输入流。它继承于Reader。

BufferedReader 的作用是为其他字符输入流添加一些缓冲功能。

常用API方法

BufferedReader(Reader in)
BufferedReader(Reader in, int size)

void     close()
void     mark(int markLimit)
boolean  markSupported()
int      read()
int      read(char[] buffer, int offset, int length)
String   readLine()
boolean  ready()
void     reset()
long     skip(long charCount)

【源码】

package java.io;

public class BufferedReader extends Reader {

    private Reader in;

    // 字符缓冲区
    private char cb[];
    
    // nChars 是cb缓冲区中字符的总的个数
    // nextChar 是下一个要读取的字符在cb缓冲区中的位置
    private int nChars, nextChar;

    // 表示“标记无效”。它与UNMARKED的区别是:
    // (01) UNMARKED 是压根就没有设置过标记。
    // (02) 而INVALIDATED是设置了标记,但是被标记位置太长,导致标记无效!
    private static final int INVALIDATED = -2;
    // 表示没有设置“标记”
    private static final int UNMARKED = -1;
    // “标记”
    private int markedChar = UNMARKED;
    // “标记”能标记位置的最大长度
    private int readAheadLimit = 0; /* Valid only when markedChar > 0 */

    // skipLF(即skip Line Feed)是“是否忽略换行符”标记
    private boolean skipLF = false;

    // 设置“标记”时,保存的skipLF的值
    private boolean markedSkipLF = false;

    // 默认字符缓冲区大小
    private static int defaultCharBufferSize = 8192;
    // 默认每一行的字符个数
    private static int defaultExpectedLineLength = 80;

    // 创建“Reader”对应的BufferedReader对象,sz是BufferedReader的缓冲区大小
    public BufferedReader(Reader in, int sz) {
        super(in);
        if (sz <= 0)
            throw new IllegalArgumentException("Buffer size <= 0");
        this.in = in;
        cb = new char[sz];
        nextChar = nChars = 0;
    }

    // 创建“Reader”对应的BufferedReader对象,默认的BufferedReader缓冲区大小是8k
    public BufferedReader(Reader in) {
        this(in, defaultCharBufferSize);
    }

    // 确保“BufferedReader”是打开状态
    private void ensureOpen() throws IOException {
        if (in == null)
            throw new IOException("Stream closed");
    }

    // 填充缓冲区函数。有以下两种情况被调用:
    // (01) 缓冲区没有数据时,通过fill()可以向缓冲区填充数据。
    // (02) 缓冲区数据被读完,需更新时,通过fill()可以更新缓冲区的数据。
    private void fill() throws IOException {
        // dst表示“cb中填充数据的起始位置”。
        int dst;
        if (markedChar <= UNMARKED) {
            // 没有标记的情况,则设dst=0。
            dst = 0;
        } else {
            // delta表示“当前标记的长度”,它等于“下一个被读取字符的位置”减去“标记的位置”的差值;
            int delta = nextChar - markedChar;
            if (delta >= readAheadLimit) {
                // 若“当前标记的长度”超过了“标记上限(readAheadLimit)”,
                // 则丢弃标记!
                markedChar = INVALIDATED;
                readAheadLimit = 0;
                dst = 0;
            } else {
                if (readAheadLimit <= cb.length) {
                    // 若“当前标记的长度”没有超过了“标记上限(readAheadLimit)”,
                    // 并且“标记上限(readAheadLimit)”小于/等于“缓冲的长度”;
                    // 则先将“下一个要被读取的位置,距离我们标记的置符的距离”间的字符保存到cb中。
                    System.arraycopy(cb, markedChar, cb, 0, delta);
                    markedChar = 0;
                    dst = delta;
                } else {
                    // 若“当前标记的长度”没有超过了“标记上限(readAheadLimit)”,
                    // 并且“标记上限(readAheadLimit)”大于“缓冲的长度”;
                    // 则重新设置缓冲区大小,并将“下一个要被读取的位置,距离我们标记的置符的距离”间的字符保存到cb中。
                    char ncb[] = new char[readAheadLimit];
                    System.arraycopy(cb, markedChar, ncb, 0, delta);
                    cb = ncb;
                    markedChar = 0;
                    dst = delta;
                }
                // 更新nextChar和nChars
                nextChar = nChars = delta;
            }
        }

        int n;
        do {
            // 从“in”中读取数据,并存储到字符数组cb中;
            // 从cb的dst位置开始存储,读取的字符个数是cb.length - dst
            // n是实际读取的字符个数;若n==0(即一个也没读到),则继续读取!
            n = in.read(cb, dst, cb.length - dst);
        } while (n == 0);

        // 如果从“in”中读到了数据,则设置nChars(cb中字符的数目)=dst+n,
        // 并且nextChar(下一个被读取的字符的位置)=dst。
        if (n > 0) {
            nChars = dst + n;
            nextChar = dst;
        }
    }

    // 从BufferedReader中读取一个字符,该字符以int的方式返回
    public int read() throws IOException {
        synchronized (lock) {
            ensureOpen();
            for (;;) {
                // 若“缓冲区的数据已经被读完”,
                // 则先通过fill()更新缓冲区数据
                if (nextChar >= nChars) {
                    fill();
                    if (nextChar >= nChars)
                        return -1;
                }
                // 若要“忽略换行符”,
                // 则对下一个字符是否是换行符进行处理。
                if (skipLF) {
                    skipLF = false;
                    if (cb[nextChar] == '\n') {
                        nextChar++;
                        continue;
                    }
                }
                // 返回下一个字符
                return cb[nextChar++];
            }
        }
    }

    // 将缓冲区中的数据写入到数组cbuf中。off是数组cbuf中的写入起始位置,len是写入长度
    private int read1(char[] cbuf, int off, int len) throws IOException {
        // 若“缓冲区的数据已经被读完”,则更新缓冲区数据。
        if (nextChar >= nChars) {
            if (len >= cb.length && markedChar <= UNMARKED && !skipLF) {
                return in.read(cbuf, off, len);
            }
            fill();
        }
        // 若更新数据之后,没有任何变化;则退出。
        if (nextChar >= nChars) return -1;
        // 若要“忽略换行符”,则进行相应处理
        if (skipLF) {
            skipLF = false;
            if (cb[nextChar] == '\n') {
                nextChar++;
                if (nextChar >= nChars)
                    fill();
                if (nextChar >= nChars)
                    return -1;
            }
        }
        // 拷贝字符操作
        int n = Math.min(len, nChars - nextChar);
        System.arraycopy(cb, nextChar, cbuf, off, n);
        nextChar += n;
        return n;
    }

    // 对read1()的封装,添加了“同步处理”和“阻塞式读取”等功能
    public int read(char cbuf[], int off, int len) throws IOException {
        synchronized (lock) {
            ensureOpen();
            if ((off < 0) || (off > cbuf.length) || (len < 0) ||
                ((off + len) > cbuf.length) || ((off + len) < 0)) {
                throw new IndexOutOfBoundsException();
            } else if (len == 0) {
                return 0;
            }

            int n = read1(cbuf, off, len);
            if (n <= 0) return n;
            while ((n < len) && in.ready()) {
                int n1 = read1(cbuf, off + n, len - n);
                if (n1 <= 0) break;
                n += n1;
            }
            return n;
        }
    }

    // 读取一行数据。ignoreLF是“是否忽略换行符”
    String readLine(boolean ignoreLF) throws IOException {
        StringBuffer s = null;
        int startChar;

        synchronized (lock) {
            ensureOpen();
            boolean omitLF = ignoreLF || skipLF;

            bufferLoop:
            for (;;) {

                if (nextChar >= nChars)
                    fill();
                if (nextChar >= nChars) { /* EOF */
                    if (s != null && s.length() > 0)
                        return s.toString();
                    else
                        return null;
                }
                boolean eol = false;
                char c = 0;
                int i;

                /* Skip a leftover '\n', if necessary */
                if (omitLF && (cb[nextChar] == '\n'))
                    nextChar++;
                skipLF = false;
                omitLF = false;

            charLoop:
                for (i = nextChar; i < nChars; i++) {
                    c = cb[i];
                    if ((c == '\n') || (c == '\r')) {
                        eol = true;
                        break charLoop;
                    }
                }

                startChar = nextChar;
                nextChar = i;

                if (eol) {
                    String str;
                    if (s == null) {
                        str = new String(cb, startChar, i - startChar);
                    } else {
                        s.append(cb, startChar, i - startChar);
                        str = s.toString();
                    }
                    nextChar++;
                    if (c == '\r') {
                        skipLF = true;
                    }
                    return str;
                }

                if (s == null)
                    s = new StringBuffer(defaultExpectedLineLength);
                s.append(cb, startChar, i - startChar);
            }
        }
    }

    // 读取一行数据。不忽略换行符
    public String readLine() throws IOException {
        return readLine(false);
    }

    // 跳过n个字符
    public long skip(long n) throws IOException {
        if (n < 0L) {
            throw new IllegalArgumentException("skip value is negative");
        }
        synchronized (lock) {
            ensureOpen();
            long r = n;
            while (r > 0) {
                if (nextChar >= nChars)
                    fill();
                if (nextChar >= nChars) /* EOF */
                    break;
                if (skipLF) {
                    skipLF = false;
                    if (cb[nextChar] == '\n') {
                        nextChar++;
                    }
                }
                long d = nChars - nextChar;
                if (r <= d) {
                    nextChar += r;
                    r = 0;
                    break;
                }
                else {
                    r -= d;
                    nextChar = nChars;
                }
            }
            return n - r;
        }
    }

    // “下一个字符”是否可读
    public boolean ready() throws IOException {
        synchronized (lock) {
            ensureOpen();

            // 若忽略换行符为true;
            // 则判断下一个符号是否是换行符,若是的话,则忽略
            if (skipLF) {
                if (nextChar >= nChars && in.ready()) {
                    fill();
                }
                if (nextChar < nChars) {
                    if (cb[nextChar] == '\n')
                        nextChar++;
                    skipLF = false;
                }
            }
            return (nextChar < nChars) || in.ready();
        }
    }

    // 始终返回true。因为BufferedReader支持mark(), reset()
    public boolean markSupported() {
        return true;
    }

    // 标记当前BufferedReader的下一个要读取位置。关于readAheadLimit的作用,参考后面的说明。
    public void mark(int readAheadLimit) throws IOException {
        if (readAheadLimit < 0) {
            throw new IllegalArgumentException("Read-ahead limit < 0");
        }
        synchronized (lock) {
            ensureOpen();
            // 设置readAheadLimit
            this.readAheadLimit = readAheadLimit;
            // 保存下一个要读取的位置
            markedChar = nextChar;
            // 保存“是否忽略换行符”标记
            markedSkipLF = skipLF;
        }
    }

    // 重置BufferedReader的下一个要读取位置,
    // 将其还原到mark()中所保存的位置。
    public void reset() throws IOException {
        synchronized (lock) {
            ensureOpen();
            if (markedChar < 0)
                throw new IOException((markedChar == INVALIDATED)
                                      ? "Mark invalid"
                                      : "Stream not marked");
            nextChar = markedChar;
            skipLF = markedSkipLF;
        }
    }

    public void close() throws IOException {
        synchronized (lock) {
            if (in == null)
                return;
            in.close();
            in = null;
            cb = null;
        }
    }
}

【案例】

public class TestBufferedReader {

    private static final int LEN = 5;

    public static void main(String[] args) {
        testBufferedReader();
    }

    /**
     * BufferedReader的API测试函数
     */
    private static void testBufferedReader() {

        // 创建BufferedReader字符流,内容是ArrayLetters数组
        try {
            File file = new File("H:\\a.txt");
            BufferedReader in =
                    new BufferedReader(
                            new FileReader(file));

            // 从字符流中读取5个字符。“abcde”
            for (int i = 0; i < LEN; i++) {
                // 若能继续读取下一个字符,则读取下一个字符
                if (in.ready()) {
                    // 读取“字符流的下一个字符”
                    int tmp = in.read();
                    System.out.printf("%d : %c\n", i, tmp);
                }
            }

            // 若“该字符流”不支持标记功能,则直接退出
            if (!in.markSupported()) {
                System.out.println("make not supported!");
                return;
            }

            // 标记“当前索引位置”,即标记第6个位置的元素--“f”
            // 1024对应marklimit
            in.mark(1024);

            // 跳过22个字符。
            in.skip(22);

            // 读取5个字符
            char[] buf = new char[LEN];
            in.read(buf, 0, LEN);
            System.out.printf("buf = %s\n", String.valueOf(buf));
            // 读取该行剩余的数据
            System.out.printf("readLine = %s\n", in.readLine());

            // 重置“输入流的索引”为mark()所标记的位置,即重置到“f”处。
            in.reset();
            // 从“重置后的字符流”中读取5个字符到buf中。即读取“fghij”
            in.read(buf, 0, LEN);
            System.out.printf("buf=%s\n", String.valueOf(buf));

            in.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

6.1 LineNumberReader

BufferedReader 提供了下面两个功能:

  1. 在普通 Reader 的基础上,提供了缓冲功能,可以更加高效的读取
  2. 提供了读取一行的功能:readLine()

LineNumberReader 继承自 BufferedReader,并且增加了下面两个功能:

  1. 获取行号:getLineNumber()
  2. 设置行号:setLineNumber()

不过,setLineNumber() 能改变行号,却不能改变读的位置。

public class TestLineNumberReader {
    public static void main(String[] args) {
        try {
            LineNumberReader reader = new LineNumberReader(new FileReader("H:\\a.txt"));
            
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(reader.getLineNumber() + ": " + line);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}