第三章:使用NIO

与流式的IO不同,NIO是基于块的,以块为单位处理数据。NIO中最为重要的两个组件,通道(channel)和缓冲(buffer)。缓冲是一块连续的内存块,是NIO读写数据的中转地。通道表示缓冲数据的源头或目的地,用于向缓冲读取或者写入数据,是访问缓冲的接口。

Buffer缓冲区

在这里插入图片描述

重要参数

3个重要参数:position、capacity、limit。
各参数含义:

参数写模式读模式
position(位置)当前缓冲区的位置,将从position的下一个位置写数据当前缓冲区读取的位置,将从此位置后,读取数据
capacity(容量)缓冲区的总容量上限缓冲区的总容量上限
limit(限制)缓冲区的实际上限,总是小于等于容量,通常情况下和容量相等代表可读取的总容量,和上次写入的数据量相等

相关操作

创建Buffer

  1. allocate方法从堆中分配缓冲区
ByteBuffer buffer = ByteBuffer.allocate(100);
  1. 从一个既有数组中创建缓冲区
byte[] arr = new byte[100];
ByteBuffer buffer = ByteBuffer.wrap(arr);

重置和清空缓冲区

rewind(),为提取Buffer的有效数据作准备

	/**
     * Rewinds this buffer.  The position is set to zero and the mark is
     * discarded.
     *
     * <p> Invoke this method before a sequence of channel-write or <i>get</i>
     * operations, assuming that the limit has already been set
     * appropriately.  For example:
     *
     * <blockquote><pre>
     * out.write(buf);    // Write remaining data
     * buf.rewind();      // Rewind buffer
     * buf.get(array);    // Copy data into array</pre></blockquote>
     *
     * @return  This buffer
     */
    public final Buffer rewind() {
        position = 0;
        mark = -1;
        return this;
    }

clear(),为重新写Buffer作准备

 	/**
     * Clears this buffer.  The position is set to zero, the limit is set to
     * the capacity, and the mark is discarded.
     *
     * <p> Invoke this method before using a sequence of channel-read or
     * <i>put</i> operations to fill this buffer.  For example:
     *
     * <blockquote><pre>
     * buf.clear();     // Prepare buffer for reading
     * in.read(buf);    // Read data</pre></blockquote>
     *
     * <p> This method does not actually erase the data in the buffer, but it
     * is named as if it did because it will most often be used in situations
     * in which that might as well be the case. </p>
     *
     * @return  This buffer
     */
    public final Buffer clear() {
        position = 0;
        limit = capacity;
        mark = -1;
        return this;
    }

flip(),通常在读写转换时使用

	 /**
     * Flips this buffer.  The limit is set to the current position and then
     * the position is set to zero.  If the mark is defined then it is
     * discarded.
     *
     * <p> After a sequence of channel-read or <i>put</i> operations, invoke
     * this method to prepare for a sequence of channel-write or relative
     * <i>get</i> operations.  For example:
     *
     * <blockquote><pre>
     * buf.put(magic);    // Prepend header
     * in.read(buf);      // Read data into rest of buffer
     * buf.flip();        // Flip buffer
     * out.write(buf);    // Write header + data to channel</pre></blockquote>
     *
     * <p> This method is often used in conjunction with the {@link
     * java.nio.ByteBuffer#compact compact} method when transferring data from
     * one place to another.  </p>
     *
     * @return  This buffer
     */
    public final Buffer flip() {
        limit = position;
        position = 0;
        mark = -1;
        return this;
    }

读写缓冲区

public byte get();
public ByteBuffer put(byte b);
public byte get(int index);
public ByteBuffer put(int index, byte b);
public ByteBuffer get(byte[] dst);
……

标志缓冲区

mark,相当于书签,使用reset恢复到mark位置。

	/**
     * Sets this buffer's mark at its position.
     *
     * @return  This buffer
     */
    public final Buffer mark() {
        mark = position;
        return this;
    }

复制缓冲区

复制缓冲区是指以原缓冲区为基础,生成一个一样的新缓冲区。新的缓冲区与原缓冲区共享相同的内存数据。并且,对任意一方的数据改动都是相互可见的,维护各自的position、limit和mark

/**
     * Creates a new byte buffer that shares this buffer's content.
     *
     * <p> The content of the new buffer will be that of this buffer.  Changes
     * to this buffer's content will be visible in the new buffer, and vice
     * versa; the two buffers' position, limit, and mark values will be
     * independent.
     *
     * <p> The new buffer's capacity, limit, position, and mark values will be
     * identical to those of this buffer.  The new buffer will be direct if,
     * and only if, this buffer is direct, and it will be read-only if, and
     * only if, this buffer is read-only.  </p>
     *
     * @return  The new byte buffer
     */
public ByteBuffer duplicate()

