java NIO Channel详解

一、通道基础

1、通道channel是一种途径,用于以最小的开销来访问计算机(也是通道底层)的io服务。缓冲区则是对数据进行处理的终点

(1)、io分为文件io和流io,同样channel分为文件channel和socket channel.

(2)、FileChannel 文件通道

DatagramChannel 数据报通道,用于UDP协议。

SocketChannel 客户端通道

ServerSocketChannel 服务端通道

(3)、通道打开方式

java NIO Channel详解_第1张图片

(4)、与缓冲区不同,通道不能重复使用。一旦通道关闭,通道底层的io服务。调用通道的close()方法,可能导致通道底层的io线程阻塞,哪怕通道设置成飞阻塞状态。因此使用时可以调用isOpen()方法进行判断。

二、FileChannel

1、发散聚集

文件发散读取到多个Buffer中,多个buffer的文件聚集到同一channel中。channel无论读取或是写入都是从position这个位置开始的,其初始值为0.

file1.txt内容为:

hello wolrd
这是演示文本!!!

package testFileChannel;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class TestFileChannel {

	public static void main(String[] args) throws Exception {
		String fileName = "file1.txt";// 和src在同层目录
		RandomAccessFile file = new RandomAccessFile(fileName, "rw");
		testScatter(file);// channel内容发散到多个buffer输出
		testGather(file);// 多个buffer的内容具体到同一个通道,读取通道并输出
	}

	public static void testGather(RandomAccessFile file) throws IOException {
		FileChannel channel = file.getChannel();
		ByteBuffer buffer1 = ByteBuffer.allocate(5);//初始容量为5个字节
		ByteBuffer buffer2 = ByteBuffer.allocate(48);//初始容量为48个字节
		channel.read(new ByteBuffer[]{buffer1,buffer2});//按顺序填充buffer
		
		buffer1.flip();
		buffer2.flip();//获取有效数据长度
		
		while(buffer1.hasRemaining()) {
			System.out.print((char)buffer1.get());//hello,刚好五个字节
		}
		System.out.println("\n");
		byte[] b = buffer2.array();
		System.out.println(new String(b));
		// wolrd
		//这是演示文本!!!
	}

	public static void testScatter(RandomAccessFile file) throws Exception {
		FileChannel channel = file.getChannel();//获取通道
		channel.position(file.length());//将channel的position调到文件内容的末尾位置,准备追加buffer的数据
		ByteBuffer buffer1 = ByteBuffer.allocate(5);//初始容量为5个字节
		ByteBuffer buffer2 = ByteBuffer.allocate(48);//初始容量为48个字节
		
		buffer1.put("\nhehe".getBytes());//换行放入hehe四个字节
		buffer2.put("\nnihao".getBytes());//换行放入nihao五个字节
		buffer1.flip();
		buffer2.flip();
		
		while(buffer1.hasRemaining() || buffer2.hasRemaining()) {
			channel.write(new ByteBuffer[]{buffer1,buffer2});
		}
		channel.force(true);//强制重新写入文件,file1.txt内容变为下面
		//hello wolrd
		//这是演示文本!!!
		//hehe
		//nihao
	}
}

2、文件空洞

指通道写入数据时position数值很大,而position前面并没有内容,大多数是以0来占据。对磁盘空间消耗非常大,并且会降低我们的扫描速度,大大增加内存的消耗。

package testFileChannel;

import java.io.File;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class TestFileHole {

	public static void main(String[] args) throws Exception {

		File file = new File("file3.txt");
		if(!file.exists()) {
			file.createNewFile();
		}
		RandomAccessFile rf = new RandomAccessFile(file,"rw");
		FileChannel channel = rf.getChannel();//获取通道后,然后插入数据。根据字节数来判断无用或占用磁盘空间的部分
		ByteBuffer buffer = ByteBuffer.allocate(100);
		
		putData(0,buffer,channel);
		putData(5000000,buffer,channel);
		putData(500,buffer,channel); //多次放入数据,但是最大position还是从5000000开始
		
		System.out.println(channel.size());//5000018,前面不存在内容的部分就是文件空洞
	}

	public static void putData(int position, ByteBuffer buffer, FileChannel channel) throws Exception {
		
		String str = "这是我们放入的数据";//gbk编码,此时每个占2个字节
		buffer.clear();//每次调用让buffer处于写就绪状态
		buffer.put(str.getBytes());//2*9=18字节
		buffer.flip();
		channel.position(position);
		
		while(buffer.hasRemaining()) {
			channel.write(buffer);
		}
	}
}

三、socket通道,线程安全的

1、SocketChannel

(1)、常见方法

public abstract class SocketChannel extends AbstractSelectableChannel 
			    implements ByteChannel, ScatteringByteChannel, GatheringByteChannel 
{ // This is a partial API listing 
	public static SocketChannel open( ) throws IOException;
	public static SocketChannel open (InetSocketAddress remote) throws IOException;
	public abstract Socket socket( ); 
	public abstract boolean connect (SocketAddress remote) throws IOException;
	public abstract boolean isConnectionPending( );
	public abstract boolean finishConnect( ) throws IOException;
	public abstract boolean isConnected( ); 
	public final int validOps( );
}

(2)、客户端代码

package testSocket;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

public class ClientSocket {

	public static void main(String[] args) {
		SocketChannel client = null;
		ByteBuffer buffer = ByteBuffer.allocate(100);
		try {
			client = SocketChannel.open();
			client.connect(new InetSocketAddress("localhost",30));//绑定主机名和端口或者ip地址或者端口
			client.configureBlocking(false);//非阻塞状态,多个客户端可以被一个服务端管理
			if(client.isConnected()) { //连接成功时,doSomething
				System.out.println("已连接到服务端");
				buffer.put("您好!".getBytes()).flip();
				while(buffer.hasRemaining()) {
					client.write(buffer);
				}
			}
		} catch (Exception e) {
			System.out.println(e.getMessage());
		} finally {
			try {
				client.close();
			} catch (IOException e) {
				System.out.println(e.getMessage());
			}
		}
	}
}

2、ServerSocketChannel

服务端代码

package testSocket;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;

public class ServerSocket {

	public static void main(String[] args) throws Exception{

		ServerSocketChannel server = ServerSocketChannel.open();
		server.bind(new InetSocketAddress(30));
		SocketChannel accept = server.accept(); //获取客户端,读取客户端通道里面输入的内容
		ByteBuffer buffer = ByteBuffer.allocate(100);
		accept.read(buffer);
		buffer.flip();
		byte[] array = buffer.array();
		System.out.println(new String(array));//您好!
	}
}

3、DatagramChannel

无连接,connect只是指明发送数据包的地址。

正如SocketChannel模拟连接导向的流协议(如TCP/IP),DatagramChannel则模拟包导向的无连接协议(如UDP/IP)

DatagramChannel数据报的socket通道和其他socket通道创建一致,该通道以固定大小的数据包的形式send()发送数据或者receive()接受数据。Datagram既可作为服务端(监听者)也可以作为客户端(发送者)。

下面列出了一些选择数据报socket而非流socket的理由:


 您的程序可以承受数据丢失或无序的数据。
 您希望“发射后不管”(fire and forget)而不需要知道您发送的包是否已接收。
 数据吞吐量比可靠性更重要。
 您需要同时发送数据给多个接受者(多播或者广播)。
 包隐喻比流隐喻更适合手边的任务。






你可能感兴趣的:(java,NIO(中文版),java,nio,socket)