20. Java新IO --- 学习笔记

来源:互联网 发布:人工智能 呼叫中心 编辑:程序博客网 时间:2024/05/16 06:42

本章目的:

  • 了解新IO操作与传统IO操作的区别
  • 掌握新IO操作中缓冲区的操作原理
  • 了解子缓冲区、只读缓冲区、直接缓冲区的区别及使用
  • 了解新IO中通道的概念,并可以使用通道进行双向操作
  • 掌握文件锁的使用
  • 掌握编码器的作用
  • 了解Selector实现非阻塞服务器的操作

        程序可以通过System.in来获取用户输入的数据,但是,有一个问题,如果用户没有输入信息,则程序会一直等待用户输入,这样会浪费系统资源。为了提升传统IO的操作性能,在JDK1.4之后引入了新IO处理机制 ---- NIO!!!

20.1 Java新IO简介

        NIO提供了一个全新的底层I/O模型。与最初的java.io包中面向流(stream-oriented)的概念不同, NIO中采用了面向块的概念(block-oriented)。这意味着在尽可能的情况下,I/O操作以大的数据块为单位进行,而不是一次一个字节或字符进行。采用这样的操作方式Java的I/O性能已经有了很大的提高。但是也牺牲了Java操作的简单性。

        NIO中提供了与平台无关的非阻塞I/O(nonblocking I/O)。 与面向线程、阻塞式I/O方式相比,多道通信、非阻塞I/O技术可以使应用程序更有效地处理大量连接的情况!!!

=========================================================================================================================

IO的阻塞操作

        IO操作中,接收键盘数据的操作时,只要执行到readLine()方法,程序就要停止等待用户输入数据;在网络编程中,在服务器端使用ServerSocket类的accept()方法时,服务器也是一直处于等待操作,要等待客户端连接。 这两类操作都输入阻塞操作,因为都会让程序暂停执行。

=========================================================================================================================

        新IO并没有在原来的IO基础上开发,而是采用了全新的类和接口,除了原有的功能之外还提供了以下新的特性:

  • 多路选择的非封锁式I/O设施
  • 支持文件锁和内存映射
  • 支持正则表达式的模式匹配设施
  • 字符集编码器和译码器

        在Java新IO中使用Buffer和Channel支持以上的操作~~

20.2 缓冲区与Buffer

        在基本IO操作中所有的操作都是直接以流的形式完成的;而在NIO中所有的操作都要使用到缓冲区处理,且所有的读写操作都是通过缓冲区完成的。

        缓冲区(Buffer)是一个线性的、有序的数据集,只能容纳某种特定的数据类型!!!!!

   20.2.1 Buffer的基本操作

         java.nio.Buffer本身是一个抽象类。其常用方法如下所示:



         在新IO中针对每一种基本数据类型都有一种对应的缓冲区操作类,如下所示:

      在以上7种数据缓冲操作类中,都提供了以下的几组常用方法:

      以上方法针对于各个缓冲区类型都有效!!!

      下面介绍基本的操作流程,以IntBuffer类操作为例。

范例:演示缓冲区的操作流程,同时观擦position、limit和capacity