duplicate示例

		ByteBuffer aBuffer = ByteBuffer.allocate(15);
        for (int i = 0; i < 10; i++) {
            aBuffer.put((byte)i);
        }
        ByteBuffer bBuffer = aBuffer.duplicate();
        System.out.println(aBuffer);
        System.out.println(bBuffer);

        bBuffer.flip();
        System.out.println(aBuffer);
        System.out.println(bBuffer);

        bBuffer.put((byte)100);
        System.out.println(aBuffer.get(0));
        System.out.println(bBuffer.get(0));
  Output:
  		java.nio.HeapByteBuffer[pos=10 lim=15 cap=15]
		java.nio.HeapByteBuffer[pos=10 lim=15 cap=15]
		java.nio.HeapByteBuffer[pos=10 lim=15 cap=15]
		java.nio.HeapByteBuffer[pos=0 lim=10 cap=15]
		100
		100

只读缓冲区

使用缓冲区对象的asReadOnlyBuffer方法得到一个与当前缓冲区一致的,并且共享内存数据的只读缓冲区。只读缓冲区对于数据安全非常有用。

	ByteBuffer buffer = ByteBuffer.allocate(15);
    for (int i = 0; i < 10; i++) {
        buffer.put((byte)i);
    }
    ByteBuffer readOnly = buffer.asReadOnlyBuffer();
    readOnly.flip();
    while (readOnly.hasRemaining()){
        System.out.print(readOnly.get() + " ");
    }
    System.out.println();
    buffer.put(2,(byte)20);
    readOnly.flip();
    while (readOnly.hasRemaining()){
        System.out.print(readOnly.get() + " ");
    }
Output:
	0 1 2 3 4 5 6 7 8 9 
	0 1 20 3 4 5 6 7 8 9     

	readOnly.put(2,(byte)20);
	Exception in thread "main" java.nio.ReadOnlyBufferException
		at java.nio.HeapByteBufferR.put(HeapByteBufferR.java:181)
		at Main.main(Main.java:21)  

文件映射内存

wanghaodeMacBook-Pro:~ wanghao$ pwd
/Users/wanghao
wanghaodeMacBook-Pro:~ wanghao$ cat mapfile.txt 
hello world
	RandomAccessFile raf = new RandomAccessFile("/Users/wanghao/mapfile.txt", "rw");
	FileChannel channel = raf.getChannel();
	//将文件映射到内存中
	MappedByteBuffer mbb = channel.map(FileChannel.MapMode.READ_WRITE, 0, raf.length());
	while (mbb.hasRemaining()){
	    System.out.print((char)mbb.get());
	}
	mbb.put(2, (byte)98); //修改文件
	raf.close();
Output:
    hello world
wanghaodeMacBook-Pro:~ wanghao$ cat mapfile.txt 
heblo world

Scattering和Gathering

scattering是将数据读入一组buffer中,gathering是将数据写入一组buffer中。通过ScatteringByteChannel和GatheringByteChannel接口提供相关操作。

public interface ScatteringByteChannel
    extends ReadableByteChannel
{
    public long read(ByteBuffer[] dsts, int offset, int length)
        throws IOException;
    public long read(ByteBuffer[] dsts) throws IOException;
}

public interface GatheringByteChannel
    extends WritableByteChannel
{
	public long write(ByteBuffer[] srcs, int offset, int length)
        throws IOException;
    public long write(ByteBuffer[] srcs) throws IOException;     
}

在散射读取中,通道依次填充每个缓冲区。填充一个后,就开始填充下一个。从某种意义上,缓冲区数组就是一个大的缓冲区。
散射/聚集IO对于处理结构化的数据非常有用。预先构造号大小合适的Buffer对象。

聚集写入示例

	ByteBuffer headBuffer = ByteBuffer.wrap("head".getBytes("utf-8"));
    ByteBuffer contentBuffer = ByteBuffer.wrap("content".getBytes("utf-8"));
    ByteBuffer[] buffers = new ByteBuffer[]{headBuffer, contentBuffer};
    File file = new File("/Users/wanghao/sg.txt");
    if(!file.exists()){
        file.createNewFile();
    }
    FileOutputStream fileOutputStream = new FileOutputStream(file);
    FileChannel fileChannel = fileOutputStream.getChannel();
    fileChannel.write(buffers);
    fileOutputStream.close();

wanghaodeMacBook-Pro:~ wanghao$ cat sg.txt 
headcontent

散射读取示例:

	ByteBuffer headBuffer = ByteBuffer.allocate(4);
    ByteBuffer contentBuffer = ByteBuffer.allocate(7);
    ByteBuffer[] buffers = new ByteBuffer[]{headBuffer, contentBuffer};
    File file = new File("/Users/wanghao/sg.txt");
    FileInputStream fileInputStream = new FileInputStream(file);
    FileChannel fileChannel = fileInputStream.getChannel();
    fileChannel.read(buffers);
    System.out.println(new String(buffers[0].array(), "utf-8"));
    System.out.println(new String(buffers[1].array(), "utf-8"));
    fileInputStream.close();

Output:
	head
	content   

MapperByteBuffer性能对比

