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

本文涉及的产品
数据传输服务 DTS,数据迁移 small 3个月
推荐场景:
MySQL数据库上云
数据传输服务 DTS,数据同步 1个月
数据传输服务 DTS,同步至 ClickHouse 1个月
简介:
如果将同步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 类中的get put 方法返回和传递的数据类型就是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*1024 1M )。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 ,而是CharSequence CharSequence Java 中四个可以表示字符串的类的父类,这四个类是String StringBuffer StringBuilder CharBuffer (大家要注意,StringBuffer 和本节讲的缓冲区类一点关系都没有,这个类在java.lang 包中)。也就是说,CharBuffer 类的wrap 方法可以将这四个类的对象转换成CharBuffer 对象。
另外两个参数start end 分别是子字符串的开始索引和结束索引的下一个位置,如将字符串"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 );





 本文转自 androidguy 51CTO博客,原文链接: http://blog.51cto.com/androidguy/214328 ,如需转载请自行联系原作者
相关实践学习
自建数据库迁移到云数据库
本场景将引导您将网站的自建数据库平滑迁移至云数据库RDS。通过使用RDS,您可以获得稳定、可靠和安全的企业级数据库服务,可以更加专注于发展核心业务,无需过多担心数据库的管理和维护。
Sqoop 企业级大数据迁移方案实战
Sqoop是一个用于在Hadoop和关系数据库服务器之间传输数据的工具。它用于从关系数据库(如MySQL,Oracle)导入数据到Hadoop HDFS,并从Hadoop文件系统导出到关系数据库。 本课程主要讲解了Sqoop的设计思想及原理、部署安装及配置、详细具体的使用方法技巧与实操案例、企业级任务管理等。结合日常工作实践,培养解决实际问题的能力。本课程由黑马程序员提供。
相关文章
|
11月前
|
存储 监控 安全
单位网络监控软件:Java 技术驱动的高效网络监管体系构建
在数字化办公时代,构建基于Java技术的单位网络监控软件至关重要。该软件能精准监管单位网络活动,保障信息安全,提升工作效率。通过网络流量监测、访问控制及连接状态监控等模块,实现高效网络监管,确保网络稳定、安全、高效运行。
256 11
|
3月前
|
JSON 移动开发 网络协议
Java网络编程:Socket通信与HTTP客户端
本文全面讲解Java网络编程,涵盖TCP与UDP协议区别、Socket编程、HTTP客户端开发及实战案例,助你掌握实时通信、文件传输、聊天应用等场景,附性能优化与面试高频问题解析。
|
1月前
|
JSON 网络协议 安全
【Java】(10)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
142 1
|
1月前
|
JSON 网络协议 安全
【Java基础】(1)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
160 1
|
1月前
|
机器学习/深度学习 分布式计算 Java
Java与图神经网络:构建企业级知识图谱与智能推理系统
图神经网络(GNN)作为处理非欧几里得数据的前沿技术,正成为企业知识管理和智能推理的核心引擎。本文深入探讨如何在Java生态中构建基于GNN的知识图谱系统,涵盖从图数据建模、GNN模型集成、分布式图计算到实时推理的全流程。通过具体的代码实现和架构设计,展示如何将先进的图神经网络技术融入传统Java企业应用,为构建下一代智能决策系统提供完整解决方案。
288 0
|
4月前
|
缓存 索引
基于Reactor模式的高性能网络库之缓冲区Buffer组件
Buffer 类用于处理 Socket I/O 缓存,负责数据读取、写入及内存管理。通过预分配空间和索引优化,减少内存拷贝与系统调用,提高网络通信效率,适用于 Reactor 模型中的异步非阻塞 IO 处理。
185 3
|
8月前
|
存储 网络协议 安全
Java网络编程,多线程,IO流综合小项目一一ChatBoxes
**项目介绍**:本项目实现了一个基于TCP协议的C/S架构控制台聊天室,支持局域网内多客户端同时聊天。用户需注册并登录,用户名唯一,密码格式为字母开头加纯数字。登录后可实时聊天,服务端负责验证用户信息并转发消息。 **项目亮点**: - **C/S架构**:客户端与服务端通过TCP连接通信。 - **多线程**:采用多线程处理多个客户端的并发请求,确保实时交互。 - **IO流**:使用BufferedReader和BufferedWriter进行数据传输,确保高效稳定的通信。 - **线程安全**:通过同步代码块和锁机制保证共享数据的安全性。
347 23
|
8月前
|
存储 缓存 安全
Java字符串缓冲区
字符串缓冲区是用于处理可变字符串的容器,Java中提供了`StringBuffer`和`StringBuilder`两种实现。由于`String`类不可变,当需要频繁修改字符串时,使用缓冲区更高效。`StringBuffer`是一个线程安全的容器,支持动态扩展、任意类型数据转为字符串存储,并提供多种操作方法(如`append`、`insert`、`delete`等)。通过这些方法,可以方便地对字符串进行添加、插入、删除等操作,最终将结果转换为字符串。示例代码展示了如何创建缓冲区对象并调用相关方法完成字符串操作。
230 13
|
9月前
|
安全 网络协议 Java
Java网络编程封装
Java网络编程封装原理旨在隐藏底层通信细节,提供简洁、安全的高层接口。通过简化开发、提高安全性和增强可维护性,封装使开发者能更高效地进行网络应用开发。常见的封装层次包括套接字层(如Socket和ServerSocket类),以及更高层次的HTTP请求封装(如RestTemplate)。示例代码展示了如何使用RestTemplate简化HTTP请求的发送与处理,确保代码清晰易维护。
|
9月前
|
缓存 网络协议 Java
JAVA网络IO之NIO/BIO
本文介绍了Java网络编程的基础与历史演进,重点阐述了IO和Socket的概念。Java的IO分为设备和接口两部分,通过流、字节、字符等方式实现与外部的交互。
292 0
下一篇
oss云网关配置