通道
通道和buffer
Java NIO系统的核心在于:通道(Channel)和缓冲区(Buffer)。通道表示打开到 IO 设备(例如:文件、套接字)的连接。若需要使用 NIO 系统,需要获取用于连接 IO 设备的通道以及用于容纳数据的缓冲区。然后操作缓冲区,对数据进行处理。
》》》简而言之,Channel 负责传输, Buffer 负责存储
从通道读取数据到缓冲区
从缓冲区写入数据到通道
1.定义
-Channel 表示 IO 源与目标打开的连接。Channel 类似于传统的“流”,但Channel既可以从通道中读取数据,又可以写数据到通道。但流的读写通常是单向的,不仅如此,通道可以异步地读写。
-只不过 Channel本身不能直接访问数据,Channel 只能与Buffer 进行交互。通道中的数据总是要先读到一个Buffer,或者总是要从一个Buffer中写入。
2.通道类型
• FileChannel:用于读取、写入、映射和操作文件的通道。
• DatagramChannel:通过 UDP 读写网络中的数据通道。
• SocketChannel:通过 TCP 读写网络中的数据。
• ServerSocketChannel:可以监听新进来的 TCP 连接,对每一个新进来
的连接都会创建一个 SocketChannel。
3.获取通道
①Java 针对支持通道的类提供了 getChannel() 方法
FileInputStream
FileOutputStream
RandomAccessFile
DatagramSocket
Socket
ServerSocket
FileInputStream fis = new FileInputStream("d:/1.mkv");
FileChannel inChannel = fis.getChannel();
Socket socket = new Socket("127.0.0.1",12345);
SocketChannel channel = socket.getChannel();
②获取通道的其他方式是使用 Files 类的静态方法 newByteChannel() 获取字节通道
③通过通道的静态方法 open() 打开并返回指定通道
·public static FileChannel open(Path path, OpenOption... options)
public static ServerSocketChannel open()
OpenOption: 配置文件的操作方式
-主要使用实现类枚举StandardOpenOption
4.通道与缓冲区的数据传输
//将 Buffer 中数据写入 Channel
int byteWrite = inChannel.wirte(buffer);
//从 Channel 读取数据到 Buffer
int byteRead = inChannel.read(buffer);
//SocketChannel 从 Channel 读取数据到 Buffer
ServerSocketChannel ssChannel = ServerSocketChannel.open();
SocketChannel sChannel = ssChannel.accept();
sChannel.read(buffer)
5.FileChannel
常用方法
方 法 | 描述 |
---|---|
int read(ByteBuffer dst) | 从Channel 到 中读取数据到 ByteBuffer |
long read(ByteBuffer[] dsts) | 将Channel 到 中的数据“分散”到 ByteBuffer[] |
int write(ByteBuffer src) | 将ByteBuffer 到 中的数据写入到 Channel |
long write(ByteBuffer[] srcs) | 将ByteBuffer[] 到 中的数据“聚集”到 Channel |
long position() | 返回此通道的文件位置 |
FileChannel position(long p) | 设置此通道的文件位置 |
long size() | 返回此通道的文件的当前大小 |
FileChannel truncate(long s) | 将此通道的文件截取为给定大小 |
void force(boolean metaData) | 强制将所有对此通道的文件更新写入到存储设备中 |
通道与通道的数据传输
* @param position
* The position within the file at which the transfer is to begin; must be non-negative(非负数)
* @param count
* The maximum number of bytes to be transferred; must be non-negative(非负数)
* @param target The target channel
**/
long transferTo(long position, long count, WritableByteChannel target)
* @param src The source channel
* @param position
*The position within the file at which the transfer is to begin; must be non-negative(非负数)
* @param count
The maximum number of bytes to be transferred; must be non-negative(非负数)
* */
long transferFrom(ReadableByteChannel src, long position, long count)
transferFrom:
transferTo:
练习:
文件复制
1.利用通道(非直接缓冲区)
//利用通道(非直接缓冲区)
@Test
public void testCopy() {
long start = Instant.now().toEpochMilli();
FileInputStream fis = null;
FileOutputStream fos = null;
FileChannel inChannel = null;
FileChannel outChannel = null;
try {
//获取通道FileChannel
fis = new FileInputStream("F:/BaiduYunDownload/old.rar");
fos = new FileOutputStream("F:/BaiduYunDownload/new.rar");
inChannel = fis.getChannel();
outChannel = fos.getChannel();
②分配指定大小的缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
// 将通道中的数据存入缓冲区中
while (inChannel.read(buf) != -1){
buf.flip(); //切换读取数据的模式
// 将缓冲区中的数据写入通道中
outChannel.write(buf);
buf.clear(); //清空缓冲区
}
} catch (Exception e) {
e.printStackTrace();
}finally {
outChannel.close();
inChannel.close();
fos.close();
fis.close();
}
}
2.使用直接缓冲区完成文件的复制(内存映射文件) NIO的path和Files用法
//使用直接缓冲区完成文件的复制(内存映射文件)
@Test
public void test2() {
try {
// https://www.cnblogs.com/fysola/p/6143939.html
Path path = Paths.get("F:/","/BaiduYunDownload","old.rar");
//Path path1 = Paths.get("F:/BaiduYunDownload/old.rar");
// Path path2 = Paths.get("F:/"+"BaiduYunDownload/old.rar");
//
FileChannel inChannel = FileChannel.open(path, StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(Paths.get("F:/BaiduYunDownload/new.rar"),
StandardOpenOption.CREATE_NEW, StandardOpenOption.CREATE_NEW, StandardOpenOption.READ);
// 内存映射文件
MappedByteBuffer inMappedBuf = inChannel.map(FileChannel.MapMode.READ_ONLY,0, inChannel.size());
MappedByteBuffer outMappedBuf = outChannel.map(FileChannel.MapMode.READ_WRITE, 0, inChannel.size());
//直接对缓冲区进行数据的读写操作
byte[] dst = new byte[inMappedBuf.limit()];
inMappedBuf.get(dst);
outMappedBuf.put(dst);
inChannel.close();
outChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
通道之间的数据传输(直接缓冲区)
//通道之间的数据传输(直接缓冲区)
@Test
public void test3() throws IOException{
FileChannel inChannel = FileChannel.open(Paths.get("F:/BaiduYunDownload/old.rar"), StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(Paths.get("F:/BaiduYunDownload/new.rar"), StandardOpenOption.WRITE,
StandardOpenOption.READ, StandardOpenOption.CREATE);
// inChannel.transferTo(0, inChannel.size(), outChannel);
outChannel.transferFrom(inChannel, 0, inChannel.size());
inChannel.close();
outChannel.close();
}
分散 (Scatter) 和聚集
分散读取(Scattering Reads)
是指从 Channel 中读取的数据“分散”到多个 Buffer 中。
注意:按照缓冲区的顺序,从 Channel 中读取的数据依次将 Buffer 填满。
聚集写入(Gathering Writes)
是指将多个 Buffer 中的数据“聚集”到 Channel。
注 意:按照缓冲区的顺序,写入 position 和 limit 之间的数据到 Channel 。
/*RandomAccessFile是Java输入/输出流体系中功能最丰富的文件内容访问类,既可以读取文件内容,也可以向文件输出数据。
与普通的输入/输出流不同的是,RandomAccessFile支持跳到文件任意位置读写数据,RandomAccessFile对象包含一个记录指针,
用以标识当前读写处的位置,当程序创建一个新的RandomAccessFile对象时,该对象的文件记录指针对于文件头(也就是0处),
当读写n个字节后,文件记录指针将会向后移动n个字节。除此之外,RandomAccessFile可以自由移动该记录指针
RandomAccessFile包含两个方法来操作文件记录指针:
RandomAccessFile(File file , String mode)
//创建随机存储文件流,文件属性由参数File对象指定
RandomAccessFile(String name , String mode)
//创建随机存储文件流,文件名由参数name指定
r:以只读方式打开指定文件。如果试图对该RandomAccessFile指定的文件执行写入方法则会抛出IOException
rw:以读取、写入方式打开指定文件。如果该文件不存在,则尝试创建文件
rws:以读取、写入方式打开指定文件。相对于rw模式,还要求对文件的内容或元数据的每个更新都同步写入到底层存储设备,默认情形下(rw模式下),是使用buffer的,
只有cache满的或者使用RandomAccessFile.close()关闭流的时候儿才真正的写到文件
rwd:与rws类似,只是仅对文件的内容同步更新到磁盘,而不修改文件的元数据*/
RandomAccessFile raf1 = new RandomAccessFile("1.txt", "rw");
//1. 获取通道
FileChannel channel1 = raf1.getChannel();
//2. 分配指定大小的缓冲区
ByteBuffer buf1 = ByteBuffer.allocate(100);
ByteBuffer buf2 = ByteBuffer.allocate(1024);
//3. 分散读取
ByteBuffer[] bufs = {buf1, buf2};
channel1.read(bufs);
for (ByteBuffer byteBuffer : bufs) {
byteBuffer.flip();
}
System.out.println(new String(bufs[0].array(), 0, bufs[0].limit()));
System.out.println("-----------------");
System.out.println(new String(bufs[1].array(), 0, bufs[1].limit()));
//4. 聚集写入
RandomAccessFile raf2 = new RandomAccessFile("2.txt", "rw");
FileChannel channel2 = raf2.getChannel();
channel2.write(bufs);