package org.forfan06.bufferdemo;import java.nio.IntBuffer;public class IntBufferDemo01{public static void main(String args[]){IntBuffer buf = IntBuffer.allocate(10);    //开辟10个大小的缓冲区System.out.print("1、写入数据之前的position、limit和capacity: ");System.out.println("position = " + buf.position() + ", limit = " + buf.limit() + ", capacity = " + buf.capacity());int temp[] = {5, 7, 9};    //定义整型数组buf.put(3);           //向缓冲区写入数据buf.put(temp);         //向缓冲区写入一组数据System.out.print("2、写入数据之后的position、limit和capacity: ");System.out.println("position = " + buf.position() + ", limit = " + buf.limit() + ", capacity = " + buf.capacity());buf.flip();       //重设缓冲区  (在写入之前调用,改变缓冲的指针)System.out.println("3、 准备输出数据时的position、limit和capacity: ");System.out.println("缓冲区中的内容是:");while(buf.hasRemaining()){    //只要缓冲区有内容输出int x = buf.get();     //取出当前内容System.out.print(x + "、");    //输出内容}}}

      程序运行结果:

1、写入数据之前的position、limit和capacity: position = 0, limit = 10, capacity = 102、写入数据之后的position、limit和capacity: position = 4, limit = 10, capacity = 103、 准备输出数据时的position、limit和capacity: 缓冲区中的内容是:3、5、7、9、

      程序解析:

以上程序首先开辟了10个长度的缓冲区空间,之后向缓冲区写入了4个元素,在每次写入之后position都会有变化;而当调用flip()方法时,position和limit将同时产生变化。这实际上就是面向块的操作。

=============================================

写入的数据不能超过规定的缓冲区大小。

      如果只开辟了3个缓冲区,但是却写入了5个数据,则操作会出现以下错误提示:

Exception in thread "main" java.nio.BufferOverflowException

     at java.nio.HeapIntBuffer.put(Unknown Source)

=============================================


   20.2.2 深入缓冲区操作

      在Buffer中存在一系列的状态变量,这些状态变量随着写入或读取都有可能被该表。在缓冲区中可以使用3个值表示缓冲区的状态。

  • position: 表示下一个缓冲区读取或写入的操作指针,当向缓冲区中写入数据时此指针会改变;指针永远放到写入的最后一个元素之后。例如,如果写入了4个位置的数据,则position会指向第5个位置
  • limit: 表示还有多少数据需要存储或读取, position <= limit
  • capacity:表示缓冲区的最大容量, limit <= capacity。 此值在分配缓冲区时被设置,一般不会更改

      在之前的例子中,随着数据向缓冲区中的写入或是重设,对应的position和limit也会改变。 下面将逐步分析这些操作的步骤。如下图所示:

             

   20.2.3 创建子缓冲区

       可以使用各个缓冲区类的slice()方法从一个缓冲区中创建一个新的子缓冲区,子缓冲区与原缓冲区中的部分数据可以共享。

范例: 观察子缓冲区的创建及数据共享

package org.forfan06.bufferdemo;import java.nio.IntBuffer;public class IntBufferDemo02{public static void main(String args[]){IntBuffer buf = IntBuffer.allocate(10);  //开辟10个大小的缓冲区IntBuffer sub = null;  //定义缓冲区对象for(int i = 0; i < 10; i++){buf.put(2 * i + 1);   //加入10个奇数}buf.position(2);  //主缓冲区指针设置在第3个元素上buf.limit(6);  //主缓冲区limit为6sub = buf.slice();  //开辟子缓冲区for(int i = 0; i < sub.capacity(); i++){int temp = sub.get(i);   //根据下标取得元素sub.put(temp - 1);  //修改子缓冲区内容}buf.flip();  //重设缓冲区buf.limit(buf.capacity());  //设置limitSystem.out.print("主缓冲区中的内容: ");while(buf.hasRemaining()){       //只要缓冲区有内容则输出int x = buf.get();       //取出当前内容System.out.print(x + "、");    //输出内容}}}

程序运行结果:

主缓冲区中的内容: 1、3、4、6、8、10、13、15、17、19、
     以上程序中,子缓冲区的内容改变之后主缓冲区的内容也跟着一起变化。 

   20.2.4 创建只读缓冲区

     如果现在需要使用到缓冲区中的内容,但又不希望其内容被修改,则可以通过asReadOnlyBuffer()方法创建一个只读缓冲区。但是创建完毕后,此缓冲区不能变为可写状态!!!

范例:创建只读缓冲区

package org.forfan06.bufferdemo;import java.nio.IntBuffer;public class IntBufferDemo03{public static void main(String args[]){IntBuffer buf = IntBuffer.allocate(10);IntBuffer read = null;for(int i = 0; i < 10; i++){buf.put(2 * i + 1);}read = buf.asReadOnlyBuffer();read.flip();System.out.print("缓冲区中的内容: ");while(read.hasRemaining()){int x = read.get();System.out.print(x + "、");}System.out.println();read.put(30);  //此行会报错, 不可写!!!!}}

程序运行结果:

缓冲区中的内容: 1、3、5、7、9、11、13、15、17、19、Exception in thread "main" java.nio.ReadOnlyBufferExceptionat java.nio.HeapIntBufferR.put(HeapIntBufferR.java:166)at IntBufferDemo03.main(Unknown Source)执行错误
      从程序的运行结果中可以发现,从之前的缓冲区中创建了一个新的缓冲区,新创建的缓冲区可以输出里面的内容,但是一旦修改内容就会出现异常!!

   20.2.5 创建直接缓冲区

     在缓冲区操作类中,只有ByteBuffer可以创建直接缓冲区,这样Java虚拟机将尽最大努力直接对其执行本机的IO操作。也就是说,在每次调用基础操作系统的一个本机I/O操作之前(或之后),虚拟机都会尽量避免将缓冲区的内容复制到中间缓冲区中(或从中间缓冲区中复制内容)。

      创建直接缓冲区时直接使用ByteBuffer类定义如下方法即可:


范例: 创建直接缓冲区

package org.forfan06.bufferdemo;import java.nio.ByteBuffer;public class ByteBufferDemo01{public static void main(String args[]){ByteBuffer buf = null;buf = ByteBuffer.allocateDirect(10);  //开辟直接缓冲区byte temp[] = {1, 3, 5, 7, 9};buf.put(temp);buf.flip();    //重设缓冲区System.out.print("缓冲区中的内容: ");while(buf.hasRemaining()){int x = buf.get();System.out.print(x + "、");}}}

运行结果:

缓冲区中的内容: 1、3、5、7、9、


20.3 通道

     通道(Channel)可以用来读取和写入数据。通道类似于之前的输入/输出流,但是,程序不会直接操作通道,所有的内容都是先读到或写入到缓冲区中,再通过缓冲区中取得或写入的。

      通道与传统的流操作不同: 传统的流操作分为输入或输出流;而通道本身是双向操作的,既可以完成输入也可以完成输出(通过缓冲区Buffer) 

      Channel本身是一个接口, 此接口定义了以下方法:

   20.3.1 FileChannel

      FileChannel是Channel接口的子类。可以进行文件的读/写操作。此类的常用方法如下所示:


     如果要使用FileChannel,则可以依靠FileInputStream或FileOutputStream类中的getChannel()方法取得输入或输出的通道。下面是使用通道写入文本操作的实例

范例:使用输出通道输出内容

package org.forfan06.channeldemo;import java.io.File;import java.io.FileOutputStream;import java.nio.ByteBuffer;import java.nio.channels.FileChannel;public class FileChannelDemo01{public static void main(String args[]) throws Exception{String info[] = {"CSDN", "Java Studying", "www.csdn.net", "forfan06"};   //待输出的数据File file = new File("D:" + File.separator + "out.txt");FileOutputStream output = null;    //文件输出流output = new FileOutputStream(file);   //实例化输出流FileChannel fout = null;   //声明输出的通道对象fout = output.getChannel();   //得到输出的文件通道ByteBuffer buf = ByteBuffer.allocate(1024);   //开辟缓冲区for(int i = 0; i < info.length; i++){   //姜内容写入到缓冲区buf.put(info[i].getBytes());}buf.flip();  //重设缓冲区,准备输出fout.write(buf);   //输出fout.close();   //关闭输出通道output.close();   //关闭输出流}}

       上面程序是使用输出通道将内容全部放到缓冲区中,一次性写入到文件中。实际上FileChannel是双向操作的,同时可以完成输出和输入数据的功能

 范例:使用通道进行读写操作

package org.forfan06.channeldemo;import java.io.File;import java.io.FileInputStream;import java.io.FileOutputStream;import java.nio.ByteBuffer;import java.nio.channels.FileChannel;public class FileChannelDemo02{public static void main(String args[]) throws Exception{File file1 = new File("D:" + File.separator + "note.txt");File file2 = new File("D:" + File.separator + "outnote.txt");FileInputStream input = null;FileOutputStream output = null;input = new FileInputStream(file1);   //实例化输入、输出流output = new FileOutputStream(file2);FileChannel fin = null;   //声明输入、输出的通道对象FileChannel fout = null;fin = input.getChannel();   //得到输入、输出的文件通道fout = output.getChannel();ByteBuffer buf = ByteBuffer.allocate(1024);  //开辟缓冲区int temp = 0;   //声明变量接收内容while((temp = fin.read(buf)) != -1){    //如果没有读到文件结尾buf.flip();   //重设缓冲区fout.write(buf);   //输出缓冲区buf.clear();   //清空缓冲区}fin.close();    //关闭输入、输出通道fout.close(); input.close();    //关闭输入/输出流output.close();  }}


   20. 内存映射

      内存映射可以把文件映射到内存中,这样文件内的数据就可以用内存读/写指令来访问,而不是用InputStream或OutputStream这样的I/O操作类,采用此种方式读取文件的速度是最快的!!!!

=================================================================================================

Java中访问文件内容的4种方法:

  • RandomAccessFile, 随机读取数据,此种访问速度较慢
  • FileInputStream,文件输入流,使用此种方式速度较慢
  • 缓冲读取(例如BufferedReader),使用此种方式访问速度较快
  • 内存映射(MappedByteBuffer), 使用此种方式读取速度最快!!!!!!

=================================================================================================

       要想将文件映射到内存中,可以使用FileChannel类提供的map()方法,此方法定义如下:


    map()方法在使用时要指定映射模式!!!在内存映射中提供了3种模式,此3种模式分别由FileChannel类中的3个常量表示,如下所示:

范例:内存映射

package org.forfan06.channeldemo;import java.io.File;import java.io.FileInputStream;import java.nio.MappedByteBuffer;import java.nio.channels.FileChannel;public class  FileChannelDemo03{public static void main(String args[]) throws Exception{File file = new File("D:" + File.separator + "personal.txt");FileInputStream input = null;  //文件输入流input = new FileInputStream(file);   //声明输入的通道对象FileChannel fin = null;   //声明输入的通道对象fin = input.getChannel();    //得到输入文件通道MappedByteBuffer mbb = null;    //声明文件的内存映射mbb = fin.map(FileChannel.MapMode.READ_ONLY, 0, file.length());   //将文件映射到内存中byte data[] = new byte[(int) file.length()];   //开辟字节数组,接收数据int foot = 0;   //定义下标while(mbb.hasRemaining()){   //判断是否有数据data[foot++] = mbb.get();   //取出数据}System.out.println(new String(data));  //显示输入的数据fin.close();   //关闭输入通道input.close();   //关闭输入流}}

     尽管创建内存映射文件非常简单,但是,如果使用MappedByteBuffer写入数据就可能非常危险。 因为仅仅是改变数组中的单个元素的内容这样的简单操作,就有可能直接修改磁盘上的具体文件,因为修改数据与数据重新保存到磁盘是一样的!!!!


20.4 文件锁: FileLock

     在Java新IO中提供了文件锁的功能,这样当一个线程将文件锁定之后,其他线程是无法操作此文件的。
     要想进行文件的锁定操作,则要使用FileLock类完成,此类的对象需要依靠FileChannel类进行实例化操作!!!
    
     FileChannel类中提供了以下几个方法取得FileLock类的实例化对象!!!


      上图中所示的文件锁定方式有2种:
  • 共享锁: 允许多个线程进行文件的读取操作
  • 独占锁: 只允许一个线程进行文件的读/写操作
       文件锁定之后需要依靠FileLock类进行解锁。FileLock类的常用方法如下所示:

范例:将D:\csdn.txt文件锁定
package org.forfan06.filelockdemo;import java.io.File;import java.io.FileOutputStream;import java.nio.channels.FileChannel;import java.nio.channels.FileLock;public class FileLockDemo01{public static void main(String args[]) throws Exception{File file = new File("D:" + File.separator + "csdn.net");FileOutputStream output = null;  //文件输出流output = new FileOutputStream(file);  //实例化输出流FileChannel fout = null;    //声明输出的通道对象fout = output.getChannel();  //得到输入文件通道FileLock lock = fout.tryLock();   //试图获得此通道的文件锁if(lock != null){System.out.println(file.getName() + "文件锁定300秒");Thread.sleep(300000);   //将文件锁定300秒lock.release();  //释放文件锁System.out.println(file.getName() + "文件解除锁定");}fout.close();    //关闭输出通道output.close();     //关闭输出流}}
         上面程序在运行时,将文件进行独占锁定,这样其他线程在锁定的3000秒内是无法对该文件进行读写操作的。

20.5 字符集: Charset

       在Java语言中所有的信息都是以UNICODE进行编码的,但是在计算机的世界里并不只单单存在一种编码,而是存在多种编码;如果对编码处理不当,就有可能产生乱码。

       Java的新IO包中提供了Charset类来负责编码的问题!!Charset类还包含了创建编码器(CharsetEncoder)和创建解码器(CharsetDecoder)的操作。

==========================================================================================

  • 编码器和解码器

       编码和解码实际上是从最早的电报发展起来的,所有的内容如果需要使用电报传送,则必须变为相应的编码,之后再通过指定的编码进行解码的操作。

       在新IO中为了保证程序可以适应各种不同的编码,所以提供了编码器和解码器!!通过解码器程序可以方便地读取各个平台上不同编码的数据,之后再通过编码器将程序的内容以正确的编码进行输出。

==========================================================================================

        Charset类的常用操作如下图所示:


     CharsetEncoder类和CharsetDecoder类的常用方法:


     如果想要对内容进行编码,则首先应该掌握Charset类中所支持的全部编码。 可以参照下面的范例

范例:取得Charset类的全部编码

package org.forfan06.charsetdemo;import java.nio.charset.Charset;import java.util.Iterator;import java.util.Map;import java.util.SortedMap;public class GetAllCharsetDemo{public static void main(String args[]){SortedMap<String, Charset> all = null;   //声明SortedMap集合all = Charset.availableCharsets();   //取得全部编码Iterator<Map.Entry<String, Charset>> iter = null;  //声明Iterator对象iter = all.entrySet().iterator();  //实例化Iterator对象while(iter.hasNext()){Map.Entry<String, Charset> me = iter.next();System.out.println(me.getKey() + "--->" + me.getValue());}}}
      程序运行结果:

Big5--->Big5Big5-HKSCS--->Big5-HKSCSEUC-JP--->EUC-JPEUC-KR--->EUC-KRGB18030--->GB18030GB2312--->GB2312GBK--->GBKIBM-Thai--->IBM-ThaiIBM00858--->IBM00858

       运行上面程序后,将返回全部支持的字符集,Map集合中的key保存的是每种编码的别名,在实际使用时,可以使用forName()方法根据编码的别名实例化CharSet对象。


     下面演示对d:\csdn.txt中的内容通过CharsetEncoder和CharsetDecoder类来使用ISO-8859-1编码的过程。

范例: 编码-解码操作

package org.forfan06.charsetdemo;import java.nio.ByteBuffer;import java.nio.CharBuffer;import java.nio.charset.Charset;import java.nio.charset.CharsetEncoder;import java.nio.charset.CharsetDecoder;public class CharsetCoderDemo01{public static void main(String args[]) throws Exception{Charset latin1 = Charset.forName("ISO-8859-1");  //以ISO-8859-1编码CharsetEncoder encoder = latin1.newEncoder();   //实例化编码、解码对象CharsetDecoder decoder = latin1.newDecoder();//通过CharBuffer类中的wrap()方法,将一个字符串变为CharBuffer类型//CharBuffer cb = CharBuffer.wrap("欢迎光临小站");   此处如果是中文输入,则会抛出UnmappableCharacterException异常~~//客户端的输入编码不对。设置一下即可 --〉  http://daizuan.iteye.com/blog/1112909/* * 另外下面这块可以正常运行 *Charset latin2 = Charset.forName("UTF-8"); *CharBuffer cb = CharBuffer.wrap("欢迎光临小站");*/CharBuffer cb = CharBuffer.wrap("Welcome to My Website!!!");ByteBuffer buf = encoder.encode(cb);   //进行编码、解码操作CharBuffer cbuf = decoder.decode(buf);System.out.println(cbuf);   //输出}}

         程序若以被注释掉的语句块运行将 以ISO-8859-1的方式显示中文,这样肯定是无法显示的,会造成乱码问题(运行时抛出UnmappableCharacterException异常)。

====================================================================================

CharsetEncoder和CharsetDecoder经常使用在文件的读写上,以实现文件编码的转换功能!!!!!

====================================================================================

20.6 Selector

      在新IO中Selector是一个极其重要的概念,在原来使用IO和Socket构造网络服务时,所有的网络服务将使用阻塞的方式进行客户端的连接;而如果使用了新IO则可以构造一个非阻塞的网络服务!!

      Selector类的常用方法如下所示:

       在进行非阻塞网络开发时需要使用SelectableChannel类向Select类注册!!!

       而且在新IO中实现网络程序需要依靠ServerSocketChannel类与SocketChannel类,这两个类都是SelectableChannel的子类,SelectableChannel提供了注册Selector的方法和阻塞模式。

       ServerSocketChannel类的常用方法如下所示:

       在使用register()方法时需要指定一个选择器(Selector对象)以及Select域。 其中, Selector对象可以通过Selector中的open()方法取得;而Selector域则在SelectionKey类中定义,如下所示:

       通过下面代码可以建立一个非阻塞的服务器端:

int ports = 8888;  //定义连接的端口号Selector selector = Selector.open();  //打开一个连接器ServerSocketChannel iniSer = null;  //声明ServerSocketChannel对象initSer = ServerSocketChannel.open();   //打开服务器套接字通道initSer.configureBlocking(false);  //服务器配置为非阻塞ServerSocket initSock = initSer.socket();  //检索与此通道关联的服务器套接字InetSocketAddress address = null;  //表示监听地址address = new InetSocketAddress(ports);  //实例化绑定地址initSock.bind(address);  //绑定地址initSer.register(selector, SelectionKey.OP_ACCEPT);  //注册选择器,相当于使用accept()方法接收System.out.println("服务器运行,在" + ports + "端口监听");

      在上面代码中,首先通过Selector.open()方法打开一个选择器,之后通过ServerSocketChannel类中的open()方法打开一个服务器套接字通道。

      程序中最重要就是configureBlocking()方法,将服务器设置为非阻塞状态,之后通过ServerSocketChannel的socket()方法返回一个ServerSocket对象,并在8888端口绑定服务器的监听端口。

      最后,使用register()方法将选择器注册为accept方法,等待客户端连接。


      如果要使用服务器向客户端发送信息,则需要通过SelectionKey类中提供的方法判断服务器的操作状态。要想取得客户端的连接也需要使用SelectionKey类。下面是SelectionKey类中的常用方法:

      因为SelectionKey中提供了4种操作状态,所以4种状态也对应了4个isXxx()方法!!!取得SelectionKey的操作方法如下:

int keysAdd = 0;  //接收一族SelectionKeywhile((keysAdd = selector.select()) > 0){  //选择一组键,相应的通道已经为IO准备就绪Set<SelectionKey> selectedKeys = selector.selectedKeys();  //取出全部生成的keyIterator<SelectionKey> iterator = selectedKeys.iterator();  //实例化Iterator对象while(iterator.hasNext()){SelectionKey key = (SelectionKey) iter.next();    //取出每一个SelectionKeyif(key.isAcceptable()){   //判断客户端是否已经连接上//执行服务器的输出操作}}selectedKeys.clear();  //清除全部的key}

     下面,通过Selector创建一个非阻塞的服务器,此服务器向客户端返回当前的系统时间

范例:取得服务器的时间

package org.forfan06.selectordemo;import java.net.InetSocketAddress;import java.net.ServerSocket;import java.nio.ByteBuffer;import java.nio.channels.SelectionKey;import java.nio.channels.Selector;import java.nio.channels.ServerSocketChannel;import java.nio.channels.SocketChannel;import java.util.Data;import java.util.Iterator;import java.util.Set;public class DateServer{public static void main(String args[]) throws Exception{int ports[] = {8000, 8001, 8002, 8003, 8005, 8006};  //定义一组连接端口号Selection selector = Selector.open();    //打开一个连接器for(int i = 0; i < ports.length; i++){   //构造服务器的启动信息ServerSocketChannel initSer = null;   //声明ServerSocketChannel对象initSer = ServerSocketChannel.open();  //打开服务器套接字通道initSer.configureBlocking(false);  //服务器配置为非阻塞ServerSocket initSock = initSer.socke();  //检索与此通道关联的服务器套接字InetSocketAddress address = null; //表示监听地址address = new InetSocketAddress(ports[i]);  //实例化绑定地址initSock.bind(address);   //绑定地址//注册选择器,相当于使用accept()方法接收客户端连接initSer.register(selector, SelectionKey.OP_ACCEPT);System.out.println("服务器运行,在" + ports[i] + "端口监听");}int keysAdd = 0;  //接收一组SelectionKeywhile((keysAdd = selector.select()) > 0){   //选择一组键,相应的通道已为IO准备就绪Set<SelectionKey> selectedKeys = selector.selectedKeys();  //取出全部生成的keyIterator<SelectionKey> iterator = selectedKeys.iterator();  //实例化Iterator对象while(iterator.hasNext()){    //迭代SelectionKey key = (SelectionKey) iterator.next();if(key.isAcceptable()){      //判断客户端是否已经连接上ServerSocketChannel server = (ServerSocketChannel) key.channel();   //取得ChannelSocketChannel client = server.accept();  //接收新连接client.configureBlocking(false);  //设置成非阻塞状态ByteBuffer outBuf = ByteBuffer.allocateDirect(1024);  //开辟直接缓冲区outBuf.put(("当前时间为: " + new Date()).getBytes());   //向缓冲区设置内容outBuf.flip();   //重置缓冲区client.write(outBuf);   //输出信息client.close();   //关闭输出流}}SelectedKeys.clear();   //清除全部的key}}}

     上面程序运行之后,程序将在8000、8001、8002、8003、8005、8006这6个端口进行服务器的监听,等待客户端连接,客户端连接后将返回系统的当前时间。

      客户端可以直接使用普通的Socket闯将或使用telnet命令进行连接。

=====================================================================================

  • 服务器运行后并不会退出

在使用Selector实现的服务器操作代码中,程序执行完后并不会像传统的Socket那样立刻关闭服务器,而是会继续等待下一次的连接!!!!!  

=====================================================================================

20.7 本章要点

  1. 使用Java新IO可以提升传统IO的操作性能

  2. 在新IO中所有的读、写操作都是通过缓冲区完成!!!缓冲区中只能容纳特定的数据类型!!!

  3. 在缓冲区中使用position、limit、capacity表示缓冲区的操作状态

  4. 通道提供了双向的读、写操作

  5. 使用内存映射可以提升输入流的性能

  6. 如果一个线程操作一个文件时,不希望其他线程进行访问,则可以通过FileLock锁定文件

  7. 在Java新IO中可以使用CharsetEncoder和CharsetDecoder完成编码的转换操作

  8. 使用Java新IO中的Selector可以构造非阻塞的服务器网络!!!!!


0 0