NIO与Socket编程技术指南(txt+pdf+epub+mobi电子书下载)


发布时间:2020-06-06 14:56:36

点击下载

作者:高洪岩

出版社:机械工业出版社

格式: AZW3, DOCX, EPUB, MOBI, PDF, TXT

NIO与Socket编程技术指南

NIO与Socket编程技术指南试读:

前言

为什么要写这本书

早在几年前,笔者就曾想过整理一份基于Java语言的NIO与Socket相关的稿件,因为市面上大部分的Java书籍都是以1章或2章的篇幅介绍NIO与Socket技术,并没有完整地覆盖该技术的知识点,而限于当时的时间及精力,一直没有如愿。

机会终于来了,公司要搭建基础构架知识体系,我负责公司该技术方向的培训,这重燃了我对NIO和Socket技术的热情。在学习Java技术的过程中,当学习了Java SE/Java EE之后想探索更深层次的技术,如大数据、分布式和高并发类等,可能会遇到针对NIO、Socket的学习,但NIO和Socket技术的学习并不像JDBC一样简单,学习NIO和Socket时可能要遇到很多的问题。为了在该技术领域有更高的追求,我将NIO和Socket的技术点以教案的方式进行了整理,并在公司中与同事一起进行学习和交流,同事的反响非常热烈。若干年前的心愿终于达成,同事们也很期待这本书能早日出版发行,那样他们就有真正的纸质参考资料了。希望本书能够受到其他学习NIO和Socket的读者喜爱,这是我最大的心愿。

本书介绍NIO和Socket开发中最值得关注的内容,并给出个人的一些想法和见解,希望拓宽读者的学习思路。

在学习NIO和Socket技术之前,建议先了解一下多线程与并发相关的知识,这对设计和理解代码有非常大的帮助。多线程方面的资料推荐《Java多线程编程核心技术》,并发相关的资料推荐《Java并发编程:核心方法与框架》,这两本书都是笔者编著的,希望可以给读者带来一些帮助。本书特色

在本书写作的过程中,我尽量做到言简意赅,并且全部用演示案例的方式来讲解技术知识点,使读者看到代码及运行结果后就可以知道此项目要解决的是什么问题。这类似于网络中的博客风格,让读者用最短的时间学习知识点,明白知识点的应用方式及使用时的注意事项,取得快速学习并解决相应问题的效果。读者对象

·Java程序员

·系统架构师

·大数据开发者

·其他对NIO和Socket技术感兴趣的人员如何阅读本书

本着实用、易懂的学习原则,本书通过6章内容来介绍Java多线程相关的技术。

第1章介绍NIO技术中的缓冲区,包括Buffer、ByteBuffer、CharBuffer类的核心API的使用。

第2章介绍NIO技术中的Channel(通道)类的继承关系、核心接口的作用,并重点介绍FileChannel类的使用,以增加读者对NIO操作File类的熟悉度。

第3章介绍如何使用NetworkInterface类获得网络接口的信息,包括IP地址、子网掩码等,还会介绍InetAddress和InterfaceAddress类的常见API。如果进行Java开发,且基于Socket技术,那么这章可以给你需要的信息。

第4章介绍如何使用Java语言实现Socket通信。Socket通信是基于TCP/IP和UDP实现的。另外,将介绍ServerSocket、Socket、DatagramSocket和DatagramPacket类的全部API。只有熟练掌握Socket技术后,在阅读相关网络框架的源代码时才不会迷茫。也就是说,如果读者想要进行Java高性能后台处理,那么必须要学习Socket,并且它是进行细化学习的基础。

第5章介绍NIO技术中最重要的Selector(选择器)技术。NIO技术的核心——多路复用就是在此章体现的。学习这章内容需要有Socket的编程基础,这就是为什么在前面用两章篇幅来介绍Java的Socket编程的原因。同步非阻塞可以大幅度提升程序运行的效率,就在此章体会一下吧。

第6章介绍AIO。AIO是异步IO,NIO是非阻塞IO。AIO在NIO的基础上实现了异步执行、回调处理等高级功能,可以在不同的场景使用AIO或NIO,可以说NIO和AIO是Java高级程序员、架构师等必须要掌握的技术。勘误和支持

由于笔者的水平有限,加之编写仓促,书中难免会出现一些错误或者不准确的地方,恳请读者批评指正,期待能够得到你们的真挚反馈,在技术之路上互勉共进。若读者想与我进行技术交流,可发电子邮件到279377921@qq.com。致谢

感谢所在单位领导的支持与厚爱,使我在技术道路上更有信心。

