Java-NIO篇章(3)——Channel通道类详解
Java NIO中,一个socket连接使用一个Channel(通道)来表示。对应到不同的网络传输协议类型,在Java中都有不同的NIO Channel(通道) 相对应。其中最为重要的四种Channel(通道)实现: FileChannel、 SocketChannel、 ServerSocketChannel、 DatagramChannel :
- FileChannel 文件通道,用于文件的数据读写; (管文件的传输通道)
- SocketChannel 套接字通道,用于Socket套接字TCP连接的数据读写; (管TCP数据传输的通道)
- ServerSocketChannel 服务器套接字通道(或服务器监听通道),允许我们监听TCP连接请求,为每个监听到的请求,创建一个SocketChannel套接字通道; (只管与服务器 TCP连接 的通道)
- DatagramChannel 数据报通道,用于UDP协议的数据读写。 (管UDP数据传输的通道)
我在学习Channel的时候,老是搞不清楚ServerSocketChannel和SocketChannel的关系,这次我不允许我的读者也搞不清。用大白话讲就是通道你可以理解为数据传输的管道,这个管道是双向传输的,即既可以通过Channel向文件或者网络客户端写数据也可以从文件或者网络客户端读数据。如果你要读取文件的数据,使用FileChannel ;如果需要建立网络连接,在服务器使用ServerSocketChannel 来作为客户端连接请求的通道,也就是说它只负责服务器端的连接请求的数据传输。通过ServerSocketChannel就可以和服务器建立连接,然后通过ServerSocketChannel创建SocketChannel 通道进行TCP数据传输。下面分别介绍每一个通道的用法。
FileChannel文件通道
FileChannel是专门操作文件的通道。通过FileChannel,既可以从一个文件中读取数据,也可以将数据写入到文件中。特别申明一下, FileChannel为阻塞模式,不能设置为非阻塞模式。不说你也知道,学习IO操作可以首先要获取FileChannel通道 、然后读取FileChannel通道中的数据或者将数据写入FileChannel通道,然后关闭通道。最后补充一个就是强制将通道的数据刷盘到磁盘的方法即可,那么就按照上面的步骤开始吧!
获取到FileChannel对象
获取FileChannel对象有三种方式,第一种方式可以通过文件的输入流、输出流获取FileChannel文件通道,代码如下:
//创建一个文件输入流 FileInputStream fis = new FileInputStream("word.txt"); //获取文件流的通道,只能从通道中读取数据,不能写入数据 FileChannel inChannel = fis.getChannel(); //创建一个文件输出流 FileOutputStream fos = new FileOutputStream("word.txt"); //获取文件流的通道,只能向通道中写入数据,不能读取数据 FileChannel outchannel = fos.getChannel();
也可以通过RandomAccessFile文件随机访问类,获取FileChannel文件通道实例,代码如下:
// 创建 既可以写也可以读的随机访问类 RandomAccessFile 随机访问对象 // 参数"rw"表示可读可写,如果只读可以给"r",只写给"w"即可 RandomAccessFile rFile = new RandomAccessFile("word.txt", "rw"); //获取文件流的通道(可读可写) FileChannel channel = rFile.getChannel();
从FileChannel中读取数据
下面给出标准的读取数据的代码,具体解释在注释中,代码中channel.read(buffer)将通道的数据读到缓冲区上,虽然是读取通道的数据,对于通道来说是读取模式,但是对于ByteBuffer缓冲区来说则是写入数据,这时, ByteBuffer缓冲区处于写入模式 ,而buffer.get()才是从通道读取数据,需要flip()切换读模式:
try(FileChannel channel = new RandomAccessFile("word.txt", "rw").getChannel()){ // 准备缓冲区,分配10字节的空间 ByteBuffer buffer = ByteBuffer.allocate(10); int len = -1; while ((len=channel.read(buffer))!=-1){ // 将channel中的数据读取到缓存区中,返回读到的数据长度,没读到数据返回-1 buffer.flip(); // 切换读取模式,左右指针指向已存数据首位 while (buffer.hasRemaining()){// 如果position log.error("文件未找到"); } // wrap 方法执行完自动切换wrapBuffer为读模式 ByteBuffer wrapBuffer = ByteBuffer.wrap("你好世界!".getBytes()); try(FileChannel channel = new RandomAccessFile("word.txt","rw").getChannel()){ int len = 0; while ((len = channel.write(wrapBuffer))!=0){ System.out.println("已经写入字节数为:"+len); //已经写入字节数为:15 } }catch (IOException e){ log.error("文件未找到"); } } // 关闭通道 channel.close(); //强制刷新到磁盘 channel.force(true); try( FileChannel from = new FileInputStream("world.txt").getChannel(); FileChannel to = new FileOutputStream("to.txt").getChannel() ) { long size = from.size(); for (long left = size; left 0 ; ) { // 该方法每次最多传输2g的数据量 long n = from.transferTo((size-left), from.size(), to); left -= n; } }catch (IOException ie){ ie.printStackTrace(); } }
NIO提供的关于File的操作
遍历目录文件:
public class FileTest { public static void main(String[] args) throws IOException { // 访问文件夹 visitorFile(); // 拷贝文件夹 copyFile(); // 删除文件夹 deleteFile(); } private static void copyFile() throws IOException { String source = "C:\\Users\\cheney\\Documents\\CFSystem"; String target = "C:\\Users\\cheney\\Documents\\CFSystem_bak"; Files.walk(Paths.get(source)).forEach(path -> { // 替换成新的路径 String targetName = path.toString().replace(source, target); try { if(Files.isDirectory(path)){ Files.createDirectory(Paths.get(targetName)); }else if(Files.isRegularFile(path)){ Files.copy(path,Paths.get(targetName)); } }catch (IOException e){ e.printStackTrace(); } }); } private static void deleteFile() throws IOException { Files.walkFileTree(Paths.get("C:\\Users\\cheney\\Documents\\CFSystem_bak"),new SimpleFileVisitor(){ @Override // 在访问目录之前被调用。你可以在这里执行预处理操作。 public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { // 进入文件夹时不能删除文件夹,因为里面还有文件 System.out.println("进入------>"+dir); return super.preVisitDirectory(dir, attrs); } @Override // 在访问某个目录的文件时被调用。你可以在这里执行对文件的操作。 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { // 删除文件夹 System.out.println("删除xxxxxx:"+file); Files.delete(file); return super.visitFile(file, attrs); } @Override // 在访问文件失败时被调用。例如,由于权限问题或其他原因,无法访问文件。 public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { return super.visitFileFailed(file, exc); } @Override // 在访问目录之后被调用。你可以在这里执行后处理操作。 public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { // 如果退出之前遍历删除过文件,那么可以删除文件夹 System.out.println("退出