非阻塞通讯(2)

接着上面的话题:
SocketChannel提供了一些接收和发送数据的方法,比如:
read(ByteBuffer buffer):接收数据,把它们存放到参数指定的ByteBuffer中;
write(ByteBuffer buffer):把参数指定的ByteBuffer中的数据发送出去。
ByteBuffer表示字节缓冲区,SocketChannel的read()和write()方法都会操纵ByteBuffer.ByteBuffer类继承于Buffer类。为什么传入的参数要是字节的呢?原因很简单,编码问题,如果你传入的是字符的话,很有可能别人收到后乱码了。所以传入字节,然后再用CharSet类来对收到的字节重新编码。

现在在总结一下重量级人物:Buffer缓冲区
数据输入和输出往往是比较耗时的操作。缓冲区从两个方面提高I/O操作的效率:
1,减少实际的物理读写次数;
2,缓冲区在创建时被分配内存,这块内存区域一直被重用,这可以减少动态分配和回收内存区域的次数;

java.nio包中的BufferedInputStream,BufferedOutputStream,BufferedReader和BufferedWriter在起实现中都运用了缓冲区。
容量:表示该缓冲区可以保存多少数据;
极限:表示缓冲区的当前终点,不能对缓冲区中超过极限的区域进行读写操作。极限是可以修改的,这有利于缓冲区的重用,例如,假定容量为100的缓冲区已经填满了数据,接着程序程序在重用缓冲区时,仅仅将10个新的数据写入缓冲区中从0到10的区域,这时可以将极限设为10,这样就不能读取先前的数据了。极限是一个非负整数,不应该大于容量;

位置:表示缓冲区中下一个读写单元的位置,每次读写缓冲区的数据时,都会改变该值,为下一个读写数据作准备,位置是一个非负整数,不应该大于极限。具体见图:



缓冲区提供了三个方法:
clear():把极限设为容量,再把位置设为0;
flip():把极限设为位置,再把位置设为0;
rewind():不改变极限,把位置设为0,见源码:
  /**
     * 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;
    }

    /**
     * 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;
    }

    /**
     * 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;
    }

看的很清晰吧,Buffer类的remaining()方法返回缓冲区的剩余容量,取值等于极限-位置。Buffer类的compact()方法删除缓冲区从0到当前位置position的内容,然后把从当前位置position到极限的内容复制到0到limit-position的区域内,当前位置position和极限limit的取值也作相应的变化。见图:




   /**
     * Returns the number of elements between the current position and the
     * limit. </p>
     *
     * @return  The number of elements remaining in this buffer
     */
    public final int remaining() {
    return limit - position;
    }
    
java.nio.Buffer类是一个抽象类,不能被实例化,共有8个具体的缓冲区类,其中最基本的缓冲区是ByteBuffer,它存放的数据单元是字节,ByteBuffer类并没有提供公开的构造方法,但是提供了两个获得ByteBuffer实例的静态工厂方法。
1,allocate(int capacity):返回一个ByteBuffer对象,参数capacity指定缓冲区的容量,具体看看源码是怎么实现的:
    public static ByteBuffer allocate(int capacity) {
    if (capacity < 0)
        throw new IllegalArgumentException();
    return new HeapByteBuffer(capacity, capacity);
    }
这个HeapByteBuffer是啥,上网查找了一些资料,对HeapByteBuffer的介绍:HeapByteBuffer分配在jvm的堆(如新生代)上,和其它对象一样,由gc来扫描、回收。
每个HeapByteBuffer内部有一个byte[]存储数据。这个byte[]在构造HeapByteBuffer的时候分配好,长度不会自动增长。看一下HeapByteBuffer类
 // For speed these fields are actually declared in X-Buffer;
    // these declarations are here as documentation
    /*
 
    protected final byte[] hb;
    protected final int offset;
 
    */
 
    HeapByteBuffer(int cap, int lim) {        // package-private
 
    super(-1, 0, lim, cap, new byte[cap], 0);
    /*
    hb = new byte[cap];
    offset = 0;
    */
    }
的确,HeapByteBuffer向内存申请了一个byte[]字节的空间。接着去调用父类ByteBuffer的构造函数:
  ByteBuffer(int mark, int pos, int lim, int cap,    // package-private
         byte[] hb, int offset)
    {
    super(mark, pos, lim, cap);//HeapByteBuffer super(-1, 0, lim, cap, new byte[cap], 0);刚开始lim==cap
    this.hb = hb;//将申请的byte[cap]赋给hb
    this.offset = offset;
    }
把申请到的hb付给父类中的hb...接着在去调用ByteBuffer父类的Buffer的构造方法:
// Creates a new buffer with the given mark, position, limit, and capacity,
    // after checking invariants.
    //
    Buffer(int mark, int pos, int lim, int cap) {    // package-private
    if (cap < 0)
        throw new IllegalArgumentException();
    this.capacity = cap;//把当前的capatity设为我们传入的参数cap
    limit(lim);//设置极限
    position(pos);//设置位置,刚开始设为0
    if (mark >= 0) {
        if (mark > pos)
        throw new IllegalArgumentException();
        this.mark = mark;
    }
    }