感谢机械工业出版社华章公司的高婧雅,始终支持我的写作,你是我最爱的编辑。因为你们的鼓励和帮助,所以我才会如此顺利地完成了这本书的写作。高洪岩第1章缓冲区的使用

学习NIO能更加接近架构级的技术体系,对未来的职业发展有非常好的促进作用。

当你看到以上这段文字的时候,笔者要恭喜你,因为你正在往Java高性能、高并发、高吞吐量技术的道路上迈进,也就代表着未来是有可能将自己的职业规划定位在Java高级程序员、Java资深工程师,以及技术经理、技术总监或首席技术官(CTO)这类职位上。这些职位对Java技术的掌握是有一定要求和标准的,至少笔者认为要将自己对技术的关注点从SSH、SSM分离出去,落脚在多线程、并发处理、NIO及Socket技术上,因为这些技术是开发Java高性能服务器必须要掌握的,甚至有些第三方的优秀框架也在使用这些技术。先不说自己开发框架,即使想要读懂第三方框架的源代码,也要掌握上面提到的多线程、并发处理、NIO及Socket这4种核心技术。当你正在进行SSH、SSM这类Web开发工作时,想要往更高的层次发展,笔者的其他两本书《Java多线程编程核心技术》和《Java并发编程:核心方法与框架》,以及本书一定会带给你非常大的帮助,因为这些内容是Java SE技术中的核心,是衡量一个Java程序员是否合格的明显标志。

在正式开始介绍NIO之前,先简要介绍一下Java SE中的4大核心技术:多线程、并发处理、Socket和NIO。如果你是这些技术的初学者,那么这将帮助你了解这些技术及其用途,以及它们的应用场景。(1)多线程

可以这样说,高性能的解决方案一定离不开多线程,它可以使1个CPU几乎在同一时间运行更多的任务。在指定的时间单位内运行更多的任务,其实就是大幅度提高运行效率,让软件运行更流畅,处理的数据更多,以提升使用软件时的用户体验。在Java中,使用Thread类来实现多线程功能的处理。在学习多线程时,要注意同步与异步的区别,也就是着重观察synchronized关键字在不同代码结构中的使用效果。另外,多线程的随机性,以及多线程运行乱序的可控制性,这些都是在学习该技术时要着重掌握的。在学习Socket之前,建议先掌握多线程技术,因为使用Socket实现某些功能时是需要借助于多线程的。另外在面试时,多线程方面的知识点是被问及比较多的,可见该技术的重要程度。

推荐笔者的拙作《Java多线程编程核心技术》,封面如图1-1所示。(2)并发处理

你可以愉快地使用Thread类来学习编写多线程的应用程序,但在真实的软件项目开发中实现一些较复杂的逻辑时,其实并不是那么容易,因为多线程的随机性、不方便控制性和调试麻烦等特性也许会给开发过程带来麻烦,但好在Doug Lea开发的java.util.concurrent并发包提供了绝大多数常用的功能。concurrent并发包是对多线程技术的封装,使用并发包中的类可以大幅度降低多线程代码的复杂度。使用封装好的API就可以实现以前使用几十行甚至上百行才能实现的功能。使用并发包可以限制访问的流量、线程间的数据交流,在同步处理时使用更加方便和高效率的锁(Lock)对象、读写锁对象,以及可以提高运行效率的线程池,支持异步及回调接口,支持计划任务,支持fork-join分治编程,而且还提供了并发集合框架。上述功能都是Doug Lea的贡献。只有真正地接触到concurrent并发包,才能深刻地体会使用Thread类编程的原始性,会让你的解题思路更加广阔。

推荐笔者的拙作《Java并发编程:核心方法与框架》,封面如图1-2所示。图1-1 Java多线程编程核心技术图1-2 Java并发编程:核心方法与框架(3)Socket