分别使用IO流、ByteBuffer和MappedByteBuffer测试文件读写耗时。
使用IO流的方式:

 	long start = System.currentTimeMillis();
    DataOutputStream dataOutputStream = new DataOutputStream(
            new BufferedOutputStream(new FileOutputStream("/Users/wanghao/io.txt")));
    for (int i = 0; i < 10000000; i++) {
        dataOutputStream.writeInt(i);
    }
    if(dataOutputStream != null){
        dataOutputStream.close();
    }
    System.out.println("write cost time:" + (System.currentTimeMillis() -start));
    start = System.currentTimeMillis();
    DataInputStream dataInputStream = new DataInputStream(
            new BufferedInputStream(new FileInputStream("/Users/wanghao/io.txt")));
    for (int i = 0; i < 10000000; i++) {
        dataInputStream.readInt();
    }
    if(dataInputStream != null){
        dataInputStream.close();
    }
    System.out.println("read cost time:" + (System.currentTimeMillis() -start));

Output:
	write cost time:1030
	read cost time:657

使用Buffer

	public static void main(String[] args) throws  Exception{
        long start = System.currentTimeMillis();
        FileOutputStream fileOutputStream = new FileOutputStream(new File("/Users/wanghao/buffer.txt"));
        FileChannel fileChannel = fileOutputStream.getChannel();
        ByteBuffer buffer = ByteBuffer.allocate(10000000*4);
        for (int i = 0; i < 10000000; i++) {
            buffer.put(int2byte(i));
        }
        buffer.flip();
        fileChannel.write(buffer);
        System.out.println("write cost time:" + (System.currentTimeMillis() -start));

        start = System.currentTimeMillis();
        FileInputStream fileInputStream = new FileInputStream(new File("/Users/wanghao/buffer.txt"));
        FileChannel fileChannel1 = fileInputStream.getChannel();
        ByteBuffer buffer1 = ByteBuffer.allocate(10000000*4);
        fileChannel1.read(buffer1);
        buffer1.flip();
        while (buffer1.hasRemaining()){
            byte2int(buffer1.get(), buffer1.get(), buffer1.get(), buffer1.get());
        }
        System.out.println("read cost time:" + (System.currentTimeMillis() -start));
    }
    public static byte[] int2byte(int res){
        byte[] targets = new byte[4];
        targets[3] = (byte)(res & 0xff);
        targets[2] = (byte)((res >> 8) & 0xff);
        targets[1] = (byte)((res >> 16) & 0xff);
        targets[0] = (byte)((res >> 24) & 0xff);
        return targets;
    }
    public static int byte2int(byte b1,byte b2, byte b3,byte b4){
        return ((b1 & 0xff) << 24) | ((b2 & 0xff) << 16) | ((b3 & 0xff) << 8) | (b4 & 0xff);
    }

Output:
	write cost time:531
	read cost time:74

使用MappedByteBuffer,将文件直接映射到内存

		long start = System.currentTimeMillis();
        FileChannel fileChannel = new RandomAccessFile("/Users/wanghao/mbp.txt","rw").getChannel();
        IntBuffer intBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, 10000000*4).asIntBuffer();
        for (int i = 0; i < 10000000; i++) {
            intBuffer.put(i);
        }
        if(fileChannel != null){
            fileChannel.close();
        }
        System.out.println("write cost time:" + (System.currentTimeMillis() -start));

        start = System.currentTimeMillis();
        FileChannel fileChannel1 = new FileInputStream("/Users/wanghao/mbp.txt").getChannel();
        IntBuffer intBuffer1 = fileChannel1.map(FileChannel.MapMode.READ_ONLY, 0, 10000000*4).asIntBuffer();
        while (intBuffer1.hasRemaining()){
            intBuffer1.get();
        }
        if(fileChannel1 != null){
            fileChannel1.close();
        }
        System.out.println("read cost time:" + (System.currentTimeMillis() -start));
Output:
	write cost time:139
	read cost time:41
耗时/ioStreamBufferMappedByteBuffer
1030531139
6577441

直接内存

DirectBuffer,直接访问系统物理内存,不占用堆空间。普通的Buffer内存在JVM堆空间上分配。
申请方法:ByteBuffer.allocateDirect(capacity)

访问性能

使用DirectBuffer

        ByteBuffer buffer = ByteBuffer.allocateDirect(500);
        long start = System.currentTimeMillis();
        for (int i = 0; i < 1000000; i++) {
            for (int j = 0; j < 100; j++) {
                buffer.putInt(j);
            }
            buffer.flip();
            for (int j = 0; j < 100; j++) {
                buffer.getInt();
            }
            buffer.clear();
        }
        System.out.println("cost time:" + (System.currentTimeMillis() -start));
Output:
	cost time:282      

使用普通Buffer

        ByteBuffer buffer = ByteBuffer.allocate(500);
        long start = System.currentTimeMillis();
        for (int i = 0; i < 1000000; i++) {
            for (int j = 0; j < 100; j++) {
                buffer.putInt(j);
            }
            buffer.flip();
            for (int j = 0; j < 100; j++) {
                buffer.getInt();
            }
            buffer.clear();
        }
        System.out.println("cost time:" + (System.currentTimeMillis() -start));
 Output:
 	cost time:467

通过对比,DirectBuffer比普通Buffer速度快了近1倍。

创建销毁性能

DirectBuffer的创建和销毁比普通Buffer慢

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值