`
airu
  • 浏览: 271534 次
  • 性别: Icon_minigender_1
  • 来自: 云南
社区版块
存档分类
最新评论

Java NIO 缓冲区基础二

 
阅读更多
上一篇讲了基本的缓冲区概念,以及NIO的缓冲区属性以及部分操作。
现在让我们在看看缓冲区的一些细节。
1、缓冲区的创建
之前也许你已经看到了。我们使用alocate方法创建一个缓冲区。下面是一个创建方法集合。
public abstract class CharBuffer
extends Buffer implements CharSequence, Comparable {
// This is a partial API listing 
public static CharBuffer allocate(int capacity);
public static CharBuffer wrap (char [] array);
public static CharBuffer wrap (char [] array, int offset, int length);
public final boolean hasArray( ) 
public final char [] array( )
public final int arrayOffset( ) 
}

注意,这些都是静态方法。
wrap方法使用一个现有的数组作为缓冲区备份。这里很重要,因为,其实缓冲区都需要这样一个真正存放数据的地方,我们可以看看如果allocate方法
/**
     * Allocates a new byte buffer.
     *
     * <p> The new buffer's position will be zero, its limit will be its
     * capacity, and its mark will be undefined.  It will have a {@link #array
     * </code>backing array<code>}, and its {@link #arrayOffset </code>array
     * offset<code>} will be zero.
     *
     * @param  capacity
     *         The new buffer's capacity, in bytes
     *
     * @return  The new byte buffer
     *
     * @throws  IllegalArgumentException
     *          If the <tt>capacity</tt> is a negative integer
     */
 public static ByteBuffer allocate(int capacity) {
	if (capacity < 0)
	    throw new IllegalArgumentException();
	return new HeapByteBuffer(capacity, capacity);
    }

HeapByteBuffer又是什么呢?继续跟踪
HeapByteBuffer(int cap, int lim) {		// package-private

	super(-1, 0, lim, cap, new byte[cap], 0);
	/*
	hb = new byte[cap];
	offset = 0;
	*/

    }

这里解释下,HeapByteBuffer是ByteBuffer的一个实现类。她直接使用父类的构造函数来初始化。super第一个参数是mark,-1表示undefined,然后是position,然后是limit,然后是capacity,接下来就是一个真正存放的数组,最后是数组的偏移。

这下可以理解了,缓冲区实际上是由缓冲区的几个属性、操作和一个存放数据的数组(内存)组成的。我们如果改变数组,或者是通过缓冲区改变数组,都会影响双方的。

例如 CharBuffer charBuffer = CharBuffer.wrap(myArray,12,42)
这样的操作,设置了初始的position和limit。我们可以使用clear函数,然后再开始填充缓冲区,这里的缓冲区,并不是从12到42,而是从0到myArray.length

这里,我们还应该看到一个函数,hasArray(),这说明并非所有的缓冲区都包含一个数组,那么什么样的缓冲区不包含数组呢?答案是直接缓冲区。通过allocate和wrap创建的缓冲区都是间接的。直接缓冲区我们待会儿说。但是无论是直接还是间接,事实上缓冲区都必须又一个存放数据的地方。
如果是直接缓冲区,我们是不能获取到数组的,所以array方法不能再直接缓冲区中调用,否则抛出UnsupportedOperationException异常。当然,如果是只读的缓冲区,我们也不能调用array方法或者是arrayOffset方法。
arrayOffset方法是返回作为备份数组的起始下标。这就说明,并非一个数组必须是所有元素都在缓冲区内,而是可以拆分的。但是这里还没讲到,所以,以上提到的缓冲区,如果使用arrayOffset方法,返回都是0.

为了方便,我们的CharBuffer提供了几个方法
public abstract class CharBuffer
extends Buffer implements CharSequence, Comparable {
// This is a partial API listing
public static CharBuffer wrap (CharSequence csq) 
public static CharBuffer wrap (CharSequence csq, int start, int end)
}

我们可以这样用。
CharBuffer charBuffer = CharBuffer.wrap ("Hello World");
这对于字符集码和正则表达式处理都是很方便的。

2、复制缓冲区
这里的复制,仅仅是复制缓冲区,而不是数据。
public abstract class CharBuffer
extends Buffer implements CharSequence, Comparable {
// This is a partial API listing 
public abstract CharBuffer duplicate( ); 
public abstract CharBuffer asReadOnlyBuffer( );
public abstract CharBuffer slice( ); 
}

这三个函数可以完成复制功能。前两个顾名思义,容易理解,最后一个slice,其实也很容易理解,分割缓冲区,这时,起决于position和limit,新的缓冲区将针对原来的数据中的position到limit之间的数据作为新的缓冲区数据。这时,arrayOffset方法返回的就是position的位置了。

例如:要创建一个映射到数组位置12-20(9个元素)的buffer对象:
char[] myBuffer = new char[100];
CharBuffer cb = CharBuffer.wrap(myBuffer);
cb.position(12).limit(21);
CharBuffer sliced = cb.slice();


3、字节缓冲区
虽然我们有很多缓冲区类型,除了boolean型都有一一对应。
但是,ByteBuffer作为一个基本的缓冲区,其作用非同寻常。我们的通道只和字节缓冲区打交道。并且,其他的缓冲区和字节缓冲区都可以转换。
说到这里,我们就不得不理解计算机如何存储数据了。
实际上,内存是按照字节为单位来存放数据的。现在最流行的便是8个位一个字节。我们熟悉的是int占用4个字节,这样,这四个字节如何排列,又成为一个问题。
这个问题已经是硬件问题了,因为不同的硬件有不同的表现。
对于intel处理器,一般都是小端(little endian)
而对于摩托罗拉,Sun的处理器,一般都是大端(big endian)
还有网络字节顺序实际上也是大端的。
那么这两者有什么区别呢?
假设 一个int值 0x01234567 (注意是16进制),内存从左到右增长。地址左小右大
那么他在大端的存放 就类似 :   0x01  0x23  0x45  0x67
那么他在小端的存放,就应该是: 0x67  0x45  0x23  0x01

所以我们的字节操作中有了顺序问题。
package java.nio;
public final class ByteOrder
{
public static final ByteOrder BIG_ENDIAN;
public static final ByteOrder LITTLE_ENDIAN;
public static ByteOrder nativeOrder();
public String toString();
}

在JVM中,实际上我们是使用大端的。
看看关于Order的操作。
public abstract class ByteBuffer extends Buffer
implements Comparable {
// This is a partial API listing 
public final ByteOrder order();
public final ByteBuffer order(ByteOrder bo);
}

order返回系统使用的字节顺序。注意如果系统字节顺序和JVM不一致,可能性能上会有些许慢。

现在知道了字节顺序,那么字节缓冲区转换为其他缓冲区,也就清楚了。
比如,我们有一个4字节缓冲区 0x01  0x23  0x45  0x67
转换为一个IntBuffer,那么,根据字节顺序,如果是大端,直接就是 0x1234567
如果是小端顺序,那么读出来就是 0x67452301
注意:视图缓冲区一旦创建,字节顺序是不可改变的。视图缓冲区后面讲到。

4、直接缓冲区
前面说到,allocate和wrap创建的都是间接缓冲区。间接缓冲区,就是在操作系统和JVM之间其实有一层,我们如果对一个间接缓冲区使用,那么首先是要在操作系统层次创建一个临时缓冲区,然后copy过去,再操作,在删除临时缓冲区。这都很麻烦。
所以,直接缓冲区。也就是在操作系统,而不是JVM进程中创建缓冲区,这就绕过了JVM栈,所以依赖于具体的操作系统。事实上,在操作系统中创建缓冲区,比起在JVM中,更加消耗资源。(因为JVM中是已经划分好的内存,直接设置一下,而操作系统中要通过系统调用,个人理解。)

使用 allocateDirect方法就可以获得直接缓冲区。
public static ByteBuffer allocateDirect (int capacity)
public abstract boolean isDirect( );
isDirect对于直接缓冲区的非字节视图缓冲区,也可能返回true

5、视图缓冲区

可以这样理解,字节缓冲区是所有缓冲类型的基础。我们在通道中,网络中文件流中四处传递字节缓冲区。但是,一旦进入应用,我们就要通过视图缓冲区来解读具体的数据了。
字节缓冲区提供了很多这样的API
public abstract class ByteBuffer
extends Buffer implements Comparable {
// This is a partial API listing 
public abstract CharBuffer asCharBuffer( ); 
public abstract ShortBuffer asShortBuffer( ); 
public abstract IntBuffer asIntBuffer( ); 
public abstract LongBuffer asLongBuffer( ); 
public abstract FloatBuffer asFloatBuffer( );
public abstract DoubleBuffer asDoubleBuffer( );
}

前面提到过从ByteBuffer到IntBuffer的转换。其实这种转换,是一种解释包装。
所以需要提供一个顺序,然后按照顺序和类型,转换成另外一种视图,而原始数据其实是不变的。
例如,我们可以这样;
ByteBuffer byteBuffer = 
    ByteBuffer.allocate(7).order(ByteOrder.BIG_ENDIAN);
CharBuffer charBuffer = byteBuffer.asCharBuffer();

注意,java中char是占两个字节的。我们的转换可以看下图:



新的charBuffer视图其实还是使用的原来的缓冲区的数据,只是这时的元素变成了2个字节的char了。

看一段代码:
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.CharBuffer;


public class CharBufferView {
	public static void print(Buffer buffer){
		System.out.println("pos="+buffer.position()+", limit="+ 
				buffer.limit() + ", capacity="+buffer.capacity()
				+":'" + buffer.toString()+"'");
	}
	public static void main(String[] args)
		throws Exception{
		ByteBuffer byteBuffer = ByteBuffer.allocate(8).order(ByteOrder.BIG_ENDIAN);
		CharBuffer charBuffer = byteBuffer.asCharBuffer();
		
		byteBuffer.put(0,(byte)0);
		byteBuffer.put(1,(byte)'H');
		byteBuffer.put(2,(byte)0);
		byteBuffer.put(3,(byte)'e');
		byteBuffer.put(4,(byte)0);
		byteBuffer.put(5,(byte)'l');
		byteBuffer.put(6,(byte)0);
                  byteBuffer.put(7,(byte)'l');
                  byteBuffer.put(8,(byte)0);
                  byteBuffer.put(9,(byte)'o');
		
		CharBufferView.print(byteBuffer);
		CharBufferView.print(charBuffer);
		
	}
}

结果:
pos=0, limit=10, capacity=10:'java.nio.HeapByteBuffer[pos=0 lim=10 cap=10]'
pos=0, limit=5, capacity=5:'Hello'

可以看到,转换为视图后,limit和capacity都变了。

6、数据元素视图

上面的视图使用了一个视图类来转换。在ByteBuffer中,我们可以直接获取不同视图的元素。
public abstract class ByteBuffer
extends Buffer implements Comparable {
public abstract char getChar( ); 
public abstract char getChar (int index); 
public abstract short getShort( ); 
public abstract short getShort (int index); 
public abstract int getInt( ); 
public abstract int getInt (int index); 
public abstract long getLong( ); 
public abstract long getLong (int index); 
public abstract float getFloat( );
public abstract float getFloat (int index);
public abstract double getDouble( ); 
public abstract double getDouble (int index); 
public abstract ByteBuffer putChar (char value); 
public abstract ByteBuffer putChar (int index, char value); 
public abstract ByteBuffer putShort (short value); 
public abstract ByteBuffer putShort (int index, short value); 
public abstract ByteBuffer putInt (int value); 
public abstract ByteBuffer putInt (int index, int value); 
public abstract ByteBuffer putLong (long value); 
public abstract ByteBuffer putLong (int index, long value); 
public abstract ByteBuffer putFloat (float value); 
public abstract ByteBuffer putFloat (int index, float value); 
public abstract ByteBuffer putDouble (double value); 
public abstract ByteBuffer putDouble (int index, double value); 
}

这些操作一看便知。前面也提过其实就是根据大端小端的字节顺序,和这些数据类型的长度来组织数据。
这里需要注意的是:如果get或者是put时,数据不足,或者空间不够,都会发生异常。
如果不是用putXX的方法,直接getXX,那么可能产生意想不到的问题。


7、无符号数存取
对于java来说,没有无符号数处理(除了char)。为此,书中给出一个程序,用来处理无符号数缓冲区。
其基本原理是,使用比要存取的数据类型更大的数据类型来存放这个数,同时使用与运算强制设置符号位为0,剩余的位就可以作为数据而不是符号位了。
public class Unsigned
{
public static short getUnsignedByte (ByteBuffer bb) {
    return ((short)(bb.get( ) & 0xff)); 
}
public static void putUnsignedByte (ByteBuffer bb, int value) {
    bb.put ((byte)(value & 0xff));
 }
...
}

这里省略了其他部分。这样我们就可以看到带符号的

8、内存映射缓冲区
映射缓冲区是与文件存储的数据元素关联的字节缓冲区,它通过内存映射来访问。映射缓
冲区通常是直接存取内存的,只能通过 FileChannel 类创建。映射缓冲区的用法和直接缓冲
区类似,但是 MappedByteBuffer 对象具有许多文件存取独有的特征。
  • 大小: 20.5 KB
0
2
分享到:
评论

相关推荐

    java NIO.zip

    2. **缓冲区(Buffers)**: 缓冲区是NIO中用于存储数据的容器,它们是固定大小的,并且具有特定类型的。Java NIO提供了诸如ByteBuffer、CharBuffer、IntBuffer等类型,对应于不同的数据类型。缓冲区具有读写位置,...

    java NIO 视频教程

    Java NIO: Channels and Buffers(通道和缓冲区) 标准的IO基于字节流和字符流进行操作的,而NIO是基于通道(Channel)和缓冲区(Buffer)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。 ...

    Java NIO英文高清原版

    2. **缓冲区(Buffer)**:在NIO中,数据被存储在缓冲区对象中。缓冲区提供了一种高效的方式管理内存,可以方便地进行读写操作。缓冲区有固定大小,一旦写满,需要清空或者翻转才能继续写入。同样,读取数据也需要将...

    nio学习demo 处理因缓冲区满导致写入失败问题

    本示例主要关注如何使用NIO解决“沾包”问题以及处理因缓冲区满导致的写入失败问题。首先,我们需要理解这两个问题的背景。 "沾包"问题通常发生在网络通信中,特别是TCP协议中。由于TCP是流式传输,不保留消息边界...

    Java语言基础教程-Java NIO流篇2

    缓冲区是NIO的核心,它允许我们批量处理数据,提高了效率。 接下来,我们来看NIO的另一重要概念——通道(Channel)。通道是连接到I/O设备(如文件、网络套接字等)的桥梁,它可以读取或写入数据。通道是双向的,...

    java NIO技巧及原理

    2. **缓冲区(Buffer)**:用于在通道和应用程序之间存储数据,提供了更高效的访问方式。 3. **选择器(Selector)**:允许单个线程监控多个通道,进行多路复用,提高并发处理能力。 **Java NIO的非阻塞特性:** 非...

    JavaNIO.pdf

    Java NIO(Non-blocking I/O)是Java平台中的一种I/O处理方式,它提供了面向缓冲区的I/O处理机制,可以实现高性能、高效的I/O操作。 缓冲区(Buffer) 缓冲区是Java NIO中非常重要的一个概念,它是特定基本类型...

    java nio 读文件

    NIO的主要特点是面向缓冲区,非阻塞I/O,以及选择器,这些特性使得NIO在处理大量并发连接时表现出更高的效率。在本篇文章中,我们将深入探讨Java NIO如何读取文件。 一、NIO的基本概念 1. 缓冲区(Buffer):NIO的...

    Java NIO.pdf

    #### 二、Java NIO 的核心组件 - **缓冲区(Buffer)**:用于存储不同数据类型的数据,根据数据类型的不同(如基本数据类型 int、long、char 等),提供了多种缓冲区。 - `ByteBuffer` - `CharBuffer` - `...

    一个java NIO的例子

    2. **缓冲区(Buffer)**:缓冲区是数据存储的容器,所有从通道读取的数据都会先被放入缓冲区,然后从缓冲区写入通道。Java NIO提供了字节、字符、短整型、整型、长整型、浮点型和双精度浮点型等类型的缓冲区。缓冲...

    java nio 包读取超大数据文件

    #### 二、Java NIO关键组件 Java NIO的核心组件包括: - **Channels**:用于表示IO源或目标的一个连接点。 - **Buffers**:用来存储数据的对象,是数据传输的载体。 - **Selectors**:多路复用器,用于监控多个...

    java NIO实例

    3. **Buffer(缓冲区)**:在NIO中,数据读写都是通过缓冲区进行的。缓冲区是一个可以容纳特定类型数据(如字节、字符、整数等)的容器,它提供了对数据的高效访问和管理。 4. **FileChannel**:用于文件的读写,...

    Java NIO测试示例

    Java NIO提供了ByteBuf、CharBuf、ShortBuf、IntBuf、LongBuf、FloatBuf和DoubleBuf等不同类型的缓冲区,它们都有统一的API,如put()用于写入数据,get()用于读取数据,clear()用于清空缓冲区,flip()用于切换读写...

    Java语言基础教程-Java NIO流篇1

    接下来,我们进入【第2节】 Java NIO流-缓冲区(Buffer)。缓冲区是NIO中的核心组件,它是直接与通道(Channel)交互的地方。在Java NIO中,数据不是直接从通道读取到应用程序,也不是直接从应用程序写入通道,而是...

    Java NIO Socket基本

    2. **缓冲区(Buffer)**:NIO的核心是缓冲区,它提供了一种存储和操作数据的高效方式。缓冲区类型包括ByteBuffer、CharBuffer、IntBuffer、DoubleBuffer等,它们都继承自`java.nio.Buffer`。 3. **选择器...

Global site tag (gtag.js) - Google Analytics