高性能服务器的架构设计离不开集群,集群同样离不开Socket。Socket技术可以实现不同计算机间的数据通信,从而实现在集群中的服务器之间进行数据交换,因此,Socket技术是必须要学习的,它也是工作、面试时经常涉及的知识点。即使你是一位Java语言Socket技术的初学者,如果有C++语言学习的经验,那么在学习Socket技术时会觉得得心应手,因为Java语言中的Socket技术其实是封装了操作系统中Socket编程的API,示例代码如下:JNIEXPORT jobject JNICALL Java_java_net_NetworkInterface_getByIndex0 (JNIEnv *env, jclass cls, jint index){ netif *ifList, *curr; jobject netifObj = NULL; // Retained for now to support IPv4 only stack, java.net.preferIPv4Stack if (ipv6_available()) { return Java_java_net_NetworkInterface_getByIndex0_XP (env, cls, index); } /* get the list of interfaces */ if (enumInterfaces(env, &ifList) < 0) { return NULL; } /* search by index */ curr = ifList; while (curr != NULL) { if (index == curr->index) { break; } curr = curr->next; } /* if found create a NetworkInterface */ if (curr != NULL) { netifObj = createNetworkInterface(env, curr, -1, NULL); } /* release the interface list */ free_netif(ifList); return netifObj;}

上面的代码片段出自:http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/73a9fef98b93/src/windows/native/java/net/NetworkInterface.c。

从上述代码片段中可以发现,使用Java语言开发Socket软件时,内部调用的还是基于操作系统的Socket的API。

如果没有C++编程经验,就不能学习Java中的Socket技术吗?其实也不是,JDK已经将Socket技术进行了重量级的封装,可以用最简单的代码实现复杂的功能,API接口设计得简洁、有序,因此,即使不懂C++,也能顺利地学习Socket编程。掌握C++语言其实是更有益于学习底层对Socket的封装,研究一些细节问题时会应用到。在常规的学习时,掌握C++语言似乎就没有这么大的帮助了。

Socket技术基于TCP/IP,提前了解一些协议的知识更有利于学习Socket。但是TCP/IP规范非常复杂,我们不可能把该协议的所有细节都掌握,只需要掌握TCP与UDP里常规的内容即可,因为这是Socket技术实现网络通信主要使用的协议。是否有书籍把TCP/UDP的理论知识和Socket编程结合起来?真的有这样的书,推荐《UNIX网络编程(卷1):套接字联网API》和《UNIX网络编程(卷2):进程间通信》。这两本书就将TCP/UDP/Socket进行整合并介绍,对TCP和UDP的细节进行文字讲述,并且使用Socket API进行代码的演示,但是演示的代码使用的是C语言实现的,并不是Java,但Java程序员可以以这两本书作为TCP/UDP理论知识的参考。如果你想更加深入、细致地研究TCP/IP编程,这两本书会提供很大帮助。

Socket编程其实就是实现服务端与客户端的数据通信,不管使用任何的编程语言,在实现上基本上都是4个步骤:①建立连接;②请求连接;③回应数据;④结束连接,这4个步骤的流程图如图1-3所示。图1-3 Socket编程流程图

虽然图1-3中使用C语言实现Socket编程,但同样可以使用Java中的ServerSocket和Socket类来代替并实现网络通信。本书中Socket的所有案例都是在这4个步骤的生命周期中再结合ServerSocket和Socket类产生的。

另外,本书是将NIO与Socket相结合的,在学习NIO之前,必须先学习Socket,因为NIO中的核心通道类都是基于Socket技术的通道类。学习Socket时要着重学习Socket Option特性,因为它会影响程序运行的效率。在网络程序优化时,除了优化代码之外,还要优化Socket Option中的参数。本书将Socket有关类中的API几乎进行了全部讲解,因为笔者不希望只列举常用代码,而其他知识点一带而过或根本不介绍的情况发生。学习技术时就要以“全面覆盖,某点深钻”的方式进行全方位学习,这样在阅读第三方框架的源代码时才不会出现现查API的情况,极大地提高了代码阅读效率,也会对TCP/IP编程有更深的认识。(4)NIO

什么是NIO?百度百科中的解释如图1-4所示:图1-4 百度百科解释的NIO

大致来讲,NIO相比普通的I/O提供了功能更加强大、处理数据更快的解决方案,它可以大大提高I/O(输入/输出)吞吐量,常用在高性能服务器上。随着互联网的发展,在大多数涉及Java高性能的应用软件中,NIO是必不可少的技术之一。

NIO实现高性能处理的原理是使用较少的线程来处理更多的任务,如图1-5所示。

使用较少的Thread线程,通过Selector选择器来执行不同Channel通道中的任务,执行的任务再结合AIO(异步I/O)就能发挥服务器最大的性能,大大提升软件运行效率。图1-5 NIO高性能的核心原理图

通过对前面4个核心技术的简单介绍,至少你的思维中不再只是Struts、Spring、Hibernate、MyBatis、SpringMVC、CSS、jQuery、AJAX等这些Java Web技术了,而是需要思考如何组织软件架构、服务器分布、通信优化、高性能处理等这些高级技能,为以后的学习和工作打下坚实的技术基础。

学习NIO能更加接近和了解架构级的技术体系,对未来的职业发展有非常好的辅助作用。1.1 NIO概述

常规的I/O(如InputStream和OutputStream)存在很大的缺点,就是它们是阻塞的,而NIO解决的就是常规I/O执行效率低的问题。即采用非阻塞高性能运行的方式来避免出现以前“笨拙”的同步I/O带来的低效率问题。NIO在大文件操作上相比常规I/O更加优秀,对常规I/O使用的byte[]或char[]进行封装,采用ByteBuffer类来操作数据,再结合针对File或Socket技术的Channel,采用同步非阻塞技术实现高性能处理。现在主流的高性能服务处理框架Netty正是通过封装了NIO技术来实现的,许多第三方的框架都以Netty框架作为底层再进行封装。可以这样认为,想要成为一个合格的Java服务器程序员,NIO技术是必须要掌握的技能。本书也将NIO技术中核心类的API几乎进行了全部覆盖,以让读者全面地掌握NIO和Socket技术。

本章将介绍NIO技术中的核心要点:缓冲区(Buffer)。缓冲区在NIO的使用中占据了很高的地位,因为数据就是存放到缓冲区中,并对数据进行处理的。例如,进行CURD操作时,都是对缓冲区进行处理,也就是数据处理的正确与否与操作缓冲区的正确与否关系紧密。每种缓冲区都有自己独有的API,这些API提供的功能已经足够在大多数的场景下进行软件设计了。那么,我们就开始详细介绍吧!1.2 缓冲区介绍

在使用传统的I/O流API时,如InputStream和OutputStream,以及Reader和Writer联合使用时,常常把字节流中的数据放入byte[]字节数组中,或把字符流中的数据放入char[]字符数组中,也可以从byte[]或char[]数组中获取数据来实现功能上的需求,但由于在Java语言中对array数组自身进行操作的API非常少,常用的操作仅仅是length属性和下标[x]了,在JDK中也没有提供更加方便操作数组中数据的API,如果对数组中的数据进行高级处理,需要程序员自己写代码进行实现,处理的方式是比较原始的,这个问题可以使用NIO技术中的缓冲区Buffer类来解决,它提供了很多工具方法,大大提高了程序开发的效率。

Buffer类的声明信息如图1-6所示。图1-6 Buffer类的声明信息

从Buffer类的Java文档中可以发现,Buffer类是一个抽象类,它具有7个直接子类,分别是ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer、ShortBuffer,也就是缓冲区中存储的数据类型并不像普通I/O流只能存储byte或char数据类型,Buffer类能存储的数据类型是多样的。注意 Buffer类没有BooleanBuffer这个子类。

类java.lang.StringBuffer是在lang包下的,而在nio包下并没有提供java.nio.StringBuffer缓冲区,在NIO中存储字符的缓冲区可以使用CharBuffer类。

NIO中的Buffer是一个用于存储基本数据类型值的容器,它以类似于数组有序的方式来存储和组织数据。每个基本数据类型(除去boolean)都有一个子类与之对应。1.3 Buffer类的使用

在JDK 1.8.0_92版本中,Buffer类的API列表如图1-7所示。

本节会对这些API进行演示和讲解,目的就是让读者全面地掌握NIO核心类—Buffer的使用。

需要注意的是,Buffer.java类是抽象类,并不能直接实例化,而其子类:ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer和ShortBuffer也是抽象类。这7个子类的声明信息如下:public abstract class ByteBuffer extends Bufferpublic abstract class CharBuffer extends Bufferpublic abstract class DoubleBuffer extends Bufferpublic abstract class FloatBuffer extends Bufferpublic abstract class IntBuffer extends Bufferpublic abstract class LongBuffer extends Bufferpublic abstract class ShortBuffer extends Buffer图1-7 Buffer类的API列表

抽象类Buffer.java的7个子类也是抽象类,也就意味着ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer和ShortBuffer这些类也不能被直接new实例化。如果不能直接new实例化,那么如何创建这些类的对象呢?使用的方式是将上面7种数据类型的数组包装(wrap)进缓冲区中,此时就需要借助静态方法wrap()进行实现。wrap()方法的作用是将数组放入缓冲区中,来构建存储不同数据类型的缓冲区。注意 缓冲区为非线程安全的。

下面就要开始介绍Buffer类中全部的API了。虽然Buffer类的7个子类都有与其父类(Buffer类)相同的API,但为了演示代码的简短性,在测试中只使用ByteBuffer或CharBuffer类作为API功能的演示。1.3.1 包装数据与获得容量

在NIO技术的缓冲区中,存在4个核心技术点,分别是:

·capacity(容量)

·limit(限制)

·position(位置)

·mark(标记)

这4个技术点之间值的大小关系如下:

0≤mark≤position≤limit≤capacity

首先介绍一下缓冲区的capacity,它代表包含元素的数量。缓冲区的capacity不能为负数,并且capacity也不能更改。

int capacity()方法的作用:返回此缓冲区的容量。

示例代码如下:public class Test1 {public static void main(String[] args) { byte[] byteArray = new byte[] { 1, 2, 3 }; short[] shortArray = new short[] { 1, 2, 3, 4 }; int[] intArray = new int[] { 1, 2, 3, 4, 5 }; long[] longArray = new long[] { 1, 2, 3, 4, 5, 6 }; float[] floatArray = new float[] { 1, 2, 3, 4, 5, 6, 7 }; double[] doubleArray = new double[] { 1, 2, 3, 4, 5, 6, 7, 8 }; char[] charArray = new char[] { 'a', 'b', 'c', 'd' }; ByteBuffer bytebuffer = ByteBuffer.wrap(byteArray); ShortBuffer shortBuffer = ShortBuffer.wrap(shortArray); IntBuffer intBuffer = IntBuffer.wrap(intArray); LongBuffer longBuffer = LongBuffer.wrap(longArray); FloatBuffer floatBuffer = FloatBuffer.wrap(floatArray); DoubleBuffer doubleBuffer = DoubleBuffer.wrap(doubleArray); CharBuffer charBuffer = CharBuffer.wrap(charArray); System.out.println("bytebuffer=" + bytebuffer.getClass().getName()); System.out.println("shortBuffer=" + shortBuffer.getClass().getName()); System.out.println("intBuffer=" + intBuffer.getClass().getName()); System.out.println("longBuffer=" + longBuffer.getClass().getName()); System.out.println("floatBuffer=" + floatBuffer.getClass().getName()); System.out.println("doubleBuffer=" + doubleBuffer.getClass().getName()); System.out.println("charBuffer=" + charBuffer.getClass().getName()); System.out.println(); System.out.println("bytebuffer.capacity=" + bytebuffer.capacity()); System.out.println("shortBuffer.capacity=" + shortBuffer.capacity()); System.out.println("intBuffer.capacity=" + intBuffer.capacity()); System.out.println("longBuffer.capacity=" + longBuffer.capacity()); System.out.println("floatBuffer.capacity=" + floatBuffer.capacity()); System.out.println("doubleBuffer.capacity=" + doubleBuffer.capacity()); System.out.println("charBuffer.capacity=" + charBuffer.capacity());}}

程序运行结果如下:bytebuffer=java.nio.HeapByteBuffershortBuffer=java.nio.HeapShortBufferintBuffer=java.nio.HeapIntBufferlongBuffer=java.nio.HeapLongBufferfloatBuffer=java.nio.HeapFloatBufferdoubleBuffer=java.nio.HeapDoubleBuffercharBuffer=java.nio.HeapCharBufferbytebuffer.capacity=3shortBuffer.capacity=4intBuffer.capacity=5longBuffer.capacity=6floatBuffer.capacity=7doubleBuffer.capacity=8charBuffer.capacity=4

由于ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer和ShortBuffer是抽象类,因此wrap()就相当于创建这些缓冲区的工厂方法,在源代码中创建的流程示例如图1-8所示。图1-8 创建流程

从源代码中可以发现,通过创建HeapByteBuffer类的实例来实现创建ByteBuffer类的实例。因为ByteBuffer与HeapByteBuffer是父子类的关系,所以在将HeapByteBuffer类的对象赋值给数据类型为ByteBuffer的变量时产生多态关系。

ByteBuffer类缓冲区的技术原理就是使用byte[]数组进行数据的保存,在后续使用指定的API来操作这个数组以达到操作缓冲区的目的,示例代码如图1-9所示。

在HeapByteBuffer类的构造方法中,使用代码super(-1,off,off+len,buf.length,buf,0)调用父类的构造方法将字节数组buf传给父类ByteBuffer,而且子类HeapByteBuffer还重写了父类ByteBuffer中的大部分方法,因此,在调用HeapByteBuffer类的API时,访问的是父类中的buf字节数组变量,在调用API处理buf字节数组中的数据时,执行的是HeapByteBuffer类中重写的方法。图1-9 HeapByteBuffer类构造方法的流程

从源代码中可以了解到,缓冲区存储的数据还是存储在byte[]字节数组中。使用缓冲区与使用byte[]字节数组的优点在于缓冲区将存储数据的byte[]字节数组内容与相关的信息整合在1个Buffer类中,将数据与缓冲区中的信息进行了整合,并进行了封装,这样便于获得相关的信息及处理数据。

capacity代表着缓冲区的大小,效果如图1-10所示。图1-10 容量图示

缓冲区中的capacity其实就是buf.length属性值。1.3.2 限制获取与设置

方法int limit()的作用:返回此缓冲区的限制。

方法Buffer limit(int newLimit)的作用:设置此缓冲区的限制。

什么是限制呢?缓冲区中的限制代表第一个不应该读取或写入元素的index(索引)。缓冲区的限制(limit)不能为负,并且limit不能大于其capacity。如果position大于新的limit,则将position设置为新的limit。如果mark已定义且大于新的limit,则丢弃该mark。

position和mark这两个知识点在后面的章节有详细的介绍,此处只需要理解“限制(limit)代表第一个不应该读取或写入元素的index,缓冲区的limit不能为负,并且limit不能大于其capacity”即可。

limit的应用示例如图1-11所示。图1-11 limit应用示例

虽然图1-11中的缓冲区一共有11个位置可以存放数据,但只允许前4个位置存放数据,后面的其他位置不可以存放数据。因此,JDK API DOC中对limit的解释是:代表第一个不应该读取或写入元素的index。下面再用代码进行验证,测试源代码如下:public class Test2 {public static void main(String[] args) { char[] charArray = new char[] { 'a', 'b', 'c', 'd', 'e' }; CharBuffer buffer = CharBuffer.wrap(charArray); System.out.println("A capacity()=" + buffer.capacity() + " limit()=" + buffer.limit()); buffer.limit(3); System.out.println(); System.out.println("B capacity()=" + buffer.capacity() + " limit()=" + buffer.limit()); buffer.put(0, 'o');// 0 buffer.put(1, 'p');// 1 buffer.put(2, 'q');// 2 buffer.put(3, 'r');// 3--此位置是第一个不可读不可写的索引 buffer.put(4, 's');// 4 buffer.put(5, 't');// 5 buffer.put(6, 'u');// 6}}

程序运行后,在第16行出现异常,如图1-12所示。

在A处打印的值是两个5,说明在调用wrap()方法后,limit的值是capacity+1,因为limit取值范围是从索引0开始,而capacity是从1开始。

Limit使用的场景就是当反复地向缓冲区中存取数据时使用,比如第1次向缓冲区中存储9个数据,分别是A、B、C、D、E、F、G、H、I,如图1-13所示。图1-12 出现异常图1-13 第1次存储9个数据

然后读取全部9个数据,完成后再进行第2次向缓冲区中存储数据,第2次只存储4个数据,分别是1、2、3、4,效果如图1-14所示。图1-14 第2次存储4个数据

当读取时却出现了问题,如果读取全部数据1、2、3、4、E、F、G、H、I时是错误的,所以要结合limit来限制读取的范围,在E处设置limit,从而实现只能读取1、2、3、4这4个正确的数据。1.3.3 位置获取与设置

方法int position()的作用:返回此缓冲区的位置。

方法Buffer position(int newPosition)的作用:设置此缓冲区新的位置。

什么是位置呢?它代表“下一个”要读取或写入元素的index(索引),缓冲区的position(位置)不能为负,并且position不能大于其limit。如果mark已定义且大于新的position,则丢弃该mark。

position应用示例如图1-15所示。图1-15 position应用示例

在图1-13中,position对应的index是3,说明从此位置处开始写入或读取,直到limit结束。

下面用代码来验证position是下一个读取或写入操作的index:public class Test3 {public static void main(String[] args) { char[] charArray = new char[] { 'a', 'b', 'c', 'd' }; CharBuffer charBuffer = CharBuffer.wrap(charArray); System.out.println("A capacity()=" + charBuffer.capacity() + " limit()=" + charBuffer.limit() + " position()=" + charBuffer.position()); charBuffer.position(2); System.out.println("B capacity()=" + charBuffer.capacity() + " limit()=" + charBuffer.limit() + " position()=" + charBuffer.position()); charBuffer.put("z"); for (int i = 0; i < charArray.length; i++) { System.out.print(charArray[i] + " "); }}}

程序运行结果如下:A capacity()=4 limit()=4 position()=0B capacity()=4 limit()=4 position()=2a b z d1.3.4 剩余空间大小获取

方法int remaining()的作用:返回“当前位置”与limit之间的元素数。

方法remaining()的应用示例如图1-16所示。图1-16 方法remaining()应用示例

方法int remaining()的内部源代码如下:public final int remaining() { return limit - position;}

示例代码如下:public class Test4 {public static void main(String[] args) { char[] charArray = new char[] { 'a', 'b', 'c', 'd', 'e' }; CharBuffer charBuffer = CharBuffer.wrap(charArray); System.out.println("A capacity()=" + charBuffer.capacity() + " limit()=" + charBuffer.limit() + " position()=" + charBuffer.position()); charBuffer.position(2); System.out.println("B capacity()=" + charBuffer.capacity() + " limit()=" + charBuffer.limit() + " position()=" + charBuffer.position()); System.out.println("C remaining()=" + charBuffer.remaining());}}

程序运行结果如下:A capacity()=5 limit()=5 position()=0B capacity()=5 limit()=5 position()=2C remaining()=31.3.5 使用Buffer mark()方法处理标记

方法Buffer mark()的作用:在此缓冲区的位置设置标记。

标记有什么作用呢?缓冲区的标记是一个索引,在调用reset()方法时,会将缓冲区的position位置重置为该索引。标记(mark)并不是必需的。定义mark时,不能将其定义为负数,并且不能让它大于position。如果定义了mark,则在将position或limit调整为小于该mark的值时,该mark被丢弃,丢弃后mark的值是-1。如果未定义mark,那么调用reset()方法将导致抛出InvalidMarkException异常。

缓冲区中的mark有些类似于探险或爬山时在关键路口设置“路标”,目的是在原路返回时找到回去的路。

mark的示例代码如下:public class Test5 {public static void main(String[] args) { byte[] byteArray = new byte[] { 1, 2, 3 }; ByteBuffer bytebuffer = ByteBuffer.wrap(byteArray); System.out.println("bytebuffer.capacity=" + bytebuffer.capacity()); System.out.println(); bytebuffer.position(1); bytebuffer.mark(); // 在位置1设置mark System.out.println("bytebuffer.position=" + bytebuffer.position()); bytebuffer.position(2); // 改变位置 bytebuffer.reset(); // 位置重置 System.out.println(); // 回到位置为1处 System.out.println("bytebuffer.position=" + bytebuffer.position());}}

程序运行结果如下:bytebuffer.capacity=3bytebuffer.position=1bytebuffer.position=11.3.6 知识点细化测试

前面介绍了缓冲区4个核心技术点:capacity、limit、position和mark,根据这4个技术点,可以设计出以下7个实验。

1)缓冲区的capacity不能为负数,缓冲区的limit不能为负数,缓冲区的position不能为负数。

2)position不能大于其limit。

3)limit不能大于其capacity。

4)如果定义了mark,则在将position或limit调整为小于该mark的值时,该mark被丢弃。

5)如果未定义mark,那么调用reset()方法将导致抛出InvalidMarkException异常。

