Java网络编程从入门到精通(33):非阻塞I/O的缓冲区(Buffer)

本文涉及的产品
数据传输服务 DTS,数据迁移 small 3个月
推荐场景:
MySQL数据库上云
数据传输服务 DTS,数据同步 small 3个月
推荐场景:
数据库上云
数据传输服务 DTS,数据同步 1个月
简介: 本文为原创,如需转载,请注明作者和出处,谢谢! 上一篇:Java网络编程从入门到精通(32):一个非阻塞I/O的例子 如果将同步I/O方式下的数据传输比做数据传输的零星方式(这里的零星是指在数据传输的过程中是以零星的字节方式进行的),那么就可以将非阻塞I/O方式下的数据传输比做数据传输的集装箱方式(在字节和低层数据传输之间,多了一层缓冲区,因此,可以将缓冲区看做是装载字节的集装箱)。

本文为原创,如需转载,请注明作者和出处,谢谢!

上一篇:Java网络编程从入门到精通(32):一个非阻塞I/O的例子

如果将同步I/O方式下的数据传输比做数据传输的零星方式(这里的零星是指在数据传输的过程中是以零星的字节方式进行的),那么就可以将非阻塞I/O方式下的数据传输比做数据传输的集装箱方式(在字节和低层数据传输之间,多了一层缓冲区,因此,可以将缓冲区看做是装载字节的集装箱)。大家可以想象,如果我们要运送比较少的货物,用集装箱好象有点不太合算,而如果要运送上百吨的货物,用集装箱来运送的成本会更低。在数据传输过程中也是一样,如果数据量很小时,使用同步I/O方式会更适合,如果数据量很大时(一般以G为单位),使用非阻塞I/O方式的效率会更高。因此,从理论上说,数据量越大,使用非阻塞I/O方式的单位成本就会越低。产生这种结果的原因和缓冲区的一些特性有着直接的关系。在本节中,将对缓冲区的一些主要特性进行讲解,使读者可以充分理解缓冲区的概念,并能通过缓冲区来提高程序的执行效率。

创建缓冲区

Java提供了七个基本的缓冲区,分别由七个类来管理,它们都可以在java.nio包中找到。这七个类如下所示:

  •  ByteBuffer   
  • ShortBuffer
  • IntBuffer
  • CharBuffer
  • FloatBuffer
  • DoubleBuffer
  • LongBuffer

这七个类中的方法类似,只是它们的返回值或参数和相应的简单类型相对应,如ByteBuffer类的get方法返回了byte类型的数据,而put方法需要一个byte类型的参数。在CharBuffer类中的getput方法返回和传递的数据类型就是char。这七个类都没有public构造方法,因此,它们不能通过new来创建相应的对象实例。这些类都可以通过两种方式来创建相应的对象实例。

1.        通过静态方法allocate来创建缓冲区。

这七类都有一个静态的allocate方法,通过这个方法可以创建有最大容量限制的缓冲区对象。allocate的定义如下:

ByteBuffer类中的allocate方法:

public   static  ByteBuffer allocate( int  capacity)

IntBuffer类中的allocate方法:

public   static  IntBuffer allocate( int  capacity)

其他五个缓冲区类中的allocate 方法定义和上面的定义类似,只是返回值的类型是相应的缓冲区类。

allocate方法有一个参数capacity,用来指定缓冲区容量的最大值。capacity的不能小于0,否则会抛出一个IllegalArgumentException异常。使用allocate来创建缓冲区,并不是一下子就分配给缓冲区capacity大小的空间,而是根据缓冲区中存储数据的情况来动态分配缓冲区的大小(实际上,在低层Java采用了数据结构中的堆来管理缓冲区的大小),因此,这个capacity可以是一个很大的值,如1024*10241M)。allocate的使用方法如下:

