详细知识参考我有道云笔记
代码语言:javascript复制package com.shi.nio;
import java.nio.ByteBuffer;
/**
*
* @author shiye
* 一、缓冲区(Buffer):在 Java NIO 中负责数据的存取。缓冲区就是数组。用于存储不同数据类型的数据
*
* 根据数据类型不同(boolean 除外),提供了相应类型的缓冲区:
* ByteBuffer
* CharBuffer
* ShortBuffer
* IntBuffer
* LongBuffer
* FloatBuffer
* DoubleBuffer
*
* ·上述缓冲区的管理方式几乎一致,通过 allocate() 获取缓冲区
*
* 二、缓冲区存取数据的两个核心方法:
* put() : 存入数据到缓冲区中
* get() : 获取缓冲区中的数据
*
* 三、缓冲区中的四个核心属性:
* capacity : 容量,表示缓冲区中最大存储数据的容量。一旦声明不能改变。
* limit : 界限,表示缓冲区中可以操作数据的大小。(limit 后数据不能进行读写)
* position : 位置,表示缓冲区中正在操作数据的位置。
*
* mark : 标记,表示记录当前 position 的位置。可以通过 reset() 恢复到 mark 的位置
*
* 0 <= mark <= position <= limit <= capacity
*
* 四、直接缓冲区与非直接缓冲区:
* 非直接缓冲区:通过 allocate() 方法分配缓冲区,将缓冲区建立在 JVM 的内存中
* 直接缓冲区:通过 allocateDirect() 方法分配直接缓冲区,将缓冲区建立在物理内存中。可以提高效率
*/
public class TestBuffer {
public static void main(String[] args) {
String str = "shiye";
//1 分配一个指定大小的缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
// ByteBuffer.allocateDirect(10);//直接内存缓冲区
// buffer.isDirect();//是否是直接内存缓冲区
System.out.println("-------------------1 allocate(初始创建)-----------");
System.out.println("容量:" buffer.capacity());
System.out.println("界限:" buffer.limit());
System.out.println("当前位置:" buffer.position());
buffer.put(str.getBytes());
System.out.println("-------------------2 put(写数据)-----------");
System.out.println("容量:" buffer.capacity());
System.out.println("界限:" buffer.limit());
System.out.println("当前位置:" buffer.position());
buffer.flip();
System.out.println("-------------------3 flip(切换到读取数据模式)-----------");
System.out.println("容量:" buffer.capacity());
System.out.println("界限:" buffer.limit());
System.out.println("当前位置:" buffer.position());
System.out.println("-------------------4 get(读取数据)-----------");
byte[] dst = new byte[10];
buffer.get(dst, 0, 2);//从下标0,读取2个数据 道dst中
System.out.println("dst中读到的数据位:" new String(dst));
System.out.println("容量:" buffer.capacity());
System.out.println("界限:" buffer.limit());
System.out.println("当前位置:" buffer.position());
buffer.rewind();
System.out.println("-------------------5 rewind(重新读数据)-----------");
System.out.println("容量:" buffer.capacity());
System.out.println("界限:" buffer.limit());
System.out.println("当前位置:" buffer.position());
System.out.println("buffer中读到的数据位:" (char)buffer.get());//rewind 测试
buffer.clear();
System.out.println("-------------------6 clear(清除位置信息,数据还存在)-----------");
System.out.println("容量:" buffer.capacity());
System.out.println("界限:" buffer.limit());
System.out.println("当前位置:" buffer.position());
System.out.println("buffer中读到的数据位:" (char)buffer.get());//clear 测试
System.out.println("-------------------7 mark(标记当前位置)-----------");
System.out.println("当前位置:" buffer.position());
buffer.mark();//mark 标记当前位置
System.out.println("buffer中读到的数据位:" (char)buffer.get());//mark 测试
System.out.println("当前位置:" buffer.position());
buffer.reset();//返回到mark标记好的位置 重新读取数据
System.out.println("reset当前位置:" buffer.position());//reset 测试
if(buffer.hasRemaining()) {
System.out.println("当前缓冲区中剩余可以操作的的数据的数量" buffer.remaining());
}
}
/**
运行结果:
-------------------1 allocate(初始创建)-----------
容量:1024
界限:1024
当前位置:0
-------------------2 put(写数据)-----------
容量:1024
界限:1024
当前位置:5
-------------------3 flip(切换到读取数据模式)-----------
容量:1024
界限:5
当前位置:0
-------------------4 get(读取数据)-----------
dst中读到的数据位:sh
容量:1024
界限:5
当前位置:2
-------------------5 rewind(重新读数据)-----------
容量:1024
界限:5
当前位置:0
buffer中读到的数据位:s
-------------------6 clear(清除位置信息,数据还存在)-----------
容量:1024
界限:1024
当前位置:0
buffer中读到的数据位:s
-------------------7 mark(标记当前位置)-----------
当前位置:1
buffer中读到的数据位:h
当前位置:2
reset当前位置:1
当前缓冲区中剩余可以操作的的数据的数量1023
*/
}
代码语言:javascript复制package com.shi.nio;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.LinkedList;
import java.util.List;
/**
*
* @author shiye 一、通道(Channel):用于源节点与目标节点的连接。在 Java NIO 中负责缓冲区中数据的传输。Channel
* 本身不存储数据,因此需要配合缓冲区进行传输。
*
* 二、通道的主要实现类 java.nio.channels.Channel 接口: |--FileChannel
* |--SocketChannel |--ServerSocketChannel |--DatagramChannel
*
* 三、获取通道 1. Java 针对支持通道的类提供了 getChannel() 方法 本地 IO:
* FileInputStream/FileOutputStream RandomAccessFile
*
* 网络IO: Socket ServerSocket DatagramSocket
*
* 2. 在 JDK 1.7 中的 NIO.2 针对各个通道提供了静态方法 open() 3. 在 JDK 1.7 中的 NIO.2 的
* Files 工具类的 newByteChannel()
*
* 四、通道之间的数据传输 transferFrom() transferTo()
*
* 五、分散(Scatter)与聚集(Gather) 分散读取(Scattering Reads):将通道中的数据分散到多个缓冲区中
* 聚集写入(Gathering Writes):将多个缓冲区中的数据聚集到通道中
*
* 六、字符集:Charset 编码:字符串 -> 字节数组 解码:字节数组 -> 字符串
*/
public class TestChannel {
/**
*
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
test4();
}
/**
* 编码和解码
* @throws CharacterCodingException
*/
public static void test4() throws CharacterCodingException {
//获取UTF-8的字符集
Charset charset = Charset.forName("UTF-8");
//获取编码器
CharsetEncoder utf8_encoder = charset.newEncoder();
//获取解码器
CharsetDecoder utf8_decoder = charset.newDecoder();
CharBuffer charBuffer = CharBuffer.allocate(1024);
charBuffer.put("还是施爷足智多谋!");
charBuffer.flip();
//1 编码 CharBuffer -> ByteBuffer 字符流->字节流
ByteBuffer byteBuffer = utf8_encoder.encode(charBuffer);
for(int i = 0;i<byteBuffer.limit();i ) {
System.out.println(byteBuffer.get());
}
//2 解码 ByteBuffer-> CharBuffer 字节流->字符流
byteBuffer.flip();
CharBuffer charBuffer2 = utf8_decoder.decode(byteBuffer);
System.out.println(charBuffer2.toString());
//所有支持的字符集
// SortedMap<String, Charset> charsets = Charset.availableCharsets();
// charsets.forEach((key,value)->{
// System.out.println( key " " value);
// });
}
/**
* 分散(Scatter)与聚集(Gather) 分散读取(Scattering Reads):将通道中的数据分散到多个缓冲区中 聚集写入(Gathering
* Writes):将多个缓冲区中的数据聚集到通道中
*
* @throws IOException
*
* 注意文件不能超过内存的大小
* Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.nio.HeapByteBuffer.<init>(Unknown Source)
*
* 结果:
* 总耗时:7065
*/
public static void test3() throws IOException {
long time1 = System.currentTimeMillis();
// 1 读取文件 获取通道
RandomAccessFile raf1 = new RandomAccessFile("d:/nio.zip", "rw");
FileChannel inChannel = raf1.getChannel();
// 2 计算总共需要多少个缓冲区
int size = 1024 * 1024 * 100;// 100M大小
int index = (int) ((raf1.length() - 1) / size 1);// 文件超出 Integer.MAX_VALUE 需要分开缓存
List<ByteBuffer> bufs = new LinkedList<ByteBuffer>();
for (int i = 0; i < index; i ) {
if (i != index - 1) {
ByteBuffer team = ByteBuffer.allocate(size);
bufs.add(team);
} else {
// System.out.println((raf1.length() - (size * (index - 1))) / 1024 / 1024 "M");
ByteBuffer team = ByteBuffer.allocate((int) (raf1.length() - (size * (index - 1))));
bufs.add(team);
}
}
//3 分散读取
ByteBuffer[] byteArray = bufs.toArray(new ByteBuffer[index]);
inChannel.read(byteArray);
for(ByteBuffer byteBuffer:byteArray) {
byteBuffer.flip();//变成读模式
}
//4 聚焦写入
RandomAccessFile raf2 = new RandomAccessFile("d:/test.zip","rw");
FileChannel outChanel = raf2.getChannel();
outChanel.write(byteArray);
//5 关闭文件
inChannel.close();
outChanel.close();
long time2 = System.currentTimeMillis();
System.out.println("总耗时:" (time2 - time1));
}
/**
* transferFrom() transferTo()
*
* @throws IOException 注意文件只能有1.98G 太大文件会丢失 结果: 总耗时:2349
*/
public static void test2() throws IOException {
long time1 = System.currentTimeMillis();
FileChannel inChannel = FileChannel.open(Paths.get("d:/" "big.zip"), StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(Paths.get("d:/" "2.zip"), StandardOpenOption.READ,
StandardOpenOption.WRITE, StandardOpenOption.CREATE);
inChannel.transferTo(0, inChannel.size(), outChannel);
long time2 = System.currentTimeMillis();
System.out.println("总耗时:" (time2 - time1));
inChannel.close();
outChannel.close();
}
/**
* 使用直接缓冲区完成文件的复制(内存映射文件)
*
* @throws IOException
*
* 结果: 读取文件耗时:1264 写文件耗时:14845
*/
public static void test1() throws IOException {
long time1 = System.currentTimeMillis();
// 1 读文件内存
FileChannel inChannel = FileChannel.open(Paths.get("d:/" "nio.zip"), StandardOpenOption.READ);
MappedByteBuffer inMappedBuffer = inChannel.map(MapMode.READ_ONLY, 0, inChannel.size());// inChannel.size()
// 不能大于Integer.MAX_VALUE
byte[] dst = new byte[inMappedBuffer.limit()];
inMappedBuffer.get(dst);
long time2 = System.currentTimeMillis();
System.out.println("读取文件耗时:" (time2 - time1));
// 2 写文件到磁盘
FileChannel outChannel = FileChannel.open(Paths.get("d:/" "2.zip"), StandardOpenOption.READ,
StandardOpenOption.WRITE, StandardOpenOption.CREATE);
MappedByteBuffer outMappedBuffer = outChannel.map(MapMode.READ_WRITE, 0, inChannel.size());
outMappedBuffer.put(dst);// 直接对缓冲区进行数据的读写
long time3 = System.currentTimeMillis();
System.out.println("写文件耗时:" (time3 - time2));
inChannel.close();
outChannel.close();
}
}
代码语言:javascript复制package com.shi.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
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.time.LocalDateTime;
import java.util.Scanner;
import java.util.Set;
import org.junit.Test;
/*
* 一。 使用NIO完成网络通讯的三个核心
* 1.通道 (Channel):负责连接
* java.nio.channels.Channel接口
* |--SocketChannel
* |--ServerSocketChannel
* |--DatagramChannel
*
* |--Pipe.SinkChannel
* |--Pipe.SourceChannel
*
* 2.缓冲区(buffer):负责数据的存储
*
* 3.选择器(Selector):是SelectableChannel 的多路复用器.用于监控SeclectableChannel 的IO状况
*
*
*/
public class TestNonBlockingNIO {
//客户端
@Test
public void client() throws IOException {
//1.获取通道
SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",9898));
//2.切换非阻塞模式
sChannel.configureBlocking(false);
//3.分配指定大小的缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate(1*1024);
//4.发送给服务器端数据
Scanner scan = new Scanner(System.in);
while(scan.hasNext()) {
byteBuffer.put((LocalDateTime.now().toString() "t" scan.next()).getBytes());
byteBuffer.flip();//切换到读模式
sChannel.write(byteBuffer);
byteBuffer.clear();
}
//5.关闭通道
sChannel.close();
}
//服务器端
@Test
public void server() throws Exception {
//1.获取通道
ServerSocketChannel ssChannel = ServerSocketChannel.open();
//2.切换非阻塞模式
ssChannel.configureBlocking(false);
//3.绑定连接
ssChannel.bind(new InetSocketAddress(9898));
//4.获取选择器
Selector selector = Selector.open();
//5.将通道注册到选择器上,并且指定“监听接收事件”
ssChannel.register(selector, SelectionKey.OP_ACCEPT);
//6.轮询式的获取选择器上已经“准备就绪”的事件
while(selector.select()>0) {
//7.获取当前选择器中所有注册的“选择键(已经就绪的监听事件)”
Set<SelectionKey> selectedKeys = selector.selectedKeys();
//8.获取准备“就绪”的事件
selectedKeys.forEach(selectionKey->{
if(selectionKey.isAcceptable()) {
try {
//9."接收就绪"状态事件
SocketChannel sChannel = ssChannel.accept();
//10.切换非阻塞模式
sChannel.configureBlocking(false);
//11.将通道注册到选择器上
sChannel.register(selector, SelectionKey.OP_READ);
} catch (IOException e) {
e.printStackTrace();
}
}else if(selectionKey.isReadable()) {
//获取选择器上“读就绪”状态的通道
SocketChannel sChannel = (SocketChannel) selectionKey.channel();
//读取数据
ByteBuffer buf = ByteBuffer.allocate(1*1024);
int len = 0;
try {
while((len = sChannel.read(buf)) > 0) {
buf.flip();//变成只读模式
System.out.println(new String(buf.array(),0,len));
buf.clear();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
sChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}else if(selectionKey.isConnectable()) {
System.out.println("连接状态的通道自己处理....");
}else if(selectionKey.isValid()) {
System.out.println("验证状态的通道自己处理....");
}else if(selectionKey.isWritable()) {
System.out.println("写状态的通道自己处理....");
}
});
ssChannel.close();
}
}
}