6)如果position大于新的limit,则position的值就是新limit的值。

7)当limit和position值一样时,在指定的position写入数据时会出现异常,因为此位置是被限制的。

1.验证第1条

验证:缓冲区的capacity不能为负数,缓冲区的limit不能为负数,缓冲区的position不能为负数。

首先测试一下“缓冲区的capacity不能为负数”,需要使用allocate()方法开辟出指定空间大小的缓冲区,示例代码如下:public class Test1_1 {public static void main(String[] args) { try { ByteBuffer bytebuffer = ByteBuffer.allocate(-1); } catch (IllegalArgumentException e) { System.out.println("ByteBuffer容量capacity大小不能为负数"); }}}

allocate(int capacity)方法分配一个新的缓冲区。

程序运行结果如下:ByteBuffer容量capacity大小不能为负数

然后测试一下“缓冲区的limit不能为负数”,示例代码如下:public class Test1_2 {public static void main(String[] args) { byte[] byteArray = new byte[] { 1, 2, 3 }; ByteBuffer bytebuffer = ByteBuffer.wrap(byteArray); try { bytebuffer = (ByteBuffer) bytebuffer.limit(-1); } catch (IllegalArgumentException e) { System.out.println("ByteBuffer限制limit大小不能为负数"); }}}

程序运行结果如下:ByteBuffer限制limit大小不能为负数

