版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
面向服务端编程由于种种历史的原因,现在的Java技术主要用于构建高性能网络服务器端应用开发,用户可以通过专属的客户端应用、手机或者是浏览器进行服务器资源的访问但是随着当前互联网技术的发展,以及各种电商应用平台的打折促销活动,必然会加大服务器并发访问量而出现IO资源枯竭的问题,如图9-2所示。这样的最终结果将导致服务响应速度慢,甚至会出现服务器宕机的问题,从而造成整个应用的瘫痪。服务处理性能分析从计算机硬件的传统设计来讲,影响最终服务处理性能的不仅仅是CPU的强弱(CPU决定了内核线程的数量与数据的处理速度),最重要的是IO传输性能的限制,因为所有CPU计算的数据都需要通过IO加载到内存之中一个完整的应用之中如果要想实现绝对的性能提高,需要充分的考虑到CPU的资源利用、线程池的使用(J.U.C提供支持)、JVM进程内存分配策略以及IO的处理性能,当这些全部都考虑到了之后再继续考虑程序算法的优化,才可以释放单台服务器的全部性能,而传统的IO模型是无法做到这一点的,这也是为什么在现代网络编程中去大量采用NIO开发的原因所在。阻塞式IO编程在JDK1.0推出时,Java为了解决网络程序开发所面临的问题,提供了程序开发包,在该包中明确的定义了TCP与UDP通讯协议的实现类,但是这种传统的网络程序的开发是基于阻塞式的IO模型来实现的在阻塞式IO编程中,服务端需要为每一个请求的客户端分配处理线程,由于服务端的线程资源有限,所以在高并发访问环境下,服务端的线程资源将很快被占满,这样新的客户端将无法进行服务端的连接。同时在每个用户线程处理的过程中,服务端也需要持续等待客户端请求数据发送,这样一来就会出现IO阻塞的问题。而如果某一个客户端连接成功,并且长时间不做任何IO操作时,该线程资源也将无法释放,这样就造成了严重的性能损耗。IO操作在所有的网络通讯模型之中,IO是其最核心的处理单元,服务端需要等待客户端发送请求数据后才可以进行后续的处理与响应,这样对于操作系统来讲一个完整的IO操作就分为了个部分:数据等待:系统内核等待用户发送的请求数据,当数据到达后这些数据会被保存在内核中的某个缓冲区内;数据复制:将数据从内核缓冲区复制到应用进程缓冲区。内核空间与用户空间在操作系统之中为了便于应用程序的管理,将进程分为用户进程(或称为应用进程)与系统进程两类,并且在内存中为这两类的进程分配了不同的保存空间,分别为用户空间与内核空间之所以这样划分,是考虑到CPU指令的执行的安全问题(在CPU中提供有很多底层的操作指令,如果使用不当,将导致系统崩溃的问题出现),所以对于一些特权指令(例如:磁盘数据读取、网络端口监听)只允许系统进程访问,用户进程将无法进行访问。文件输入流与内核调用每一个JVM进程都属于用户进程,这样在编写Java应用程序时假设需要调用一些系统硬件指令时,就会将一个进程由用户态切换到内核态,完成相应的处理,以FileInputStream类中的read()数据流读取方法为例,由于该方法需要进行磁盘操作,那么必然需要将进程由用户态切换为内核态才可以实现操作系统底层的硬件调用在进行网络通讯过程中,由于会涉及到网络协议与IO传输的处理,则所有的数据会先保存在内核缓冲区,在数据接收完成后,再将其拷贝到用户缓冲区之中。UNIX中的五种IO模型在UNIX系统下一共有五种IO模型,分别是为阻塞式IO、非阻塞式IO、IO复用、信号驱动式IO、异步IO(AIO)阻塞式I/O模型在进行数据读取时,用户线程将持续等待内核进程,一直到内核进程的数据复制到应用进程缓冲区之中才返回。在该用户进程等待期间由于并不会占用CPU资源,所以其他的进程可以正常的执行,但是在数据读取过程中会产生阻塞问题,即:如果内核进程持续得不到数据,则该操作将持续保持阻塞状态。非阻塞式I/O模型设置一个切换的处理状态,如果发现现在内核进程没有读取到数据,则切换回用户状态并继续处理其他的相关操作,随后不断进行内核进程数据状态的检查,一直到内核数据准备完成后再进行后续的数据复制以及数据处理的相关操作。相比较阻塞式I/O,非阻塞式IO提供了内核数据状态检查的支持,但是此时的CPU由于需要处理更多的系统调用,所以该模型的处理性能是比较低的。I/O多路复用模型用户进程不再直接进行数据读取操作,而是先使用select或poll的操作,同时该操作可以针对于多个Socket套接字进行数据读取,那个套接字有数据,就将该套接字的数据读取到内核进程之中,当内核进程读取完成后会自动通知用户进程进行内核进程数据的读取信号驱动I/O模型用户进程向sigaction系统注册一个监听信息,而后用户进程返回并进行后续操作的执行,所以用户线程不会造成数据等待阶段的阻塞问题。内核进程在数据读取完成后,会发出一个SIGIO的信号,当用户进程接收到此信号之后将开启数据读取操作。由于此类操作不需要重复执行读取状态的轮询处理,所以相比较非阻塞式I/O模型,CPU利用率更高。异步I/O模型当用户进程发出异步读取的信号后,用户进程就会返回并进行其他操作,一直到内核进程将数据复制到用户进程缓冲区之后,才会给用户进程发出一个读取信号。相比较信号驱动式IO模型,异步IO模型是在数据读取完成后再发出信号。IO多路复用模型IO多路复用(I/Omultiplexing)模型或称为事件驱动IO模型(EventDrivenIO),可以使用单个处理程序实现多个网络连接的IO操作。在该模型中只会在SELECTOR阶段出现短暂的阻塞,而后会根据每一个Socket的处理操作状态来同时管理多个IO流,提高服务器的处理性能。IO多路复用是一个抽象的设计概念,而为了实现这一概念,提供有select、poll和epoll三种具体的实现模式select实现IO多路复用在该实现程序中会对全部的IO通道进行监听,当出现了IO操作后会轮询所有的IO通道,找出可以读取数据或写入数据的IO通道进行相关操作。由于每一次都需要轮询全部的IO通道,所以其处理的时间复杂度为“O(n)”。当处理的IO通道越多,则轮询的时间也就越长,为了得到良好的select处理性能,一般都会为其设置允许打开的最大连接数。select监听Socket状态实现流程在select实现模式中,所有已连接的Socket都被保存在一个文件描述符的集合之中,该描述符保存在用户进程空间之中。但是所有的IO操作都需要内核进程完成处理,这样就需要调用select函数将用户进程之中所保存的文件描述符集合拷贝到内核进程之中,并内核进程内实现Socket状态的监听,此时的操作需要遍历全部的Socketselect标记完成在内核进程完成文件描述符集合遍历后,会对相关的Socket进行标记(读或写操作),并且将当前的文件描述符集合重新拷贝回用户进程中。用户进程需要对此时的集合再次迭代,找出读、写的Socket后再对其进行处理如果使用了select实现方式,则需要进行两次文件描述符集合的拷贝,而且还会发生2次文件描述符集合的遍历操作,考虑到性能以及当时的硬件环境问题(select实现是在1983年时的BSD系统中提出的),在select中使用了一个定长的BitsMap来表示文件描述符的长度,其最大值为1024(只能够监听0~1023个文件描述符)。poll实现IO多路复用为了解决select实现中最大连接数的局限问题,在select基础之上开发出了poll模式(在1997年对select实现进行了修复),在该模式中使用了链表替换了BitsMap(文件描述符集合),所以可以保存更多文件描述符(会受到操作系统的文件描述符上限的限制),但是由于其依然需要进行2次用户进程与内核进程的数据拷贝以及2次的集合遍历,所以操作的时间复杂度依然为“O(n)”。epoll实现IO多路复用在epoll进行处理时,考虑到文件Socket检测状态的问题,使用了红黑树的结构进行存储,这样在进行Socket的增加、查询以及删除时,可以将时间复杂度控制在“O(logn)”的范围之中。epoll采用了事件驱动机制,每当有Socket产生了事件之后,会将当前的Socket保存在链表之中,这样拷贝回用户进程的Socket数量就变得有限,减少了链表迭代所需要的时间,从而提升了处理性能由于每一次由内核进程拷贝到用户进程的Socket数量有限,这样即便监听再多的Socket(受到操作系统定义的单进程打开最大文件描述符的个数限定),处理的性能也不会大幅度降低,所以使用epoll可以有效的解决C10K问题。epoll触发模式epoll是基于事件驱动的方式实现的Socket事件处理,每当内核进程中的活跃Socket发生状态事件后,就会通过回调函数来,而epoll中的事件触发模式分为两种:LT(LevelTrigger、条件触发或称为水平触发)模式:为默认的epoll实现模式,只要Socket一直处于可用状态,就会持续通知用户进程(Socket由可用变为不可用状态时不会通知),LT模式支持阻塞非阻塞两种处理形式,并且应用进程的业务逻辑编写较为简单,不容易出现事件遗漏的问题,在并发量较小时推荐使用LT模式(select与poll模式只支持LT模式);ET(EdgeTrigger、边缘触发)模式:在发生读写操作时,只会通知用户进程一次,在ET模式中主要关注Socket的可用与不可用状态切换,所以使用ET模式容易出现事件遗漏的问题,并且用户编写的业务逻辑较为复杂,只支持非阻塞模式,在并发量较大时,推荐使用ET模式。IO与缓冲区IO操作属于操作系统的特权指令,这样在进行系统IO操作时,就需要将当前进程由用户态切换到内核态,而为了保证切换的稳定,就要在系统操作调用前保存好相关的内存堆栈环境,当内核态的进程处理完成后,CPU会将进程切换回用户态,并且恢复该进程在用户态下的相关内存堆栈环境。这样的进程状态切换会带来大量的系统开支,所以要想提升IO的读写性能就必须减少这样的切换次数,而为了解决这样的问题,往往在IO读或写时都会引入缓冲区的概念。缓冲区可以是一个数组或者是一个专属的缓冲区处理类,每次进行数据读取时都会申请一块内存数组,利用该数组形成一个缓冲区,每次通过该缓冲区读取定长的部分数据到内核进程缓冲区之中,而后再将该数据拷贝到用户进程缓冲区之中。这样只需要几次就可以将数据读取完成,从而避免了频繁的进程状态切换所带来的性能开支。Buffer类继承结构在传统的IO开发中都是基于字节或字符数组并结合read()方法实现数据的缓冲区加载,而在NIO之中,为了便于缓冲区的管理,提供了一个java.nio.Buffer的缓冲区处理类,而后根据不同的应用场景,可以选择不同数据类型的缓冲区,例如:ByteBuffer、IntBuffer或LongBuffer等,这些都属于Buffer的子类Buffer类常用操作方法No.方法类型描述01publicObjectarray()普通返回缓冲区中的全部数据02publicfinalintcapacity()普通返回缓冲区的容量03publicBufferclear()普通清空缓冲区04publicabstractBufferduplicate()普通通过已有缓冲区复制一个新的缓冲区05publicBufferflip()普通翻转缓冲区06publicfinalbooleanhasRemaining()普通判断缓冲区之中是否还有数据07publicabstractbooleanisDirect()普通判断缓冲区是否为直接内存08publicabstractbooleanisReadOnly()普通判断缓冲区是否为只读状态09publicfinalintlimit()普通返回当前缓冲区可用上限10publicBufferlimit(intnewLimit)普通设置缓冲区的上限11publicBuffermark()普通设置缓冲区的位置标记12publicfinalintremaining()普通返回position与limit之间的元素个数13publicBufferreset()普通重置缓冲区14publicabstractBufferslice()普通创建新的子缓冲区15publicabstractBufferslice(intindex,intlength)普通创建指定范围的子缓冲区IntBuffer为便于整型缓存数据的操作,在java.nio包中提供了IntBuffer工具类,该类继承自Buffer父类,同时也是一个抽象类,继承结构如图所示,在进行整型缓冲区分配时,需要通过IntBuffer.allocate()方法完成,该方法封装了HeapIntBuffer子类的实例化操作,同时需要指定缓冲区的大小。范例:使用IntBuffer进行缓冲区操作packagecom.yootk;
importjava.nio.IntBuffer;
publicclassYootkDemo{
//李兴华高薪就业编程训练营
publicstaticvoidmain(String[]args){
//沐言科技:
IntBufferbuf=IntBuffer.allocate(10);
//开辟10个大小的缓冲区
System.out.printf("【1】数据写入前的缓冲区状态:position=%d、limit=%d、capacity=%d%n",
buf.position(),buf.limit(),buf.capacity());
buf.put(3);
//向缓冲区保存数据
buf.put(newint[]{5,7,9});
//向缓冲区保存数据
System.out.printf("【2】数据写入后的缓冲区状态:position=%d、limit=%d、capacity=%d%n",
buf.position(),buf.limit(),buf.capacity());
buf.flip();
//缓存翻转,准备输出
System.out.printf("【3】flip()后的缓冲区状态:position=%d、limit=%d、capacity=%d%n",
buf.position(),buf.limit(),buf.capacity());
System.out.print("【4】获取缓存数据:");
while(buf.hasRemaining()){
//判断是否还有数据
System.out.print(buf.get()+"、");
//获取数据
}
System.out.println();
//换行
System.out.printf("【5】数据获取后的缓冲区状态:position=%d、limit=%d、capacity=%d%n",
buf.position(),buf.limit(),buf.capacity());
}
}程序执行结果:【1】数据写入前的缓冲区状态:position=0、limit=10、capacity=10【2】数据写入后的缓冲区状态:position=4、limit=10、capacity=10【3】flip()后的缓冲区状态:position=0、limit=4、capacity=10【4】获取缓存数据:3、5、7、9、【5】数据获取后的缓冲区状态:position=4、limit=4、capacity=10Buffer缓冲区的索引状态指针操作为了维护Buffer的缓存状态,在Buffer类中提供了一系列的状态属性(包括:position、limit、capacity),这些状态属性会随着不同缓冲区数据的操作而发生改变,每一个状态属性的作用如下所示:position属性:表示下一个缓冲区读取或写入的操作指针,每向缓冲区中写入数据的时候此指针就会改变,指针永远放到写入的最后一个元素之后。即:如果写入了4个位置的数据,则position会指向第5个位置。limit属性:表示当前可以读取到的最大缓冲区位置(position<=limit);capacity属性:表示缓冲区的最大容量(limit<=capacity),此值在分配缓冲区时被设置,一般不会更改。ByteBuffer在IO操作中最为常用的数据类型是字节类型,这样在java.nio包中提供了ByteBuffer的内存缓冲区处理类,该类的继承结构如图所示,该类的使用与IntBuffer类的结构类似,下面通过一个字符串的大小写转换的方式来进行说明。范例:使用ByteBuffer实现内存缓冲操作packagecom.yootk;
importjava.nio.ByteBuffer;
publicclassYootkDemo{ //李兴华高薪就业编程训练营
publicstaticvoidmain(String[]args){ //沐言科技:
Stringcontent=""; //要操作的字符串
ByteBufferbuffer=ByteBuffer.allocate(content.length());//开辟字节缓冲区
buffer.put(content.getBytes()); //保存字节数组
buffer.flip(); //缓冲区翻转
while(buffer.hasRemaining()){ //缓冲区有数据
System.out.print((char)Character.toUpperCase(buffer.get()));//获取缓冲区数据
}
}
}程序执行结果:WWW.YOOTK.COM范例:测试内存缓冲区的两种分配形式的性能packagecom.yootk;
importjava.nio.ByteBuffer;
publicclassYootkDemo{ //李兴华高薪就业编程训练营
publicstaticvoidmain(String[]args){ //沐言科技:
testHeapBufferExecuteTime(100000); //性能测试
testDirectBufferExecuteTime(100000); //性能测试
}
publicstaticvoidtestHeapBufferExecuteTime(intcount){//测试堆内存缓冲区
longstart=System.currentTimeMillis(); //获取开始时间戳
for(intx=0;x<count;x++){ //循环分配缓冲区
ByteBufferbuffer=ByteBuffer.allocate(count); //分配缓冲区
}
longend=System.currentTimeMillis(); //获取开始结束戳
System.out.println("【堆内存缓冲区】测试分配耗时:"+(end-start));
}
publicstaticvoidtestDirectBufferExecuteTime(intcount){//测试直接内存缓冲区
longstart=System.currentTimeMillis(); //获取开始时间戳
for(intx=0;x<count;x++){ //循环分配缓冲区
ByteBufferbuffer=ByteBuffer.allocateDirect(count);//分配缓冲区
}
longend=System.currentTimeMillis(); //获取开始结束戳
System.out.println("【直接内存缓冲区】测试分配耗时:"+(end-start));
}
}程序执行结果:【堆内存缓冲区】测试分配耗时:904【直接内存缓冲区】测试分配耗时:4147子缓冲区更新操作在每一个缓冲区之中都有可能存在有大量的数据,但是在项目中会存在有部分缓存数据修改的需要,所以在Buffer类中提供了一个slice()方法,利用该方法可以通过主缓冲区创建子缓冲区,而用户进行子缓冲区操作的同时也会自动同步到主缓冲区之中范例:创建并更新子缓冲区数据packagecom.yootk;
importjava.nio.ByteBuffer;
publicclassYootkDemo{ //李兴华高薪就业编程训练营
publicstaticvoidmain(String[]args){ //沐言科技:
Stringcontent=""; //要操作的字符串
ByteBufferbuffer=ByteBuffer.allocate(content.length());//开辟字节缓冲区
buffer.put(content.getBytes()); //保存字节数组
buffer.position(4); //设置主缓冲区postion位置
buffer.limit(9); //设置主缓冲区的limit位置
ByteBuffersub=buffer.slice(); //获取子缓冲区
for(intx=0;x<sub.capacity();x++){ //子缓冲区处理
sub.put((byte)Character.toUpperCase(sub.get(x)));//子缓冲区保存数据
}
buffer.flip(); //缓冲区翻转
buffer.limit(buffer.capacity()); //主缓冲区设置
while(buffer.hasRemaining()){ //缓冲区有数据
System.out.print((char)buffer.get()); //获取缓冲区数据
}
}
}程序执行结果:www.YOOTK.comChannel通道在程序开发中需要经常需要处理各类的IO资源,例如:文件IO、网络IO、管道IO等,这样一来就需要使用不同的类来完成特定的IO处理,为程序管理带来了不便。在NIO中为了简化这一问题,提供了一个通道(Channel)的概念,利用通道并结合数据缓冲区实现统一的输入与输出管理Channel常用子接口为了便于通道的管理,提供了java.nio.channels.Channel通道接口,该接口为所有可用通道的父接口,需要注意的是在Channel接口中并没有定义具体的读写操作,只是定义了通道是否打开状态的判断,而具体的读写处理,则会由相关的子接口来定义例如:ReadableByteChannel实现了字节缓冲区的读通道,而WritableByteChannel实现了字节缓冲区的写通道,而具体的实现则需要依靠具体的子类完成。FileChannel在程序进行IO处理的过程中,主要是围绕着文件IO和网络IO进行的,所以在进行文件通道操作时,可以使用FileChannel的子类进行操作,该类定义了一个文件的处理操作通道,类的定义如下:publicabstractclassFileChannel
extendsAbstractInterruptibleChannel
implementsSeekableByteChannel,GatheringByteChannel,ScatteringByteChannel{}FileChannel类常用方法No.方法类型描述01publicfinalFileLocklock()throwsIOException普通获取文件的独占锁02publicfinalFileLocktryLock()throwsIOException普通尝试获取文件的独占锁03publicstaticFileChannelopen(Pathpath,OpenOption...options)throwsIOException普通打开并创建文件,并返回该文件的通道04publicabstractlongposition()throwsIOException普通返回当前通道的读写位置05publicabstractFileChannelposition(longnewPosition)throwsIOException普通设置通道读写位置06publicabstractintread(ByteBufferdst)throwsIOException普通将数据读取到字节缓冲区之中07publicfinallongread(ByteBuffer[]dsts)throwsIOException普通将数据读取到字节缓冲区数组之中08publicabstractlongsize()throwsIOException普通获取当前通道中的文件大小09publicabstractFileChanneltruncate(longsize)throwsIOException普通将此通道的文件截断为指定大小10publicabstractlongtransferFrom(ReadableByteChannelsrc,longposition,longcount)throwsIOException普通将指定的读取通道转到此文件通道之中11publicabstractlongtransferTo(longposition,longcount,WritableByteChanneltarget)throwsIOException普通将文件通道转为指定的写入通道12publicabstractintwrite(ByteBuffersrc)throwsIOException普通将字节缓冲区的数据写入到文件之中13publicfinallongwrite(ByteBuffer[]srcs)throwsIOException普通将一组字节缓冲区的数据写入到文件之中获取FileChannel类的实例化对象FileChannel是一个抽象类,如果要想获取此类的实例化对象,则可以通过FileOutputStream以及FileInputStream类中所提供的getChannel()方法来获取,而通过该方法的实现源代码可以发现,是利用了sun.nio.ch.FileChannelImpl子类所提供的open()方法完成的,在该方法中可以通过参数的配置实现读写模式的切换范例:使用FileChannel实现通道文件写入packagecom.yootk;
importjava.io.File;
importjava.io.FileOutputStream;
importjava.nio.ByteBuffer;
importjava.nio.channels.FileChannel;
publicclassYootkDemo{ //李兴华高薪就业编程训练营
publicstaticvoidmain(String[]args)throwsException{//沐言科技:
Filefile=newFile("h:"+File.separator+"muyan"+File.separator+"yootk"+File.separator+"happy.txt"); //文件路径
if(!file.getParentFile().exists()){ //文件不存在
file.getParentFile().mkdirs(); //创建目录
}
FileOutputStreamoutput=newFileOutputStream(file);//文件输出流
FileChannelchannel=output.getChannel(); //写入文件通道
bytecontent[]="沐言科技:".getBytes();//输出数据
ByteBufferbuffer=ByteBuffer.allocate(content.length);//创建字节缓冲区
buffer.put(content); //保存缓冲数据
buffer.flip(); //缓冲区翻转
channel.write(buffer); //输出缓冲区数据
channel.close(); //通道关闭
}
}范例:使用FileChannel实现通道文件读取packagecom.yootk;
importjava.io.*;
importjava.nio.ByteBuffer;
importjava.nio.channels.FileChannel;
publicclassYootkDemo{ //李兴华高薪就业编程训练营
publicstaticvoidmain(String[]args)throwsException{//
Filefile=newFile("h:"+File.separator+"muyan"+File.separator+"yootk"+File.separator+"happy.txt"); //文件路径
if(file.exists()){ //文件存在
FileInputStreaminput=newFileInputStream(file);//文件输出流
FileChannelchannel=input.getChannel(); //写入文件通道
ByteBufferbuffer=ByteBuffer.allocate(10); //创建字节缓冲区
ByteArrayOutputStreambos=newByteArrayOutputStream();//内存
while(channel.read(buffer)!=-1){ //数据读取
buffer.flip(); //缓冲区翻转
while(buffer.hasRemaining()){ //是否还有数据
bos.write(buffer.get()); //读取并保存数据
}
buffer.clear(); //恢复缓冲区状态
}
System.out.println(bos); //输出内存流数据
channel.close(); //关闭文件通道
}
}
}程序执行结果:沐言科技:MMap内存映射在传统IO进行文件读写操作时,操作进程需要进行用户态与内核态的切换,同时还需要将所操作的数据进行拷贝处理,这样就极大的影响到了程序的处理性能,为了解决这一设计问题,在NIO中提供了mmap的支持,可以将要操作的文件内容直接映射到内存之中,这样用户操作内存数据时就会直接影响到文件数据的更新MapMode映射模式在NIO中实现MMap内存映射,主要依靠FileChannel抽象类中提供的map()方法来完成,在该方法进行操作时需要明确的设置映射的文件范围(通过position定义映射起始点,而后通过size定义映射长度)以及映射的模式,而映射模式主要是由FileChannel.MapMode类定义的No.常量类型描述1publicstaticfinalFileChannel.MapModeREAD_ONLY常量只读映射模式2publicstaticfinalFileChannel.MapModeREAD_WRITE常量读取/写入映射模式3publicstaticfinalFileChannel.MapModePRIVATE常量专用(写入时拷贝)映射模式,会形成一个新的缓冲区,不会影响到原始文件的内容范例:实现MMap读映射packagecom.yootk;
importjava.io.File;
importjava.io.FileInputStream;
importjava.nio.MappedByteBuffer;
importjava.nio.channels.FileChannel;
publicclassYootkDemo{ //李兴华高薪就业编程训练营
publicstaticvoidmain(String[]args)throwsException{//
Filefile=newFile("h:"+File.separator+"muyan"+File.separator+"yootk"+File.separator+"happy.txt"); //文件路径
FileChannelchannel=newFileInputStream(file).getChannel();//输入流通道
MappedByteBufferbuffer=channel.map(FileChannel.MapMode.READ_ONLY, 0,file.length()); //内存映射文件的全部内容
bytedata[]=newbyte[(int)file.length()]; //根据文件大小开辟数组
intfoot=0; //数组角标
while(buffer.hasRemaining()){ //是否还有数据
data[foot++]=buffer.get(); //获取并保存数据
}
System.out.println("【文件内容】"+newString(data));//数据输出
channel.close(); //关闭通道
}
}程序执行结果:【文件内容】沐言科技:范例:内存映射读写处理packagecom.yootk;
importjava.io.File;
importjava.io.RandomAccessFile;
importjava.nio.MappedByteBuffer;
importjava.nio.channels.FileChannel;
publicclassYootkDemo{ //李兴华高薪就业编程训练营
publicstaticvoidmain(String[]args)throwsException{//
Filefile=newFile("h:"+File.separator+"muyan"+File.separator+"yootk"+File.separator+"happy.txt"); //文件路径
RandomAccessFileraf=newRandomAccessFile(file,"rw");//随机读写
FileChannelchannel=raf.getChannel(); //获取文件通道
MappedByteBufferbuffer=channel.map(FileChannel.MapMode.READ_WRITE,50,30); //部分文件内容读写映射
bytedata[]=newbyte[30]; //根据文件大小开辟数组
intfoot=0; //数组角标
while(buffer.hasRemaining()){ //是否还有数据
data[foot++]=buffer.get(); //获取并保存数据
}
System.out.println("【文件内容】"+newString(data));//数据输出
buffer.clear(); //重置缓冲区
buffer.put("沐言科技:".getBytes()); //设置新数据
channel.close(); //关闭通道
}
}管道通道java.io包中为了便于两个线程之间的IO操作提供了专属的管道操作流,而在java.nio中为了进一步提升管道操作流的概念提供了专属的Pipe工具类,并由该类可以创建Sink管道与Source管道,用于实现单向数据的发送Pipe线程通讯管道java.nio.channels.Pipe类中提供了Pipe.SinkChannel与Pipe.SourceChannel两个内部类用于实现管道数据的发送与接收操作,而这两个类分别是WritableByteChannel与ReadableByteChannel接口的子类,这样就可以直接使用类中提供的通道读写方法,基于ByteBuffer实现数据的发送与接收范例:基于管道通道实现线程通讯packagecom.yootk;
publicclassYootkDemo{ //李兴华高薪就业编程训练营
publicstaticvoidmain(String[]args)throwsException{//
Pipepipe=Pipe.open(); //创建管道
startConsumerThreads(pipe); //启动消费端
startProducerThread(pipe); //启动生产端
}
publicstaticvoidstartProducerThread(Pipepipe){ //启动生产者线程
Pipe.SinkChannelsink=pipe.sink(); //发送通道
ByteBufferbuffer=ByteBuffer.allocate(100); //开辟缓冲区
Runnablerunnable=()->{ //线程主体
for(intx=0;x<3;x++){ //发送循环处理
try{
synchronized(YootkDemo.class){ //同步处理
TimeUnit.SECONDS.sleep(1); //延迟1秒执行
Stringmessage=String.format("【%s】第%d次发送数据:",Thread.currentThread().getName(),x);
buffer.put(message.getBytes()); //设置缓冲区数据
buffer.flip(); //重置缓冲区
sink.write(buffer); //发送数据
buffer.clear(); //重置缓冲区
}
}catch(Exceptione){}}
};
for(intx=0;x<2;x++){
newThread(runnable,"发送者-"+x).start(); //线程启动
}
}
publicstaticvoidstartConsumerThreads(Pipepipe){ //启动消费者线程
Pipe.SourceChannelsource=pipe.source(); //接收通道
ByteBufferbuffer=ByteBuffer.allocate(100); //开辟缓冲区
newThread(()->{
while(true){ //消费端持续接收
intlength=0; //保存接收数据长度
try{
while((length=source.read(buffer))!=-1){//数据接收
buffer.flip(); //缓冲区翻转
System.out.println(newString(buffer.array(),0,length));//输出
buffer.clear(); //重置缓冲区
}
}catch(Exceptione){}}
}).start();}
}程序执行结果:【发送者-0】第0次发送数据:【发送者-0】第1次发送数据:【发送者-0】第2次发送数据:【发送者-1】第0次发送数据:NIO网络模型为便于实现NIO下的网络应用开发,在java.nio.channels开发包中提供了“ServerSocketChannel”与“SocketChannel”两个网络通道类。开发者可以通过ServerSocketChannel绑定服务通道,而后每一个具体的客户端操作可以通过SocketChannel通道实现IO操作。而对于用户处理状态的选择,则可以由Selector类提供的select()方法完成,在SelectionKey中定义了用户操作的四种状态,分别为:OP_ACCEPT(连接监听模式)、OP_CONNECT(连接模式)、OP_READ(数据读取模式)、OP_WRITE(数据写入模式)1、【服务端】定义ECHO服务端的线程处理类packagecom.yootk.echo.server.handle;
importjava.nio.ByteBuffer;
importjava.nio.channels.SocketChannel;
publicclassEchoServerHandlerThreadimplementsRunnable{
privateSocketChannelsocketChannel; //客户端通道
privatebooleanflag=true; //循环标记
publicEchoServerHandlerThread(SocketChannelsocketChannel)throwsException{
this.socketChannel=socketChannel; //保存客户端通道
System.out.println("【新的客户端连接】客户端的IP地址为:"+ socketChannel.getRemoteAddress());
}
@Override
publicvoidrun(){ //线程任务
ByteBufferbuffer=ByteBuffer.allocate(50); //创建缓冲区
try{
while(this.flag){ //持续与客户端交互
buffer.clear(); //重置缓冲区
intreadCount=this.socketChannel.read(buffer);//接收客户端发送数据
StringreadMessage=newString(buffer.array(), 0,readCount).trim(); //数据变为字符串
System.out.println("【服务器接收到消息】"+readMessage);//提示信息
StringwriteMessage="【ECHO】"+readMessage+"\n";//回应信息
if("exit".equals(readMessage)){ //结束指令
writeMessage="【EXIT】拜拜,下次再见!"; //结束消息
this.flag=false; //修改标记
}
buffer.clear(); //清空缓冲区
buffer.put(writeMessage.getBytes()); //缓冲区保存数据
buffer.flip(); //重置缓冲区
this.socketChannel.write(buffer); //回应信息
}
this.socketChannel.close(); //关闭通道
}catch(Exceptione){}
}
}2、【服务端】定义EchoServer程序类packagecom.yootk.echo.server;
importcom.yootk.echo.server.handle.EchoServerHandlerThread;
import.InetSocketAddress;
importjava.nio.channels.*;
importjava.util.*;
importjava.util.concurrent.*;
publicclassEchoServerimplementsAutoCloseable{ //创建EchoServer服务端
publicstaticfinalintDEFAULT_PORT=9090; //默认端口
privateExecutorServiceexecutorService=Executors.newFixedThreadPool(5);//线程池
privateServerSocketChannelserverSocketChannel; //服务端Channel
privateSelectorselector; //选择器
publicEchoServer(){
this(DEFAULT_PORT); //调用单参构造
}
publicEchoServer(intport){ //保存端口
try{
this.serverSocketChannel=ServerSocketChannel.open();//开启服务端通道
this.serverSocketChannel.configureBlocking(false);//非阻塞模式
this.serverSocketChannel.bind(newInetSocketAddress(port));//服务绑定端口
this.selector=Selector.open(); //打开选择器
this.serverSocketChannel.register(this.selector, SelectionKey.OP_ACCEPT); //连接监听模式
System.out.println("ECHO服务端应用启动,本程序在"+port+ "端口上监听,等待客户端连接......");
}catch(Exceptione){
System.err.println("ECHO服务端应用启动失败,错误原因:"+e.getMessage());
}
}
publicvoidstart()throwsException{ //服务启动
intcurrentKey=0; //接收连接状态
while((currentKey=this.selector.select())>0){ //持续等待连接
Set<SelectionKey>selectedKeys=this.selector.selectedKeys();//获取全部状态
Iterator<SelectionKey>selectionIter=selectedKeys.iterator();//获取KEY
while(selectionIter.hasNext()){
SelectionKeyselectionKey=selectionIter.next();//获取每一个通道
if(selectionKey.isAcceptable()){ //模式为接收连接模式
SocketChannelclientChannel=this.serverSocketChannel.accept();//连接
if(clientChannel!=null){ //已经有了连接
this.executorService.submit(newEchoServerHandlerThread( clientChannel)); //处理用户请求
}
}
selectionIter.remove(); //移除掉此状态
}
}
}
@Override
publicvoidclose()throwsException{
this.executorService.shutdown(); //关闭线程池
this.serverSocketChannel.close(); //关闭服务通道
}
}3、【服务端】编写服务端启动类packagecom.yootk.echo;
importcom.yootk.echo.server.EchoServer;
publicclassStartEchoServerApplication{ //李兴华高薪就业编程训练营
publicstaticvoidmain(String[]args)throwsException{//沐言科技:
newEchoServer(9999).start(); //服务启动
}
}4、【客户端】创建一个键盘输入类packagecom.yootk.util;
importjava.io.*;
publicclassInputUtil{
privatestaticfinalBufferedReaderKEYBOARD_INPUT=newBufferedReader(newInputStreamReader(System.in)); //键盘缓冲输入流
privateInputUtil(){}
publicstaticStringgetString(Stringprompt)throwsIOException{//接收键盘数据
booleanflag=true; //输入标记
Stringstr=null; //接收输入字符串
while(flag){
System.out.print(prompt); //提示信息
str=KEYBOARD_INPUT.readLine(); //读取数据
if(str==null||"".equals(str)){ //判断数据是否存在
System.out.println("数据输入错误,请重新输入!!!");
}else{
flag=false; //输入结束
}
}
returnstr;
}
}5、【客户端】创建Echo客户端程序类packagecom.yootk.echo.client;
importcom.yootk.util.InputUtil;
import.InetSocketAddress;
importjava.nio.ByteBuffer;
importjava.nio.channels.SocketChannel;
importjava.util.concurrent.TimeUnit;
publicclassEchoClient{
privateSocketChannelsocketChannel; //客户端通道
privateByteBufferbuffer; //字节缓冲区
publicEchoClient(Stringhost,intport)throwsException{
this.socketChannel=SocketChannel.open(); //创建通道
this.socketChannel.connect(newInetSocketAddress(host,port));//连接服务端
this.buffer=ByteBuffer.allocate(50); //开辟字节缓冲区
}
publicvoidstart()throwsException{
booleanflag=true;
while(flag){ //持续输入信息
buffer.clear(); //清空缓冲区
Stringmsg=InputUtil.getString("请输入要发送的信息:");//提示信息
buffer.put(msg.getBytes()); //数据保存在缓冲区
buffer.flip(); //重设缓冲区
this.socketChannel.write(buffer); //发送消息
buffer.clear(); //清空缓冲区
intreadCount=this.socketChannel.read(buffer); //读取服务端回应
buffer.flip(); //重置缓冲区
System.err.print(newString(buffer.array(),0,readCount));
if("exit".equals(msg)){ //结束指令
flag=false; //结束循环
}
TimeUnit.MILLISECONDS.sleep(100); //延缓执行
}
this.socketChannel.close(); //关闭通道
}
}6、【客户端】编写Echo客户端启动类packagecom.yootk.echo;
importcom.yootk.echo.client.EchoClient;
publicclassStartEchoClientApplication{ //李兴华高薪就业编程训练营
publicstaticvoidmain(Strin
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 娱乐公司艺人经纪商业价值管理绩效考核表
- 数据备份家庭厨房风险规避量化指南
- 石油化工生产安全员安全操作与风险控制能力绩效评定表
- 游戏策划与设计师产品创新力KPI考核表
- 餐饮服务场所食品处理流程规范
- 关于项目总结的分享函(8篇)
- 电商平台电子产品上架资质审核手册
- 某区域绿化工程验收安排通知7篇
- 项目延期通知及后续安排建议3篇
- (2026年)江苏省安全员《C证》考试模拟题及答案
- 事业单位财会岗招聘笔试高频考题试卷及解析
- 2027届高三语文上学期全国各地名校9月模考病句修改汇编(含答案)
- 2026年《中国病毒性心肌炎诊疗临床指南(2026版)》
- 《化工企业设备检修作业安全规范》(AQ 3026-2026)解读化危为安
- 新背景下的2027年高考语文一轮备考策略
- ASTM D5276-23 中文版(运输包装件自由跌落测试标准 完整原文 + 不同重量跌落高度对照表)
- 四川成都简阳市2026年四级城乡社区工作者招聘考试试卷-含答案解析
- 2026年科研院所成果转化岗竞聘笔试真题附带答案
- 2026 年世界标准日标准化知识科普课件
- 2026年危重患者抢救制度培训课件
- FDE模式行业观察与实践
评论
0/150
提交评论