ByteBuffer byteBuffer  =  ByteBuffer.allocate( 1024 );
IntBuffer intBuffer 
=  IntBuffer.allocate( 1024 );

    在使用allocate创建缓冲区时应用注意,capacity的含义随着缓冲区的不同而不同。如创建字节缓冲区时,capacity指的是字节数。而在创建整型(int)缓冲区时,capacity指的是int型值的数目,如果转换成字数,capacity的值应该乘4。如上面代码中的intBuffer缓冲区最大可容纳的字节数是1024*4 = 4096个。

2.        通过静态方法wrap来创建缓冲区。

使用allocate方法可以创建一个空的缓冲区。而wrap方法可以利用已经存在的数据来创建缓冲区。wrap方法可以将数组直接转换成相应类型的缓冲区。wrap方法有两种重载形式,它们的定义如下:

ByteBuffer类中的wrap方法:

public   static  ByteBuffer wrap( byte [] array)
public   static  ByteBuffer wrap( byte [] array,  int  offset,  int  length)

IntBuffer类中的wrap方法:

public   static  IntBuffer wrap( byte [] array)
public   static  IntBuffer wrap( byte [] array,  int  offset,  int  length)

其他五个缓冲区类中的wrap 方法定义和上面的定义类似,只是返回值的类型是相应的缓冲区类。

wrap方法中的array参数是要转换的数组(如果是其他的缓冲区类,数组的类型就是相应的简单类型,如IntBuffer类中的wrap方法的array就是int[]类型)。offset是要转换的子数组的偏移量,也就是子数组在array中的开始索引。length是要转换的子数组的长度。利用后两个参数可以将array数组中的一部分转换成缓冲区对象。它们的使用方法如下:

byte [] myByte  =   new   byte [] {  1 2 3  };
int [] myInt  =   new   int [] {  1 2 3 4  };
ByteBuffer byteBuffer 
=  ByteBuffer.wrap(myByte);
IntBuffer intBuffer 
=  IntBuffer.wrap(myInt,  1 2 );

可以通过缓冲区类的capacity方法来得到缓冲区的大小。capacity方法的定义如下:

public   final   int  capacity()

如果使用allocate方法来创建缓冲区,capacity方法的返回值就是capacity参数的值。而使用wrap方法来创建缓冲区,capacity方法的返回值是array数组的长度,但要注意,使用wrap来转换array的字数组时,capacity的长度仍然是原数组的长度,如上面代码中的intBuffer缓冲区的capacity值是4,而不是2

除了可以将数组转换成缓冲区外,也可以通过缓冲区类的array方法将缓冲区转换成相应类型的数组。IntBuffer类的array方法的定义方法如下(其他缓冲区类的array的定义类似):

public   final   int [] array()

    下面的代码演示了如何使用array方法将缓冲区转换成相应类型的数组。


int [] myInt  =   new   int [] {  1 2 3 4 5 6  };
IntBuffer intBuffer 
=  IntBuffer.wrap(myInt,  1 3 );
for  ( int  v : intBuffer.array())
    System.out.print(v 
+   "   " );

在执行上面代码后,我们发现输出的结果是1 2 3 4 5 6,而不是2 3 4。这说明在将子数组转换成缓冲区的过程中实际上是将整个数组转换成了缓冲区,这就是用wrap包装子数组后,capacity的值仍然是原数组长度的真正原因。在使用array方法时应注意,在以下两种缓冲区中不能使用array方法:

  • 只读的缓冲区

如果使用只读缓冲区的array方法,将会抛出一个ReadOnlyBufferException异常。

  • 使用allocateDirect方法创建的缓冲区

如果调用这种缓冲区中的array方法,将会抛出一个UnsupportedOperationException异常。

可以通过缓冲区类的hasArray方法来判断这个缓冲区是否可以使用array方法,如果返回true,则说明这个缓冲区可以使用array方法,否则,使用array方法将会抛出上述的两种异常之一。