最后测试一下“缓冲区的position不能为负数”,示例代码如下:public class Test1_3 {public static void main(String[] args) { byte[] byteArray = new byte[] { 1, 2, 3 }; ByteBuffer bytebuffer = ByteBuffer.wrap(byteArray); try { bytebuffer = (ByteBuffer) bytebuffer.position(-1); } catch (IllegalArgumentException e) { System.out.println("ByteBuffer位置position大小不能为负数"); }}}

程序运行结果如下:ByteBuffer位置position大小不能为负数

2.验证第2条

验证:position不能大于其limit。

示例代码如下:public class Test2 {public static void main(String[] args) { byte[] byteArray = new byte[] { 1, 2, 3 }; ByteBuffer bytebuffer = ByteBuffer.wrap(byteArray); bytebuffer.limit(2); try { bytebuffer.position(3); } catch (IllegalArgumentException e) { System.out.println("ByteBuffer的position位置不能大于其limit限制"); }}}

程序运行结果如下:ByteBuffer的position位置不能大于其limit限制

3.验证第3条

验证:limit不能大于其capacity。

示例代码如下:public class Test3 {public static void main(String[] args) { byte[] byteArray = new byte[] { 1, 2, 3 }; ByteBuffer bytebuffer = ByteBuffer.wrap(byteArray); try { bytebuffer.limit(100); } catch (IllegalArgumentException e) { System.out.println("ByteBuffer的limit不能大于其capacity容量"); }}}

