NIO最全入门
1.NIO概述
在jdk4中引入了NIO ,可以最大限度的满足java程序I/O的需求
java.nio包,定义了各种与buffer相关的类
java.nio.channel包,包含了与Channel和Selector相关的类
java.nio.charset包,与字符集相关的类
在NIO中有三大核心组件:Channel,Buffer,Selector
传统的i/o面向流的,每次可以从流中读取一个或多个字节,只能向后读取,不能向前移动,NIO是面向缓冲区的,把数据读到缓冲区中,可以在缓冲区中向前向后移动,增加了程序的灵活性。
IO与NIO的区别
IO流是线程阻塞的,再调用read()和write()读写数据时,线程阻塞, 知道数据读取完毕或者数据完全写入,在读写过程中线程不能做其他任务。
NIO是非阻塞的,当线程从channel中读取数据时,如果通道中没有可用的数据,线程不会阻塞,可以做其他的任务。
2.Buffer
2.1Buffer的属性
buffer缓冲区实际上就是一个数组,把数组的内容与信息包装成一个Buffer对象,它提供了一组访问这些信息的方法。
缓冲区的重要性:
capacity容量,是指缓冲区可以存多少数据,容量在创建buffer缓冲区是指 定大小,创建后不能再修改,如果缓冲区满了,需要清空后才能继续写数据
position表示当前位置,即缓冲区写入或读取的位置。刚刚创建Buffer对象后,position就向后移动一个单元,它的最大值是capacity-1,当Buffer从写模式切换到读模式,position会被重置为0,从buffer的开始位置读取数据,每读一个数据position就像后移动一个单元
limit上限,是指第一个不能被读出或写入的位置,limit上限后面的单元既不能读也不能写,在buffer缓冲区的写模式下,limit表示能够写入多少个数据,在读取模式下,limit表示最多可以读取多少个数据
mark标记,设置一个标记位置,可以调用mark()方法,把标记就设置在position位置,当调用reset方法时,就把postion设置为mark标记位
0 <= mark <= position <= capa city
2.2Buffer的常用api
在NIO中关键的buffer有:ByteBuffer,CharBuffer,DoubleBuffer,FloatBuffer,IntBuffer,LongBuffer,ShortBuffer.这些Buffer覆盖了能通过IO发送的所有基本类型:byte,char,int,double,float,long,short,s实际当中使用较多时ByteBuffer,CharBuffer.
每个缓冲区类 都有一个静态方法allocate(capacity)可以创建一个指定容量的缓冲区;
都有一个put()方法用于向缓冲区中存储数据,get()方法用于从缓冲区中读取数据;
当缓冲区中,还有未读完的数据,可以调用compact()方法进行压缩,将所有未读取的数据复制到Buffer的起始位置,把position设置到最后一个未读取元素的后面,limit属性设置为capacity.
capacity()可以返回缓冲区的大小。
hasRemaining(),判断当前position后面是否还有可处理数据,即判断position和Limit之间是否还有数据可处理。
limit()返回limit上限的位置
mark()设置缓冲区的标志位置,这个值只能在0~position之间,可以通过reset()返回到这个位置。
position()可以返回position当前位置
remaining()返回当前position位置与limit之间的数据量
reset()方法可以将position设置为mark标志位
rewind()将position设置为0,取消mark标志位
clear()清空缓冲区,仅仅是修改position标志为0,设置limit为capacity,缓冲区中数据还是存在的
flip()可以把缓冲区由写模式切换到读模式,先把limit设置为position位置,在把position设置为0。
2.3Buffer中aip的使用
`package com.xysf.channel;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.Buffer;
import java.nio.CharBuffer;
public class FileChannelDemo1 {
public static void main(String[] args) {
//1.创建CharBuffer()缓冲区对象
CharBuffer charBuffer = CharBuffer.allocate(12);
//2.打印position,limit,Capacity
System.out.println("posistion:"+charBuffer.position()+" limit:"+charBuffer.limit()+" capacity:"+charBuffer.capacity());
//向缓冲区中存储数据put()
char [] arr ={'我','是','谁','我','在','哪','里'};
charBuffer.put(arr);
System.out.println("posistion:"+charBuffer.position()+" limit:"+charBuffer.limit()+" capacity:"+charBuffer.capacity());
//4.调用flip(),把缓冲区切换为读模式
charBuffer.flip();
System.out.println("posistion:"+charBuffer.position()+" limit:"+charBuffer.limit()+" capacity:"+charBuffer.capacity());
//5.调用get()方法读取缓冲区中的数据
System.out.println(charBuffer.get());
System.out.println("posistion:"+charBuffer.position()+" limit:"+charBuffer.limit()+" capacity:"+charBuffer.capacity());
//再次存储数据,把数据存储到position位置
charBuffer.put('神');
System.out.println("posistion:"+charBuffer.position()+" limit:"+charBuffer.limit()+" capacity:"+charBuffer.capacity());
//7.设置标志位mark();
charBuffer.mark();//将position的位置设为标志位。位置为2
//8.在读取一个数据
System.out.println(charBuffer.get());
System.out.println("posistion:"+charBuffer.position()+" limit:"+charBuffer.limit()+" capacity:"+charBuffer.capacity());
//9.调用reset(),将position重置为mark()标记位
charBuffer.reset();
System.out.println("posistion:"+charBuffer.position()+" limit:"+charBuffer.limit()+" capacity:"+charBuffer.capacity());
//10.调用compact压缩,把Buffer中未读取的数据复制到position为0的位置
charBuffer.compact();
System.out.println("posistion:"+charBuffer.position()+" limit:"+charBuffer.limit()+" capacity:"+charBuffer.capacity());
//调用clear()清空仅仅是修改position/limit的1值,
charBuffer.clear();
System.out.println("posistion:"+charBuffer.position()+" limit:"+charBuffer.limit()+" capacity:"+charBuffer.capacity());
//通过循环可以将position到limit之间的所有值打印
while(charBuffer.hasRemaining()){
System.out.println(charBuffer.get());
}
}
}`
2.4创建缓冲区的两种方式
分配操作创建缓冲区,allocate()方法分配一个私有的,指定容量大小的数组来存储元素;
包装操作来创建缓冲区,它使用提供的数组作为存储空间来存储缓冲区中的数据,不在分配其他空间。
package com.xysf.channel;
import java.nio.CharBuffer;
import java.util.Arrays;
public class Test2 {
public static void main(String[] args) {
//1>分配操作创建缓冲区
CharBuffer allocate = CharBuffer.allocate(16);
//使用包装操作创建缓冲区
char [] arr=new char[16];
CharBuffer charBuffer=CharBuffer.wrap(arr);
//通过put向缓冲区中保存数据,也会直接影响到数组
charBuffer.put("hello");
charBuffer.flip();//反转切换到读模式
System.out.println(charBuffer);
System.out.println(Arrays.toString(arr));
//数组的任何操作也会影响到缓冲区对象
arr[0]='X';
System.out.println(charBuffer);
/*
* 不管是allocate()还是通过wrap()创建的缓冲区都是间接的,简介缓冲区会使用备份数组
* hasArray()方法可以判断是否有一个可存取的备份数组
* 如果hasArray()方法返回true,可以通过array()返回缓冲区对象使用的备份数组的引用
* */
if (charBuffer.hasArray()) {
char[] array = charBuffer.array();
System.out.println(Arrays.toString(array));
}
}
}
2.5缓冲区的复制与分割
复制缓冲区,duplicate()复制的缓冲区和原来的缓冲区实际引用的同一个数组
分割缓冲区,slice()方法根据【position,limit】区间创建一个新的缓冲区
package com.xysf.channel;
import java.nio.CharBuffer;
public class Test03 {
public static void main(String[] args) {
//1.创建缓冲区
CharBuffer allocate = CharBuffer.allocate(16);
//2.存储数据
CharBuffer put = allocate.put("hello");
System.out.println("allocate.position="+allocate.position());
//3,缓冲区的复制
CharBuffer duplicate = allocate.duplicate();
System.out.println("duplicate.capacity="+duplicate.capacity()+",duplicate.limit="+duplicate.limit()+",duplicate.position="+duplicate.position());
//duplicate.capacity=16,duplicate.limit=16,duplicate.position=5
duplicate.flip();//翻转
System.out.println(duplicate);//hello
//allocate与duplicate实际引用同一个数组
duplicate.clear();
duplicate.put("nioworld");
//要输出allocate缓冲区中的内容
allocate.flip(); //翻转,把limit设置为position的值5
System.out.println(allocate);//niowo
//分割缓冲区,slice()方法根据【position,limit】区间创建一个新的缓冲区
duplicate.position(3);
System.out.println("duplicate.capacity="+duplicate.capacity()+",duplicate.limit="+duplicate.limit()+",duplicate.position="+duplicate.position());
CharBuffer slice = duplicate.slice();
System.out.println("slice.capacity="+slice.capacity()+",slice.limit="+slice.limit()+",slice.position="+slice.position());
//slice.capacity=13,slice.limit=13,slice.position=0
System.out.println(slice);
}
}
2.6直接字节缓冲区
在在硬盘中和操作系统中处理的的数据都是01二进制,缓冲区中只有ByteBuffer字节缓冲区有资格参与I/O操作.
Channel通道只能使用ByteBuffer作为他的参数
直接字节缓冲区通常是I/O操作的最好的选择,但是如果使用非直接字节缓冲区可能会导致性能损耗,如果向通道传递一个非直接字节缓冲区,通道可能会先创建一个临时的直接字节缓冲区,将非直接字节缓冲区的内容复制到临时的直接字节缓冲区中,使用临时直接直接字节缓冲区执行底层的I/O操作。
直接字节缓冲区是I/O的最佳选择,但可能创捷直接字节缓冲区的成本比非直接字节缓冲区的成本要高,直接缓冲区使用内存是通过调用本地操作系统代码分配的,绕过了JVM的堆栈,现在jvm可能会执行缓冲区缓存的优化,入门的话不要考虑程序的优化问题,先保证程序的正确性。
ByteBuffer.allocateDirect()方法创建直接字节缓冲区
3.Channel
3.1 Channel概述
Channel是一种新的I/O的访问方式,用于在字节缓冲区与通道另一侧的实体(可以是文件也可以是socket)之间进行数据传输
Channel可以双向读取数据,也可以异步读写
程序不能直接访问Channel ,Channel只能与Buffer缓冲区进行交互,即把通道中的数据读到Buffer缓冲区中,程序从缓冲区读取数据,在写操作时,程序把数据写入缓冲区中,再将缓冲区中的数据写入Channel通道中。
3.1.1常用的Channel
FileChannel,读写文件的Channel
SocketFannel/ServerSocketChannel,读写socket套接字中的的数据
DatagramChannel,通过UDP读写网络中的数据
3.2 Scatter/Gather
Scatter(发散) Gather(聚焦)是通道提供的一个重要功能(有时也称为矢量I/O) , Scatter/Gather 是指在多个缓冲区中实现一个简单的I/O操作
Scatter是指从Channel通道中读取数据,把这些数据按顺序分散写入到多个Buffer缓冲区中
Gather是指在写操作时,将多个缓冲区当中的数据写入到同一个Channel通道中.
Scatte和Gather经常用于需要将传输的数据分开处理的场景
如Scatter从一个Channel中读取数据存储到多个Buffer
ByteBuffer buf1=ByteBuffer.allocate(48);
ByteBuffer buf2=ByteBuffer.allocate(1024);
ByteBuffer [] bufferArray={buf1,buf2};
Channel.read(buferArray);
read()方法从Channel中读取数据,按照buffer在数组中的存储顺序依次存 储到缓冲区中,注意必须在buf1满后才能存储到buf2缓冲区中,使用Scatter和Gather处理的数据大小都是固定的。
3.3 FileChannel
FileChannel通过RandomAccessFile,FileInputStream,FileOutputStream对象调用getChannel()方法获取
FileChannel通道虽然是双向的,即可以读也可以写,但是从FileInputStream中获取的通道只能读不能写,如果进行写会抛出异常;从FileOutputStream流中获得的通道只能写不能读,如果访问的文件是只读的也不能进行写操作。
FileChannel是线程安全的,但是并不是所有的操作都是多线程的,如影响通道位置或文件大小的操作都是单线程的。
3.3.1.内存映射文件
FileChannel的map()方法可以将磁盘上的文件映射到虚拟内存中,把这块虚拟内存包装为一个MappedByteBuffer对象,(当一个文件映射到虚拟内存中,文件的内容通常不会从硬盘中读取到内存)
package com.xysf.channel;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.CharBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.nio.charset.Charset;
public class Test04 {
public static void main(String[] args) {
//把当前Test2.java文件以文件内存的方式读取到out.txt文件中
//创建当前文件的File对象
File file=new File("src/com/xysf/channel/Test2.java");
try (
FileChannel inChannel = new FileInputStream(file).getChannel();
FileChannel outChannel=new FileOutputStream("out.txt").getChannel();
){
//将inChannel中的数据映射到虚拟内存当中
MappedByteBuffer buffer = inChannel.map(MapMode.READ_ONLY, 0,file.length());
outChannel.write(buffer);
//也可以将Buffer中的内容打印出来
buffer.flip();
//创建字符集
Charset defaultCharset = Charset.defaultCharset();
//使用默认字符集把buffer中的字节转换为字符
CharBuffer decode = defaultCharset.decode(buffer);
System.out.println(decode);
} catch (IOException e) {
// TODO: handle exception
}
}
}
3.3.2 FileChannel的双向传输
package com.xysf.channel;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
/*
* 使用RandomAccessFile获得的通道是双向的
* 把out.txt文件内容复制,追加到该文件的后面
* */
public class Test05 {
public static void main(String[] args) {
//创建File
File file = new File("out.txt");
try (RandomAccessFile randomAccessFile=new RandomAccessFile(file,"rw");
FileChannel channel = randomAccessFile.getChannel();
){
//把channel中的数据映射到虚拟内存当中
MappedByteBuffer buffer = channel.map(MapMode.READ_ONLY, 0, file.length());
//设置channel的position属性
channel.position(file.length());
channel.write(buffer);//续写
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}
3.3.3设置缓冲区固定大小
package com.xysf.channel;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
/*
* 读写文件时设置缓冲区为固定大小
* */
public class Test06 {
public static void main(String[] args) {
try (
FileChannel inchannel = new FileInputStream("out.txt").getChannel();
FileChannel outChannel=new FileOutputStream("out2.txt").getChannel();
){
ByteBuffer buffer = ByteBuffer.allocate(48);
int read=inchannel.read(buffer);
//当read不为-1时,没有读取到末尾文件
while(read!=-1){
//将buffer中的内容写道OutChannel中
buffer.flip();//切换为
outChannel.write(buffer);
buffer.clear();
read=inchannel.read(buffer);
}
}
catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}
3.3.4 Channel到Channel的传输
经常需要把文件从一个位置批量传输到另一个位置,可以直接使用通道到通道之间的传输,不需要中间缓冲区传递数据
注意,只有FileChannel支持通道到通道之间的传输
通道到通道之间的传输十分迅捷,有的操作系统可以不使用用户空间直接传输数据。
package com.xysf.channel;
import java.io.File;
import java.io.FileOutputStream;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
/*
* 通道到通道之间的传递
* */
public class Test07 {
public static void main(String[] args) {
//把文件out.txt复制到out3.txt中
File file = new File("out.txt");
try(
RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");
FileChannel inChannel= randomAccessFile.getChannel();
FileChannel outChannel=new FileOutputStream("out3.txt").getChannel();
){
//把inChannel从0开始的所有字节传输到OutChannel中
//inChannel.transferTo(0, file.length(), outChannel);
//outChannel中的数据来自inChannel从0开始的所有字节
outChannel.transferFrom(inChannel,0, file.length());
}catch (Exception e) {
// TODO: handle exception
}
}
}
3.3.5 Gather
把文件的属性和文件内容分别存储到不同的缓冲区当中,再写入到另外一个文件中
package com.xysf.channel;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URLConnection;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.text.SimpleDateFormat;
import java.util.Date;
/*
* 把文件的属性和文件的内容,通过Gather形式写到另一个文件中
* */
public class Test08 {
public static void main(String[] args) throws IOException {
File file = new File("out.txt");//读取文件
//获得文件属性
String absolutePath = file.getAbsolutePath();//获得绝对路径
long lastModified = file.lastModified();//最后一次修改时间
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String format = simpleDateFormat.format(new Date(lastModified));
String headerText="filename:"+absolutePath+"\r\n"+"last modified:"+format+"\r\n";
//把文件属性存储在缓冲区中
ByteBuffer header = ByteBuffer.wrap(headerText.getBytes());
ByteBuffer contentBuf = ByteBuffer.allocate(128);//用来存储文件类型
//创建一个缓冲区数组
ByteBuffer[] gather ={header,contentBuf,null};
String contentType="unknown";
long contentLength=-1;
try {
FileInputStream fileInputStream = new FileInputStream(file);
FileChannel fc = fileInputStream.getChannel();
MappedByteBuffer byteBuffer = fc.map(MapMode.READ_ONLY,0,fc.size());
//将文件内容保存到gather中
gather[2]=byteBuffer;
contentLength=fc.size();
contentType = URLConnection.guessContentTypeFromName(file.getAbsolutePath());
} catch (Exception e) {
ByteBuffer byteBuffer = ByteBuffer.allocate(128);
String msg="文件访问异常"+e+"\n";
byteBuffer.put(msg.getBytes());
byteBuffer.flip();//切换为读模式
gather[2]=byteBuffer;//将文件的异常信息存储到数组中
}
StringBuilder sb=new StringBuilder();
sb.append("contentLength:").append(contentLength).append("\r\n");
sb.append("contentType:").append(contentType).append("\r\n");
contentBuf.put(sb.toString().getBytes());
contentBuf.flip();
//把gather中的数组写到目标文件中
FileOutputStream outputStream = new FileOutputStream("gather.txt");
FileChannel channel = outputStream.getChannel();
while (channel.write(gather)>0) {//用while是因为有可能一次性写不完。。
}
}
}
3.4 SocketChannel与ServerSocketChannel
ServerSocketChannel可以监听新进来的TCP连接通道
SocketChannel是一个连接TCP网络套拉字的通道
package com.xysf.channel;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
/*
* ServerSocketChannel
服务器端
* */
public class ServerSocketChannelTest09 {
public static void main(String[] args) throws IOException, InterruptedException {
int port = 1234;
// 建立一个未绑定ServcerSocket服务器的通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// serverSocketChannel没有绑定方法,需要先通过socket()方法获得ServerSocket对象
serverSocketChannel.socket().bind(new InetSocketAddress(port));
// 设置通道为非阻塞模式,当没有传入连接时,accept()方法返回null
serverSocketChannel.configureBlocking(false);
while (true) {
System.out.println("我是ServerSocket,已经准备好就等你来了");
SocketChannel sc = serverSocketChannel.accept();
if (sc == null) {
Thread.sleep(1000);
} else {
// 先给客户端发送一个问候
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
byteBuffer.put("hello!I am from ServerSocket.".getBytes());
byteBuffer.flip();
sc.write(byteBuffer);
// 在读取客户端中发送来的内容
System.out.println("from Socket Client:" + sc.socket().getRemoteSocketAddress());
byteBuffer.clear();
sc.read(byteBuffer);// 读取客户端发送的数据,保存到buffer中
byteBuffer.flip();
CharBuffer decode = Charset.defaultCharset().decode(byteBuffer);
System.out.println(decode);
sc.close();
}
}
}
}
package com.xysf.channel;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
//客户端
//SocketChannel
public class SocketChannelTest10 {
public static void main(String[] args) throws IOException {
String IP = "localhost";//Serversocket的IP地址
int port=1234;
InetSocketAddress inetSocketAddress = new InetSocketAddress(IP,port);
//创建一个未连接的SocketChannel()
SocketChannel sc = SocketChannel.open();
//建立服务器的连接
sc.connect(inetSocketAddress);
//TCP连接需要一定的时间,两个连接的建立需要进行包对话
//调用finishConnect()方法完成连接过程,如果没有连接成功返回false
while (!sc.finishConnect()) {
System.out.println("等待连接过程中......");
}
System.out.println("连接成功!!");
//向服务器发送消息
ByteBuffer buffer = ByteBuffer.wrap("Im from client socket!".getBytes());
while (buffer.hasRemaining()) {
sc.write(buffer);
}
//获得服务器给Client发送的消息
InputStream inputStream = sc.socket().getInputStream();
ReadableByteChannel newChannel = Channels.newChannel(inputStream);
buffer.clear();
newChannel.read(buffer);
buffer.flip();
CharBuffer decode = Charset.defaultCharset().decode(buffer);
System.out.println(decode);
}
}
3.5 DatagramChannel
DatagramChannel是对UDP无连接用户数据协议的通道
发送端
package com.xysf.channel;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.util.Scanner;
public class Test12DatagramChannelSender {
public static void main(String[] args) throws IOException {
// 创建未绑定的Channel
DatagramChannel dc = DatagramChannel.open();
dc.configureBlocking(false);
ByteBuffer buffer = ByteBuffer.allocate(1024);
Scanner scanner =new Scanner(System.in);
while (scanner.hasNext()) {
String inputString = scanner.next();
buffer.clear();
buffer.put(inputString.getBytes());
buffer.flip();//切换读模式
dc.send(buffer,new InetSocketAddress("localhost",8899));
//接收数据
buffer.clear();
SocketAddress receive = dc.receive(buffer);
while (receive==null) {
receive = dc.receive(buffer);
}
buffer.flip();
System.out.println(new String(buffer.array(),0,buffer.limit()));
}
}
}
接收端
package com.xysf.channel;
import java.io.IOException;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.util.Scanner;
public class Test11DataGramChannelReceiver {
public static void main(String[] args) throws IOException {
// 创建一个未绑定的通道
DatagramChannel dc = DatagramChannel.open();
dc.bind(new InetSocketAddress(8899));
dc.configureBlocking(false);
Scanner scanner = new Scanner(System.in);
ByteBuffer buffer = ByteBuffer.allocate(1024);
while (true) {
// 先接收数据
buffer.clear();
InetSocketAddress isa = (InetSocketAddress)dc.receive(buffer);
if (isa == null) {
continue;
}
System.out.print("data from:"+isa);
buffer.flip();
String msg = new String(buffer.array(),0,buffer.limit());
System.out.println(":"+isa.getPort()+"-->"+msg);
//发送数据
String next = scanner.next();
dc.send(ByteBuffer.wrap(next.getBytes()), isa);
}
}
}
3.6 Pipe
Pipe管道用于两个线程之间的数据连接
Pipe有一个Source通道和Sink通道
创建通道
Pipe pipe = new Pipe();
向管道中写数据,首先要访问Sink通道
Pipe.SinkChannel sc = new Pipe.SinkChannel();
sc.write(buffer);
读数据需要通过Source通道
Pipe.SourceChannel source = new Pipe.SourceChannel();
source.read(buffer);
package com.xysf.channel;
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
/*
* 演示在两个线程之间通过Pipe管道数据传输
* 使用PipedOutputStream和PipedInputStream两个管道输出流和管道输入流类
* 在管道通信时,线程A向PipeOutputStream中写入数据,这些数据会自动发送到对应的PipeInputStream
* */
public class Test14Pipe {
public static void main(String[] args) throws IOException {
// 输入流管道
PipedInputStream pipedInputStream = new PipedInputStream();
// 输出流管道
PipedOutputStream pipedOutputStream = new PipedOutputStream();
// 在输入流与输出流管道之间建立连接
pipedInputStream.connect(pipedOutputStream);
// 创建线程
Sender sender = new Sender(pipedOutputStream);
Receiver receiver = new Receiver(pipedInputStream);
new Thread(sender).start();
new Thread(receiver).start();
}
}
class Sender implements Runnable {
PipedOutputStream out;
public Sender() {
super();
}
public Sender(PipedOutputStream out) {
super();
this.out = out;
}
@Override
public void run() {
// 模拟发送数据
try {
for (int i = 1; i < 100; i++) {
String text = "hello,sender" + i + "\r\n";
out.write(text.getBytes());
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
try {
out.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
// 接受端
class Receiver implements Runnable {
PipedInputStream in;
public Receiver() {
super();
// TODO Auto-generated constructor stub
}
public Receiver(PipedInputStream in) {
super();
this.in = in;
}
@Override
public void run() {
try {
// 接收数据
byte[] bytes = new byte[1024];
int len = in.read(bytes);
while (len > 0) {
System.out.println(new String(bytes, 0, len));
len = in.read(bytes);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
try {
in.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
4. Slector选择器
4.1 选择器基础
选择器提供一种选择执行已经就绪的任务的能力
selector选择器允许单线程处理多个通道
如果程序打开多个连接通道,每个连接都流量都特别的底,可以使用selector对通道进行管理。
4.1.1 三个相关的类
Selector选择器管理着被注册的通道的集合的信息和他们的就绪状态
SelectableChannel可选择通道类,他是抽象类,是所有支持就绪检查
的通道类的父类
(注意:FileChannel不是SelectorChannel的子类,即FileChannel不是可选择的,可选择通道可以注册到多个选择器上,但是同一个选择器只能注册一次)
4.1.2 如何建立选择器
1)创建Selector
Selector selector = new Selector
2)必须将通道设置为非阻塞模式才能注册到选择器上
3)把注册器注册到选择器上,会返回一个选择键
SelectionKey key1 = selectableChannel.register
(selector,SelectionKey.OP_RRAD);
register()方法参数表示通道注册的选择器
第二个参数表示关心通道的那个操作
SelectKey key2 =selectableChannel2.register(selector,Selection.
OP_READ|SelectionKey.OP_WRITE);//使用位运算将你关心的操
作连接起来
SelectionKey的操作有:
SelectionKey.OP_CONNECT,指某个通道连接到服务器
SelectionKey.OP_ACCEPT,只有ServerSocketChannel有这个
事件,查看是否有新的连接
SelectionKey.OP_READ 是否有可读的通道就绪
SelectionKey.OP_WRITE,写数据的通道是否就绪
注册完成后,可以调用Select()方法轮询是否有就绪的通道
int readCount = selector.select();
select()方法,返回的是就绪通道的数量
4.2 选择键相关的方法
向Selector注册一个Channel通道时,就会返回一个SelectionKey选择键对象,这个选择键表示一个通道与一个选择器之间的注册关系
SelectionKey相关的方法:
channel()方法,返回该键对应的通道
selector()方法,返回通道注册的选择器
cancel()方法,终结这种特定的注册关系
isValid()方法,判断注册器关系是否有效
interestOps()方法,返回你关心的操作,是以整数的形式进行编码的比特掩码,可以使用位运算检查所关心的操作,如:
Boolean isAccept = interestops & SelectionKey.OP_ACCEPT == SelectionKey.OP_ACCEPT
Boolean isConnect = interestops &SelectionKey.OP_CONNECT=interestops & SelectionKey.OP_CONNECT
readyOps()方法,返回通道已就绪的操作,返回值也是一个整数,也可以使用上面相同的位操作检测通道有那个事件或操作已就绪,如:
selectionKey.readyOps() & SelectionKey.OP_WRITE !=0 说明操作已就绪
除了按位与操作之外,还可以使用isReadable(),isWritable(),isConnectable,
isAccetable()等方法检测这些比特值,上面一行检测write就绪的操作可以使用下面一行代码代替
if(selectionKey.isWritable){}
4.3 使用选择器
Selector选择器维护着注册过的通道集合,并且这些注册关系都封装在了SelectionKey对象中
每个Selector对象都需要维护以下三个集合:
已注册的键的集合,keys()方法返回这个已注册的键的集合,这个集合不能修改
已选择的键的集合,selectKeys()方法返回,该集合中每个成员都是相关的通道被选择器判断已准备好的,并且包含了键的interest集合中的操作,键可以从集合中移除,不能添加
已取消的建的集合,这个集合包含了调用过cancel()方法的键
开始刚刚初始化Selector对象,这三个集合都是空的。
Selector类的核心就是select()选择,该方法调用时执行一下步骤:
1)检查已取消键的集合,如果该集合非空,就把该集合的键从另外两个集合中移除,注销相关通道,这个步骤结束后,已取消的键的集合应该是空的
2)检查已注册键的集合中所有键的interest集合,确定每个通道所关心的操作是否已经就绪
3)Select()方法返回的是从上一次调用Select()方法后进入就绪状态的通道的数量
通常使用以下方法来管理这些键:
1)在选择器上调用select()方法
2)便利selectKeys()方法返回键的集合
检查每个键,查看相关通道的就绪信息,并进行处理
将键从已选择集合中移除
继续检查下一个键
4.4使用selector开发服务器端
package com.xysf.channel;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.Iterator;
//服务器
public class Test15Serve {
public static final int PORT = 8848;// 服务器端口号
private static ByteBuffer buffer = ByteBuffer.allocate(1024);
private static ServerSocketChannel ssc;
public static void main(String[] args) throws IOException {
// 打开一个未绑定的ServeSocketChannel
ssc = ServerSocketChannel.open();
// 获得serveSocketChannel关联的ServeSocket
ServerSocket serverSocket = ssc.socket();
// 绑定到指定端口
serverSocket.bind(new InetSocketAddress(PORT));
// 设置到ServerSocketChannel为非阻塞通道
ssc.configureBlocking(false);
// 创建选择器
Selector selector = Selector.open();
// 将ServerSocketChannel注册到选择器上
ssc.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
// 调用select()方法,会等待就绪的通道,会发生阻塞
int n = selector.select();// 返回就绪通道的数量
if (n == 0) {
continue; // 没有就绪的通道,什么也不做
}
// 获得选择键集合的迭代器
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
// 判断通道上是否有接受的连接
if (selectionKey.isAcceptable()) {
// 有新的连接,获得ServerSocketChannel
ServerSocketChannel sscTem = (ServerSocketChannel) selectionKey.channel();
// 调用accept方法返回到达服务器的i你客户端连接
SocketChannel socket = sscTem.accept();
socket.configureBlocking(false);
socket.register(selector, SelectionKey.OP_READ | selectionKey.OP_WRITE);
}
if (selectionKey.isReadable()) {
// 就绪通道有数据可读
readDataFromSocket(selectionKey);
}
if (selectionKey.isWritable()) {
//写数据
writeDateFrom(selectionKey);
}
}
}
}
private static void writeDateFrom(SelectionKey selectionKey) {
}
// 读取指定通道上的数据
private static void readDataFromSocket(SelectionKey selectionKey) throws IOException {
// 获得SocketChannel
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
ArrayList<Byte> list = new ArrayList<Byte>();//
buffer.clear();
int count = socketChannel.read(buffer);
while (count > 0) {
buffer.flip();// 切换为读模式
while (buffer.hasRemaining()) {
list.add(buffer.get());
}
buffer.clear();
count = socketChannel.read(buffer);
}
byte[] bytes = new byte[list.size()];
for (int i = 0; i < list.size(); i++) {
bytes[i]=list.get(i);
}
//生成字符串
String string = new String(bytes).trim();
System.out.println(string);
if (count<0) {
socketChannel.close();
}
}
}
4.5开发客户端
package com.xysf.channel;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Scanner;
//客户端
public class Test16Client {
public static void main(String[] args) throws IOException {
//打开通道
SocketChannel sc = SocketChannel.open();
//设置非阻塞
sc.configureBlocking(false);
//连接服务器
sc.connect(new InetSocketAddress(8848));
//调用finshConnect()方法完成连接
while (!sc.finishConnect()) {
System.out.println("正在建立连接,请稍等......");
}
System.out.println("连接成功");
Scanner scanner = new Scanner(System.in);
//向服务器发送数据
while (true) {
System.out.println("请输入要发送的内容");
String inputval = scanner.next();
ByteBuffer buffer = ByteBuffer.allocate(inputval.length());
buffer.put(inputval.getBytes());
buffer.flip();
while (buffer.hasRemaining()) {
sc.write(buffer);
}
buffer.clear();
}
}
}
4.5解决错误
客户端错误:
服务端错误:
原因:

浙公网安备 33010602011771号