注意: 使用array方法返回的数组并不是缓冲区数据的副本。被返回的数组实际上就是缓冲区中的数据,也就是说,array方法只返回了缓冲区数据的引用。当数组中的数据被修改后,缓冲区中的数据也会被修改,返之也是如此。关于这方面内容将在下一节“读写缓冲区中的数据”中详细讲解。

在上述的七个缓冲区类中,ByteBuffer类和CharBuffer类各自还有另外一种方法来创建缓冲区对象。

l         ByteBuffer

可以通过ByteBuffer类的allocateDirect方法来创建ByteBuffer对象。allocateDirect方法的定义如下:

public   static  ByteBuffer allocateDirect( int  capacity)

使用allocateDirect方法可以一次性分配capacity大小的连续字节空间。通过allocateDirect方法来创建具有连续空间的ByteBuffer对象虽然可以在一定程度上提高效率,但这种方式并不是平台独立的。也就是说,在一些操作系统平台上使用allocateDirect方法来创建ByteBuffer对象会使效率大幅度提高,而在另一些操作系统平台上,性能会表现得非常差。而且allocateDirect方法需要较长的时间来分配内存空间,在释放空间时也较慢。因此,在使用allocateDirect方法时应谨慎。

通过isDirect方法可以判断缓冲区对象(其他的缓冲区类也有isDirect方法,因为,ByteBuffer对象可以转换成其他的缓冲区对象,这部分内容将在后面讲解)是用哪种方式创建的,如果isDirect方法返回true,则这个缓冲区对象是用allocateDirect方法创建的,否则,就是用其他方法创建的缓冲区对象。

l         CharBuffer

我们可以发现,上述的七种缓冲区中并没有字符串缓冲区,而字符串在程序中却是最常用的一种数据类型。不过不要担心,虽然java.nio包中并未提供字符串缓冲区,但却可以将字符串转换成字符缓冲区(就是CharBuffer对象)。在CharBuffer类中的wrap方法除了上述的两种重载形式外,又多了两种重载形式,它们的定义如下:

public   static  CharBuffer wrap(CharSequence csq)
public   static  CharBuffer wrap(CharSequence csq,  int  start,  int  end)

其中csq参数表示要转换的字符串,但我们注意到csq的类型并不是String,而是CharSequenceCharSequenceJava中四个可以表示字符串的类的父类,这四个类是StringStringBufferStringBuilderCharBuffer(大家要注意,StringBuffer和本节讲的缓冲区类一点关系都没有,这个类在java.lang包中)。也就是说,CharBuffer类的wrap方法可以将这四个类的对象转换成CharBuffer对象。

另外两个参数startend分别是子字符串的开始索引和结束索引的下一个位置,如将字符串"1234"中的"23" 转换成CharBuffer对象的语句如下:

CharBuffer cb  =  CharBuffer.wrap( " 1234 " 1 3 );

    下面的代码演示了如何使用wrap方法将不同形式的字符串转换成CharBuffer对象。

StringBuffer stringBuffer  =   new  StringBuffer( " 通过StringBuffer创建CharBuffer对象 " );
StringBuilder stringBuilder 
=   new  StringBuilder( " 通过StringBuilder创建CharBuffer对象 " );
CharBuffer charBuffer1 
=  CharBuffer.wrap( " 通过String创建CharBuffer对象 " );
CharBuffer charBuffer2 
=  CharBuffer.wrap(stringBuffer);
CharBuffer charBuffer3 
=  CharBuffer.wrap(stringBuilder);
CharBuffer charBuffer4 
=  CharBuffer.wrap(charBuffer1,  1 3 );

下一篇: Java网络编程从入门到精通(34):读写缓冲区中的数据---使用get和put方法按顺序读写单个数据



国内最棒的Google Android技术社区(eoeandroid),欢迎访问!

《银河系列原创教程》发布