程序运行结果如下:ByteBuffer的limit不能大于其capacity容量

4.验证第4条

验证:如果定义了mark,则在将position或limit调整为小于该mark的值时,该mark被丢弃。

在此处将第4条拆分成4点来分别进行验证。

1)如果定义了mark,则在将position调整为不小于该mark的值时,该mark不丢弃。

2)如果定义了mark,则在将position调整为小于该mark的值时,该mark被丢弃。

3)如果定义了mark,则在将limit调整为不小于该mark的值时,该mark不丢弃。

4)如果定义了mark,则在将limit调整为小于该mark的值时,该mark被丢弃。

首先验证一下“如果定义了mark,则在将position调整为不小于该mark的值时,该mark不丢弃”,示例代码如下:public class Test4_1 {public static void main(String[] args) { byte[] byteArray = new byte[] { 1, 2, 3 }; ByteBuffer bytebuffer = ByteBuffer.wrap(byteArray); bytebuffer.position(1); bytebuffer.mark(); System.out.println("bytebuffer在" + bytebuffer.position() + "位置设置 mark标记"); bytebuffer.position(2); bytebuffer.reset(); System.out.println(); System.out.println("bytebuffer回到" + bytebuffer.position() + "位置");}}

程序运行结果如下:bytebuffer在1位置设置mark标记bytebuffer回到1位置