整个流程就是分配内存,然后设置capacity,limit,position的值。
下面来看directAllocate(int capacity):返回一个ByteBuffer对象,参数capactity指定缓冲区的容量。该方法返回的缓冲区称为直接缓冲区,它与当前直接缓冲区系统更好的耦合,因此能进一步提高I/O操作的速度。怎么理解呢??
这时就要看看DirectByteBuffer这个类与HeapByteBuffer的区别了,上面说过:HeapByteBuffer分配在jvm的堆(如新生代)上,和其它对象一样,由gc来扫描、回收。
而DirectByteBuffer看到Direct的意思就知道是直接,directAllocate的分配方式:

ByteBuffer 受本机内存而不是 Java 堆支持。直接 ByteBuffer 可以直接传递到本机操作系统库函数,以执行 I/O — 这使这些函数在一些场景中要快得多,因为它们可以避免在 Java 堆与本机堆之间复制数据。

    对于在何处存储直接 ByteBuffer 数据,很容易产生混淆。应用程序仍然在 Java 堆上使用一个对象来编排 I/O 操作,但持有该数据的缓冲区将保存在本机内存中,Java 堆对象仅包含对本机堆缓冲区的引用。非直接 ByteBuffer 将其数据保存在 Java 堆上的 byte[] 数组中。下图展示了直接与非直接 ByteBuffer 对象之间的区别:



直接 ByteBuffer 对象会自动清理本机缓冲区,但这个过程只能作为 Java 堆 GC 的一部分来执行,因此它们不会自动响应施加在本机堆上的压力。GC 仅在 Java 堆被填满,以至于无法为堆分配请求提供服务时发生,或者在 Java 应用程序中显式请求它发生(不建议采用这种方式,因为这可能导致性能问题)。
发生垃圾收集的情形可能是,本机堆被填满,并且一个或多个直接 ByteBuffers 适合于垃圾收集(并且可以被释放来腾出本机堆的空间),但 Java 堆几乎总是空的,所以不会发生垃圾收集。
如果有大量的文件需要以memory mapping的方式映射到内存中,那么DirectByteBuffer明显优于HeapByteBuffer。因为这部分内存不用被gc,所以降低了gc消耗。那么我觉的它的垃圾处理方式应该是由操作系统自己来做的吧。

但是使用directAllocat()方法需要注意的是:直接向系统申请内存空间的方式很快,但是分配直接缓冲区的系统开销很大,因此只有在缓冲区较大并且长期存在,或者需要经常重用时,才使用这种缓冲区。

除了boolean类型之外,每种基本类型都有对应的缓冲区类,包括CharBuffer,DoubleBuffer,FloatBuffer,IntBuffer,LongBuffer,ShortBuffer,这几个缓冲区类都有一个能够返回自身实例的静态工厂方法allocate(int capacity).在CharBuffer中存放的数据单元是为字符,在DoubleBuffer中存放的数据单元是double数据。还有一种缓冲区是MappedByteBuffer,它是ByteBuffer子类,MappedByteBuffer能够把缓冲区和文件的某个区域直接映射。这个类十分强大,但是之前也存在BUG之类的,具体看
http://zhangwenzhuo.iteye.com/blog/163754 这篇文章,讲的非常好,也给出了解决bug的链接。修正BUG挺有趣的。找了个RandomAccessFile作过度
import java.io.*;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;

class TestMemoryMapping
{
    public static void main(String[] args)
    {
        try
        {
            File f = File.createTempFile("Test", null);
            f.deleteOnExit();//虚拟机运行结束则删除文件
            RandomAccessFile raf = new RandomAccessFile(f, "rw");
            raf.setLength(1024);
            FileChannel channel = raf.getChannel();
            MappedByteBuffer buffer = channel.map
(FileChannel.MapMode.READ_WRITE, 0, 1024);
            channel.close();
            raf.close();
            buffer = null;
            // System.gc();
            if (f.delete())
                System.out.println("Temporary file
deleted: "+f);
            else
                System.out.println("Not yet deleted: "+f);
        }
        catch (IOException ex)
        {
            ex.printStackTrace();
        }
    }
}

有所具体缓冲区类都提供了读写缓冲区的方法:
get():相对读。从缓冲区的当前位置读取一个单元的数据,读完后把位置加1;
get(int index):绝对读,从参数index指定的位置读取一个单元的数据;
put():相对写,向缓冲区的当前位置写入一个单元的数据,写完后把位置加1;
put(int index):绝对写,向参数index指定的位置写入一个单元的数据。

发现在整理的过程中,学到更多的了东西,OH-YEAH,继续加油
参考:http://www.hashei.me/tag/directbytebuffer
http://www.udpwork.com/item/6095.html


    
   

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值