《Java Web开发速学宝典》出版,欢迎定购

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
Sqoop 企业级大数据迁移方案实战
Sqoop是一个用于在Hadoop和关系数据库服务器之间传输数据的工具。它用于从关系数据库(如MySQL,Oracle)导入数据到Hadoop HDFS,并从Hadoop文件系统导出到关系数据库。 本课程主要讲解了Sqoop的设计思想及原理、部署安装及配置、详细具体的使用方法技巧与实操案例、企业级任务管理等。结合日常工作实践,培养解决实际问题的能力。本课程由黑马程序员提供。
目录
相关文章
|
11天前
|
人工智能 Java 物联网
JAVA网络编程的未来:URL与URLConnection的无限可能,你准备好了吗?
随着技术的发展和互联网的普及,JAVA网络编程迎来新的机遇。本文通过案例分析,探讨URL与URLConnection在智能API调用和实时数据流处理中的关键作用,展望其未来趋势和潜力。
34 7
|
2天前
|
Java 大数据 API
14天Java基础学习——第1天:Java入门和环境搭建
本文介绍了Java的基础知识,包括Java的简介、历史和应用领域。详细讲解了如何安装JDK并配置环境变量,以及如何使用IntelliJ IDEA创建和运行Java项目。通过示例代码“HelloWorld.java”,展示了从编写到运行的全过程。适合初学者快速入门Java编程。
|
11天前
|
安全 Java API
深入探索Java网络编程中的HttpURLConnection:从基础到进阶
本文介绍了Java网络编程中HttpURLConnection的高级特性,包括灵活使用不同HTTP方法、处理重定向、管理Cookie、优化安全性以及处理大文件上传和下载。通过解答五个常见问题,帮助开发者提升网络编程的效率和安全性。
|
11天前
|
JSON 安全 算法
JAVA网络编程中的URL与URLConnection:那些你不知道的秘密!
在Java网络编程中,URL与URLConnection是连接网络资源的两大基石。本文通过问题解答形式,揭示了它们的深层秘密,包括特殊字符处理、请求头设置、响应体读取、支持的HTTP方法及性能优化技巧,帮助你掌握高效、安全的网络编程技能。
39 9
|
9天前
|
存储 安全 Java
🌟Java零基础-反序列化:从入门到精通
【10月更文挑战第21天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
37 5
|
6天前
|
安全 Java 调度
Java中的多线程编程入门
【10月更文挑战第29天】在Java的世界中,多线程就像是一场精心编排的交响乐。每个线程都是乐团中的一个乐手,他们各自演奏着自己的部分,却又和谐地共同完成整场演出。本文将带你走进Java多线程的世界,让你从零基础到能够编写基本的多线程程序。
18 1
|
4月前
|
安全 Java Linux
(七)Java网络编程-IO模型篇之从BIO、NIO、AIO到内核select、epoll剖析!
IO(Input/Output)方面的基本知识,相信大家都不陌生,毕竟这也是在学习编程基础时就已经接触过的内容,但最初的IO教学大多数是停留在最基本的BIO,而并未对于NIO、AIO、多路复用等的高级内容进行详细讲述,但这些却是大部分高性能技术的底层核心,因此本文则准备围绕着IO知识进行展开。
156 1
|
4月前
|
Java 大数据
如何在Java中进行网络编程:Socket与NIO
如何在Java中进行网络编程:Socket与NIO
|
6月前
|
监控 Java 开发者
深入理解 Java 网络编程和 NIO
【4月更文挑战第19天】Java网络编程基于Socket,但NIO(非阻塞I/O)提升了效率和性能。NIO特点是非阻塞模式、选择器机制和缓冲区,适合高并发场景。使用NIO涉及通道、选择器和事件处理,优点是高并发、资源利用率和可扩展性,但复杂度、错误处理和性能调优是挑战。开发者应根据需求选择是否使用NIO,并深入理解其原理。
65 1
|
JSON 前端开发 安全
Java网络编程IO模型 --- BIO、NIO、AIO详解
Java网络编程IO模型 --- BIO、NIO、AIO详解
355 0
Java网络编程IO模型 --- BIO、NIO、AIO详解

热门文章

最新文章