然后验证一下“如果定义了mark,则在将position调整为小于该mark的值时,该mark将被丢弃”,示例代码如下:public class Test4_2 {public static void main(String[] args) { byte[] byteArray = new byte[] { 1, 2, 3 }; ByteBuffer bytebuffer = ByteBuffer.wrap(byteArray); bytebuffer.position(2); bytebuffer.mark(); bytebuffer.position(1); try { bytebuffer.reset(); } catch (InvalidMarkException e) { System.out.println("bytebuffer的mark标记无效"); }}}

程序运行结果如下:bytebuffer的mark标记无效

接着验证一下“如果定义了mark,则在将limit调整为不小于该mark的值时,该mark不丢弃”,示例代码如下:public class Test4_3 {public static void main(String[] args) { byte[] byteArray = new byte[] { 1, 2, 3 }; ByteBuffer byteBuffer = ByteBuffer.wrap(byteArray); System.out.println("A byteBuffer position=" + byteBuffer.position() + " limit=" + byteBuffer.limit()); System.out.println(); byteBuffer.position(2); byteBuffer.mark(); System.out.println("B byteBuffer position=" + byteBuffer.position() + " limit=" + byteBuffer.limit()); byteBuffer.position(3); byteBuffer.limit(3); System.out.println(); System.out.println("C byteBuffer position=" + byteBuffer.position() + " limit=" + byteBuffer.limit()); byteBuffer.reset(); System.out.println(); System.out.println("D byteBuffer position=" + byteBuffer.position() + " limit=" + byteBuffer.limit());}}

程序运行结果如下:A byteBuffer position=0 limit=3B byteBuffer position=2 limit=3C byteBuffer position=3 limit=3D byteBuffer position=2 limit=3

最后验证一下“如果定义了mark,则在将limit调整为小于该mark的值时,该mark被丢弃”,示例代码如下:public class Test4_4 {public static void main(String[] args) { byte[] byteArray = new byte[] { 1, 2, 3 }; ByteBuffer byteBuffer = ByteBuffer.wrap(byteArray); System.out.println("A byteBuffer position=" + byteBuffer.position() + " limit=" + byteBuffer.limit()); System.out.println(); byteBuffer.position(2); byteBuffer.mark();

试读结束[说明:试读内容隐藏了图片]

下载完整电子书


相关推荐

最新文章


© 2020 txtepub下载