目录
3.FileChannel提供了map方法把文件映射为内存映像文件
4.MappedByteBuffer是ByteBuffer的子类,扩充了三个方法:
一. 内存映射文件
1. MappedByteBuffer
java处理大文件,一般用BufferedReader,BufferedInputStream这类带缓冲的IO类,不过,如果文件超大的话,更快的方式是MappedByteBuffer。
MappedByteBuffer是NIO引入的文件内存映射方案,读写性能极高。
MappedByteBuffer将文件直接映射到虚拟内存,如果文件比较大的话,可以分段映射。
2.MappedByteBuffer原理
3.FileChannel提供了map方法把文件映射为内存映像文件
MappedByteBuffer map(int mode,long position,long size);
mode指出了可访问该内存映像文件的方式:
READ_ONLY,(只读)
READ_WRITE,(读/写)
PRIVATE(私用)
4.MappedByteBuffer是ByteBuffer的子类,扩充了三个方法:
1.force() :缓冲区是READ_WRITE模式下,此方法对缓冲区内容的修改强行写入文件;
2.load() :将缓冲区的内容载入内存,并返回该缓冲区的引用;
3.isLoaded() :如果缓冲区的内容在物理内存中,则返回真,否则返回假;
5.MappedByteBuffer 拷贝文件
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
public class MappedByteBufferTest {
// MappedByteBuffer 拷贝文件
public static void MappedByteBufferCopy(String path1 , String path2) throws IOException {
long startTime = System.currentTimeMillis();
RandomAccessFile inFile = new RandomAccessFile(path1,"r");
RandomAccessFile outFile = new RandomAccessFile(path2,"rw");
FileChannel inFileChannel = inFile.getChannel();
FileChannel outFileChannel = outFile.getChannel();
//获取文件大小
long size = inFile.length();
/*MappedByteBuffer mbbi = inFileChannel.map(FileChannel.MapMode.READ_ONLY,0,size);
MappedByteBuffer mbbo = outFileChannel.map(FileChannel.MapMode.READ_WRITE,0,size);
for (int i = 0 ;i<size;i++) {
byte b = mbbi.get();
mbbo.put(b);
}*/
//将文件分成几块进行映射
int blockCount = (int)size/(1024*1024*2);
//每一块的大小
int block = (int) size/blockCount;
//分段映射
for (int i =0;i<blockCount;i++){
//MappedByteBuffer进行内存映射
MappedByteBuffer mbbi = inFileChannel.map(FileChannel.MapMode.READ_ONLY,i*block,block);
MappedByteBuffer mbbo = outFileChannel.map(FileChannel.MapMode.READ_WRITE,i*block,block);
//进行拷贝
byte [] dst = new byte[block];
mbbi.get(dst);
mbbo.put(dst);
}
long endTime = System.currentTimeMillis();
System.out.println("MappedByteBuffer copy:" + (endTime-startTime) + "ms");
}
public static void main(String[] args) throws IOException {
String path1 = "D:/Redis-x64-5.0.10.zip";
String path2 = "D:/Redis-x64-5.0.10111.zip";
MappedByteBufferCopy(path1,path2);
}
}
6.拷贝比较
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class copycompare {
//普通io拷贝
public static void ioCopy(String path1,String path2) throws IOException {
long startTime = System.currentTimeMillis();
RandomAccessFile inputStream = new RandomAccessFile(path1,"r");
RandomAccessFile outputStream = new RandomAccessFile(path2,"rw");
int n = 0;
byte[] bs = new byte[1024*1024*1];
while ((n = inputStream.read(bs))!=-1) {
outputStream.write(bs,0,n);
}
//关流
outputStream.close();
inputStream.close();
long endTime = System.currentTimeMillis();
System.out.println("普通io:" + (endTime-startTime) + "ms");
}
//ByteBuffer 拷贝
public static void bytebufferCopy(String path1,String path2) throws IOException {
long startTime = System.currentTimeMillis();
RandomAccessFile inputStream = new RandomAccessFile(path1,"r");
RandomAccessFile outputStream = new RandomAccessFile(path2,"rw");
FileChannel inChannel = inputStream.getChannel();
FileChannel outChannel = outputStream.getChannel();
ByteBuffer buf = ByteBuffer.allocate(1024*1024*1);
while (inChannel.read(buf)!=-1){
buf.flip();
while (buf.hasRemaining()){
outChannel.write(buf);
}
buf.clear();
}
inputStream.close();
outChannel.close();
outputStream.close();
inChannel.close();
long endTime = System.currentTimeMillis();
System.out.println("ByteBuffer:" + (endTime-startTime) + "ms");
}
public static void main(String[] args) throws IOException {
String path1 = "D:/Redis-x64-5.0.10.zip";
String path2 = "D:/Redis-x64-5.0.102222.zip";
ioCopy(path1,path2);
bytebufferCopy(path1,path2);
}
}
二.直接缓冲区与非直接缓冲区
1.ByteBuffer和CharBuffer之间的转换:
计算机底层只能存储二进制字节码,因此将字符存储到节点时要对字符进行编码(字符转换成二进制字节码),而从节点取出字符显示时要进行解码(将二进制字节码转换成字符)。
由于Channel只能直接操作ByteBuffer,而处理字符时就需要在CharBuffer和ByteBuffer之间进行转换,刚好Charset提供了这样的功能。
Charset.defaultCharset(); //这个很关键,也很常用,可以轻松编写平台无关代码。
2.创建方式:
//非直接缓冲区
static ByteBuffer allocate(int capacity)
//直接缓冲区
static ByteBuffer allocateDirect(int capacity)
3.两者区别
直接缓冲区:内核地址空间和用户地址空间之间形成了一个物理内存映射文件,减少了之间的copy过程。
三.分散读取与聚集写入
Scatter 分散
Scattering Reads是指数据从一个channel读取到多个buffer中
ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body = ByteBuffer.allocate(1024);
ByteBuffer[] bufferArray = { header, body };
channel.read(bufferArray);
注意: Scattering Reads在移动下一个Buffer前,必须填满当前的Buffer,这也意味着它不适用于动态消息
FileInputStream fis = new FileInputStream(new File("out.txt"));
FileChannel inChannel = fis.getChannel();
ByteBuffer buf1 = ByteBuffer.allocate(8);
ByteBuffer buf2 = ByteBuffer.allocate(8);
ByteBuffer buf3 = ByteBuffer.allocate(4);
ByteBuffer buf4 = ByteBuffer.allocate(20);
ByteBuffer[] bufferArray = {buf1,buf2,buf3,buf4};
inChannel.read(bufferArray);
System.out.println(inChannel.size());
System.out.println((char)buf3.get(3));
Gather 聚集
Gathering Writes是指数据从多个buffer写入到同一个channel
ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body = ByteBuffer.allocate(1024);
ByteBuffer[] bufferArray = {header,body};
channel.write(bufferArray);
注意: 只有position和limit之间的数据才会被写入。因此与Scattering Reads相反,Gathering Writes能较好地处理动态信息
四.JDK1.7的NIO改进
Path:一个接口,表示文件路径
Paths:有一个静态方法,返回路径(path)
public static Path get(URI uri)
public static Path get(String first,String ... more)
Files:提供一组静态方法,对文件进行操作
public static long copy(Path source, OutputStream out)
public static Path write(Path path, Iterable<? extends CharSequence> lines,
OpenOption...options)
1. 拷贝文件以及将数组内容拷贝到文本中
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
public class Nio1_7 {
public static void main(String[] args) throws IOException {
//JDK1.7对NIO的改进
//1.Path 路径对象
//2.paths 可以获取Path对象
//3.Files 提供很多文件的读写操作
long startTime = System.currentTimeMillis();
//copy方法
//Path path = Paths.get("D:/Redis-x64-5.0.10.zip");
//Files.copy(path,new FileOutputStream("D:/Redis-x64-5.0.10111.zip"));
//write方法
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("e");
//Path path = Paths.get("D:/","nio.txt");
Path path = Paths.get("D:/nio.txt");
//Iterable 可迭代的
//public static Path write(Path path, Iterable<? extends CharSequence> lines,
// OpenOption...options)
Files.write(path,list);
long endTime = System.currentTimeMillis();
System.out.println("拷贝时间 : " + (endTime-startTime) + "ms");
}
}
2.通过URI进行文件的拷贝
//通过URI进行文件的拷贝
URI uri = new URI("file:///D:/nio.txt");
Path path = Paths.get(uri);
Files.copy(path,new FileOutputStream("D:/nio111.txt"));
五.NIO的阻塞与非阻塞模式
1.NIO的阻塞模式
FileChannel只能是阻塞模式,不能切换到非阻塞模式
网络NIO的阻塞模式应用案例:
5.1.1 客户端发送图片到服务端
客户端:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.SocketChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.concurrent.TimeUnit;
public class BlockClient {
//NIO阻塞模式 TCP协议
public static void main(String[] args) throws IOException, InterruptedException {
//1.获取通道
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",8888));
//2.分配指定大小的缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
//3.从本地读取文件,并发送到服务端
//Path接口,表示文件路径
//Paths类 get方法返回Path类型的路径
FileChannel inFileChannel = FileChannel.open(Paths.get("D:/mm.jpg"), StandardOpenOption.READ);
while (inFileChannel.read(buf)!=-1){
buf.flip();
while (buf.hasRemaining()){
socketChannel.write(buf);
}
buf.clear();
}
System.out.println("客户端发送数据完成!");
// 提示服务端,客户端已经发送数据完成,否则服务端一直会阻塞
//告诉服务端发送数据完成
socketChannel.shutdownOutput();
//休眠一段时间
TimeUnit.MICROSECONDS.sleep(1000);
//TimeUnit.HOURS.sleep(2); //休眠2小时
//TimeUnit.MINUTES.sleep(10); //休眠10分钟
// 接收服务端反馈
int len = 0;
while((len = socketChannel.read(buf)) !=-1) {
buf.flip();
System.out.println(new String(buf.array(),0,len));
}
//4. 关闭通道
inFileChannel.close();
socketChannel.close();
}
}
服务端:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
public class BlockServer {
// NIO的TCP服务端,阻塞模式
public static void main(String[] args) throws IOException {
// 1. 获取端口
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//2. 绑定链接
serverSocketChannel.bind(new InetSocketAddress(8888));
// 3. 获取客户端连接的通道
System.out.println("服务端等待客户端连接");
// socketChannel 服务端接收到客户端的socket
SocketChannel socketChannel = serverSocketChannel.accept();// 阻塞线程
System.out.println("服务端和客户端已经连接");
//4. 接收客户端的数据,保存到本地
FileChannel outFileChannel = FileChannel.open
(Paths.get("D:/nn.jpg"), StandardOpenOption.WRITE,StandardOpenOption.CREATE);
ByteBuffer buf = ByteBuffer.allocate(1024);
while(socketChannel.read(buf)!= -1){
buf.flip();
outFileChannel.write(buf);
buf.clear();
}
System.out.println("服务端将接收的数据存入本地硬盘");
//发送反馈给客户端
buf.put("服务端接收数据成功!".getBytes());
buf.flip();
socketChannel.write(buf);//服务端数据会原路返回到客户端
socketChannel.close();
serverSocketChannel.close();
outFileChannel.close();
}
}
2. NIO的非阻塞模式
当线程从某通道进行读写数据时,若没有数据可用时,该线程可以进行其他任务。线程通常将非阻塞IO的空闲时间用于在其他通道上执行IO操作,所以单独的线程可以管理多个输入和输出通道。
NIO通过Selector来管理多个通道:
5.2.1 Selector的创建
Selector selector = Selector.open();
5.2.2 向Selector注册通道
channel.configureBlocking(false);
SelectionKey key = channel.register(selector,Selectionkey.OP_READ);
Channel必须处于非阻塞模式下,才能与Selector一起使用,这意味着不能将FileChannel与Selector一起使用,因为FileChannel不能切换到非阻塞模式,而套接字通道都可以。
5.2.3 Selector监听Channel的四种事件
SelectionKey.OP_READ =1 << 0 1读就绪
SelectionKey.OP_WRITE =1 << 2 4写就绪
SelectionKey.OP_CONNECT =1 << 3 8连接就绪
SelectionKey.OP_ACCEPT =1 << 4 16接收就绪
//事件顺序:connect accept read write
如果你对不止一种事件感兴趣,那么可以用“位或”操作符将常量连接起来,如下:
int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
5.2.4 SelectionKey
当向Selector注册Channel时,register()方法会返回一个SelectionKey对象。这个对象包含了一些属性:interest集合,ready集合,Channel,Selector,附加的对象(可选)。
5.2.5 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.util.Iterator;
public class TCPSelectorServer {
private static final int BUF_SIZE = 1024;
private static final int PORT = 9999;
private static final int TIMEOUT = 3000;
// 网络NIO的非阻塞模式
// 服务器只利用一个线程,通过选择器来维护多个客户端的请求
public static void main(String[] args) {
selector();
}
public static void handleAccept(SelectionKey key) throws IOException {
System.out.println("******handleAccept******");
ServerSocketChannel ssChannel = (ServerSocketChannel)key.channel();
SocketChannel sc = ssChannel.accept();
// 将客户端的socket设置为非阻塞模式
sc.configureBlocking(false);
// 传递到下一步,read
sc.register(key.selector(),SelectionKey.OP_READ, ByteBuffer.allocateDirect(BUF_SIZE));
}
/**
* @Description 服务端获取客户端信息
* @param key
* return void
*/
public static void handleRead(SelectionKey key) throws IOException {
System.out.println("******handleRead******");
SocketChannel sc = (SocketChannel)key.channel();
ByteBuffer buf = (ByteBuffer) key.attachment();
long bytesRead = sc.read(buf);
while(bytesRead>0){
buf.flip();
//循环接收客户端发送过来的信息
while(buf.hasRemaining()){
System.out.print((char) buf.get());
}
System.out.println();
buf.clear();
bytesRead = sc.read(buf);
}
if (bytesRead == -1){
sc.close();
}
}
/**
* @Description 服务端反馈信息到客户端
* @param key
* return void
*/
public static void handleWrite(SelectionKey key) throws IOException {
System.out.println("******handleWrite******");
ByteBuffer buf = (ByteBuffer) key.attachment(); //attachment 检测当前附件并返回
buf.flip();
SocketChannel sc = (SocketChannel) key.channel(); // 连接客户端的通道
while (buf.hasRemaining()){
sc.write(buf);
}
buf.compact();
sc.close();
}
public static void selector(){
Selector selector = null;
ServerSocketChannel ssc = null;
try {
selector = Selector.open(); //打开选择器
ssc = ServerSocketChannel.open(); // 服务端Socket
ssc.socket().bind(new InetSocketAddress(PORT));
//将服务端的 Socket设置成非阻塞模式
ssc.configureBlocking(false);
//SelectionKey.OP_READ =1 << 0 1读就绪
//SelectionKey.OP_WRITE =1 << 2 4写就绪
//SelectionKey.OP_CONNECT =1 << 3 8连接就绪
//SelectionKey.OP_ACCEPT =1 << 4 16接收就绪
//事件顺序:connect ac register(selector,SelectionKey.OP_ACCEPT);
while (true) {
//select() 如果没有参数 服务端一直等待客户端发送请求
if (selector.select(TIMEOUT) == 0){
System.out.println("==");
continue;
}
//走到这,说明有客户端发送请求
//SelectionKey存了很多信息
// interest ready channel selector
// selector.selectedKeys() 客户端事件集合
Iterator <SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()){
SelectionKey key = iterator.next();
if (key.isAcceptable()){// 判断服务端是否为可接收状态
handleAccept(key);// 服务端准备接收客户端的请求信息
}
if (key.isReadable()){// 判断服务端是否为可读状态
handleRead(key);// 服务端读取客户端发过来的消息
}
if (key.isWritable() && key.isValid()){
handleWrite(key);
}
if (key.isConnectable()){
System.out.println("isConnectable = true");
}
iterator.remove();
}
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
if (selector!= null){
selector.close();
}
if (ssc != null){
ssc.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
客户端:
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.concurrent.TimeUnit;
public class TCPClient {
// NIO非阻塞模式,TCP客户端
public static void client(){
ByteBuffer buffer = ByteBuffer.allocate(1024);
SocketChannel socketChannel = null;
try
{
socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false); // 设置为非阻塞模式
socketChannel.connect(new InetSocketAddress("127.0.0.1",9999));
TimeUnit.SECONDS.sleep(1);
if(socketChannel.finishConnect())
{
int i=0;
while(true)
{
TimeUnit.SECONDS.sleep(3);
String localIP = InetAddress.getLocalHost().getHostAddress();
String info = localIP + " -- I'm " + ++i + "-th information from client";
buffer.clear();
buffer.put(info.getBytes());
buffer.flip();
while(buffer.hasRemaining()){
System.out.println(buffer);
socketChannel.write(buffer); // 发送数据到服务端
}
}
}
}
catch (IOException | InterruptedException e)
{
e.printStackTrace();
}
finally{
try{
if(socketChannel!=null){
socketChannel.close();
}
}catch(IOException e){
e.printStackTrace();
}
}
}
public static void main(String[] args) throws UnknownHostException {
TCPClient.client();
}
}