1. Channel概述
1).java.nio.channels.Channel(接口):Channel表示一个“通道”,它内部封装了IO流。从NIO开始,不用直接使用IO流,改用Channel通道 + Buffer来操作读写。它比IO流更加强大。
2).常用的几个通道:
1).FileChannel:文件通道,操作文件。
2).DatagramChannel:UDP协议的通道。
3).SocketChannel和ServerSocketChannel:TCP协议的通道。
2. FileChannel类的基本使用
1).使用FileChannel做一个简单的复制:
注意:FileChannel是一个“抽象类”不能直接创建对象。可以通过一些“流对象”的getChannel()方法来获取一个“通道对象”。
2).示例代码:
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入要复制的文件:(加上绝对路径)");
String fileName1 = sc.next();
System.out.println("请输入目标文件:(加上绝对路径)");
String fileName2 = sc.next();
//1.定义输入、输出流的字节流
try (FileInputStream in = new FileInputStream(fileName1);
FileOutputStream out = new FileOutputStream(fileName2);
//2.通过输入、输出流获取"通道"对象
FileChannel inChannel = in.getChannel();
FileChannel outChannel = out.getChannel();
) {
//3.由于"通道"都是使用的Buffer存储数据,所以定义一个ByteBuffer用于临时存储数据
ByteBuffer buf = ByteBuffer.allocate(1024 * 8);
//4.循环读取
int len = 0;
while ((len = inChannel.read(buf)) != -1) {//读完一次后,buf的position应该在capacity的位置
//1.先flip():为最后一次准备的
buf.flip();//1.将limit设置为当前的position;2.将position设置为0;3.清除mark
//2.输出
outChannel.write(buf);//内部只输出:position 到 limit 之间的数据,它也会改变position
//3.将buf设置为初始状态,准备下一次的读取:为最后一次之前的几次准备的
buf.clear();//1.将position设置为0;2.将limit设置为capacity;3.清除mark
}
//打印
System.out.println("复制完毕!");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
3 FileChannel结合MappedByteBuffer实现高效读写
1).java.nio.MappedByteBuffer(抽象类):它是ByteBuffer的子类,它是自动创建的“系统内存空间”,使用这个对象作为缓存区,复制文件会非常快。
2).复制2G以下的文件:一次就成功
注意:限制2G的原因之一是:2G的大小刚好是int能取到的最大值。复制2G以上的文件中能用到,可看出。
示例代码:
public static void main(String[] args) {
//1.创建两个流
try (RandomAccessFile in = new RandomAccessFile("d:\\b.rar", "r");//输入流只需要"只读",所以设置为"r"-固定写法
RandomAccessFile out = new RandomAccessFile("e:\\b.rar","rw");//输出流要"读、写",所以设置为"rw"-固定写法
//2.获取两个通道
FileChannel inChannel = in.getChannel();
FileChannel outChannel = out.getChannel();
) {
//3.获取文件大小
long fileSize = inChannel.size();
//4.调用FileChannel的map()方法,获取一个"系统内存的映射空间"
//为什么最大只能复制2G内容?因为map方法的第三个参数内部会验证,最大只接受2G的字节数。
MappedByteBuffer inBuf = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, fileSize);
MappedByteBuffer outBuf = outChannel.map(FileChannel.MapMode.READ_WRITE, 0, fileSize);
//5.开始复制
for (int i = 0; i < fileSize; i++) {
//一次从inBuf中读取一个字节
byte b = inBuf.get(i);
//将这个字节输出到outBuf中
outBuf.put(i, b);
}
//打印
System.out.println("复制完毕!");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
3).复制2G以上的文件:分块复制
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入要复制的文件:(加上绝对路径)");
String fileName1 = sc.next();
System.out.println("请输入目标文件:(加上绝对路径)");
String fileName2 = sc.next();
//1.创建两个流
try (RandomAccessFile in = new RandomAccessFile(fileName1, "r");
RandomAccessFile out = new RandomAccessFile(fileName2,"rw");
//获取两个文件通道
FileChannel inChannel = in.getChannel();
FileChannel outChannel = out.getChannel();
) {
//获取文件大小
long fileSize = inChannel.size();
//预设一些变量
long everySize = 1024 * 1024 * 512;//每块大小:500M
long count = 1;//复制的次数,初始为:1
long everyCopySize = fileSize;//每次复制的大小,默认初始化为:文件大小
//判断是否需要分块
if (fileSize > everySize) {
//计算要复制的块数
count = fileSize % everySize == 0? fileSize / everySize:fileSize / everySize + 1;
//更改每次复制的大小
everyCopySize = everySize;
}
//分块复制
long startIndex = 0;
for (int i = 0; i < count; i++) {//控制复制的次数(块数)
//获取每块的映射缓存空间
MappedByteBuffer inBuf = inChannel.map(FileChannel.MapMode.READ_ONLY, startIndex, everyCopySize);
MappedByteBuffer outBuf = outChannel.map(FileChannel.MapMode.READ_WRITE, startIndex, everyCopySize);
//开始复制,这里就要求每次复制的大小小于int的取值的最大值2G。
for (int j = 0; j < everyCopySize; j++) {
byte b = inBuf.get(j);
outBuf.put(j, b);
}
//更改startIndex
startIndex += everyCopySize;
//更改每块的大小,因为最后一块,不足500M
everyCopySize = fileSize - startIndex > everySize ? everySize : fileSize - startIndex;
}
//打印
System.out.println("复制完毕!");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
4. SocketChannel和ServerSocketChannel实现连接
1.服务器端:ServerSocketChannel类用于连接的服务器端,它相当于:ServerSocket
1). 调用ServerSocketChannel的静态方法open():打开一个通道,新频道的套接字最初未绑定; 必 须通过其套接字的bind方法将其绑定到特定地址,才能接受连接。
ServerSocketChannel serverChannel = ServerSocketChannel.open()
2). 调用ServerSocketChannel的实例方法bind(SocketAddress add):绑定本机监听端口,准备接 受连接。 注:java.net.SocketAddress(抽象类):代表一个Socket地址。 我们可以使用它的子类:java.net.InetSocketAddress(类) 构造方法:InetSocketAddress(int port):指定本机监听端口。
serverChannel.bind(new InetSocketAddress(8888));
3). 调用ServerSocketChannel的实例方法accept():等待连接。
SocketChannel accept = serverChannel.accept();
我们可以通过ServerSocketChannel的configureBlocking(boolean b)方法设置accept()是否 阻塞
public class Server {
public static void main(String[] args){
try (ServerSocketChannel serverChannel = ServerSocketChannel.open())
{
serverChannel.bind(new InetSocketAddress(8888));
System.out.println("【服务器】等待客户端连接...");
// serverChannel.configureBlocking(true);//默认--阻塞
serverChannel.configureBlocking(false);//非阻塞
SocketChannel accept = serverChannel.accept();
System.out.println("后续代码......");
//......
//......
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端:SocketChannel类用于连接的客户端,它相当于:Socket。
1). 先调用SocketChannel的open()方法打开通道:
ServerSocketChannel serverChannel = ServerSocketChannel.open()
2). 调用SocketChannel的实例方法connect(SocketAddress add)连接服务器:
socket.connect(new InetSocketAddress("localhost", 8888));
示例:客户端连接服务器
public class Client {
public static void main(String[] args) {
try (SocketChannel socket = SocketChannel.open())
{
socket.connect(new InetSocketAddress("localhost", 8888));
System.out.println("后续代码......");
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("客户端完毕!");
}
}
5. SocketChannel和ServerSocketChannel实现收发信息
创建服务器端如下:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
public class Server {
public static void main(String[] args) {
try (ServerSocketChannel serverChannel = ServerSocketChannel.open()){
serverChannel.bind(new InetSocketAddress(8888));
System.out.println("【服务器】等待客户端链接...");
//serverChannel.configureBlocking(true);//默认--阻塞
//serverChannel.configureBlocking(false);//非阻塞
SocketChannel accept = serverChannel.accept();
System.out.println("【服务器】有连接到达...");
ByteBuffer outBuf = ByteBuffer.allocate(100);
outBuf.put("你好客户端,我是服务器".getBytes());
outBuf.flip();
accept.write(outBuf);
ByteBuffer inBuf = ByteBuffer.allocate(100);
accept.read(inBuf);
inBuf.flip();
String message = new String(inBuf.array(),0,inBuf.limit());
System.out.println("【服务器】收到消息:"+message);
accept.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
创建客户端如下:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
public class Client {
public static void main(String[] args) {
try (SocketChannel socket = SocketChannel.open()){
socket.connect(new InetSocketAddress("localhost",8888));
ByteBuffer outBuf = ByteBuffer.allocate(100);
outBuf.put("你好服务器,我是客户端".getBytes());
outBuf.flip();
socket.write(outBuf);
ByteBuffer inBuf = ByteBuffer.allocate(100);
socket.read(inBuf);
inBuf.flip();
String message = new String(inBuf.array(),0,inBuf.limit());
System.out.println("【客户端】收到消息:"+message);
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("客户端完毕!");
}
}