channel 类似于 stream,它是读写数据的双向通道,可以从 channel 将数据读入 buffer,也可以将 buffer 的数据写入 channel,而之前的 stream 要么是输入,要么是输出,channel 比 stream 更为底层
常见的 Channel 有:
常见的 buffer 有:
多线程
此前,服务器每接收到一个客户端的请求就需要开启一个线程建立 socket 连接,这样会有很多弊端:
线程池
固定线程池,线程数量是固定的,当请求太多时,那么这些请求会被阻塞,直到有 socket 断开线程空闲下来
selector
selector 的作用就是配合一个线程来管理多个 channel,获取这些 channel 上发生的事件,这些 channel 工作在非阻塞模式下,不会让线程吊死在一个 channel 上。适合连接数特别多,但流量低的场景。
调用 selector 的 select() 方法会阻塞知道 channel 发生了读写就绪事件,这些事件发生,select() 方法就会返回这些事件交给 thread 来处理
ByteBuffer 有以下重要属性:
初始状态下
写模式下
调用 flip() 后,position 切换为读取位置,limit 切换为为读取限制
分配空间
可以使用 allocate() 方法为 ByteBuffer 分配空间,其他 buffer 类也有该方法
ByteBuffer buf = ByteBuffer.allocate(16);
向 buffer 写入数据
有两种方法
int readBytes = channel.read(buf);
和
buf.put((byte)127);
从 buffer 读取数据
同样有两种方法
int writeBytes = channel.write(buf);
和
byte b = buf.get();
get 方法会让 position 读指针向后移动,如果想重复读取数据
分散读取,有一个文本文件 3parts.txt
onetwothree
使用如下方式读取,可以将数据填充至多个 buffer
try(RandomAccessFile file = new RandomAccessFile("helloworld/3parts.txt", "rw")){FileChannel channel = file.getChannel();ByteBuffer a = ByteBuffer.allocate(3);ByteBuffer b = ByteBuffer.allocate(3);ByteBuffer c = ByteBuffer.allocate(5);channel.read(new ByteBuffer[]{a, b, c});
}catch(IOException e){e.printStackTrace();
}
集中写入
ByteBuffer b1 = StandardCharasets.UTF_8.encode("hello");
ByteBuffer b2 = StandardCharasets.UTF_8.encode("world");
ByteBuffer b3 = StandardCharasets.UTF_8.encode("你好");try(FileChannel channel = new RandomAccessFile("words2.txt", "rw")){channel.write(new ByteBuffer[]{b1, b2, b3})
}catch(IOException e){
}
网络上有多条数据发送给服务器,数据之间使用 \n 进行分隔
但由于某种原因这些数据在接收时被重新组合,例如原始数据有3条:
Hello,world\n
I'm zhangsan\n
How are you?\n
变成了下面的两个 byteBuffer
Hello,world\nI'm zhangsan\nHo
w are you?\n
编写程序,将错乱的数据恢复成原始的按 \n 分隔的数据
ByteBuffer source = ByteBuffer,allocate(32);
source.put("Hello,world\nI'm zhangsan\nHo".getBytes());
split(source);
source.put("w are you?\n".getBytes());
split(source);private static void split(ByteBuffer source){source.flip();for(int i = 0; i < source.limit(); i++){//找到一条完整消息if(source.get(i) == '\n'){int length = i + 1 - source.position();//把这条完整消息存入新的 ByteBufferByteBuffer target = ByteBuffer.allocate(256);//从 source 读,向 target 写for(int j = 0; j < length; j++){target.put(source.get());}debugAll(target);}}
source.compact();
}
FileChannel 只能工作在阻塞模式下
获取
不能直接打开 FileChannel,必须通过 FileInputStream、FileOutputStream 或者 RandomAccessFile 来获取 FileChannel,它们都有 getChannel 方法
读取
会从 channel 读取数据填充 ByteBuffer,返回值表示读到了多少字节,-1 表示到达了文件的末尾
int readBytes = channel.read(buffer);
写入
写入的正确方式如下,SocketChannel
ByteBuffer buffer = ...;
buffer.put(...); //存入数据
buffer.flip(); //切换读模式while(buffer.hasRemaining()){channel.write(buffer);
}
在 while 中调用 channel.write 是因为 write 方法并不能保证一次性将 buffer 中的内容全部写入 channel
关闭
channel 必须关闭,不过调用了 FileInputStream、FileOutputStream 或者 RandomAccessFile 的 close() 方法会间接地调用 channel 的 close 方法
位置
获取当前位置
long pos = channel.position();
设置当前位置
long newPos = ...;
channel.position(newPos);
设置当前位置时,如果设置为文件的末尾
大小
使用 size 方法获取文件的大小
强制写入
操作系统处于性能的考虑,会将数据缓存,不是立刻写入磁盘。可以调用 force(true) 方法将文件内容和元数据(文件的权限等信息)立刻写入磁盘
String FROM = "helloworld/data.txt";
String TO = "helloworld/to.txt";
long start = System.nanoTime();
try(FileChannel from = new FileInputStream(FROM).getChannel();FileChannel to = new FileOutputStream(TO).getChannel();){//效率高,底层会利用操作系统的零拷贝进行优化from.transferTo(0, from.size(), to);
}catch(IOException){e.printStackTrace();
}
long end = System.nanaTime();
System.out.println("transferTo 用时:" + (end - start) / 1000_000.0);
输出
transferTo 用时:8.2001
jdk7 引入了 Path 和 Paths 类
Path source = Paths.get("1.txt"); //相对路径 使用 user.dir 环境变量来定位 1.txtPath source = Paths.get("d:\\1.txt"); //绝对路径 代表了 d:\1.txtPath source = Paths.get("d:/1.txt"); //绝对路径 代表了 d:\1.txtPath projects = Paths.get("d\\data", "projects"); //代表了 d:\data\projects
检查文件是否存在
Path path = Paths.get("helloworld/data.txt");
System.out.println(Files.exists(path));
创建一级目录
Path path = Paths.get("helloworld/d1");
Files.createDirectory(path);
创建多级目录
Path path = Path.get("helloworld/d1/d2");
Files.createDirectories(path);
拷贝文件
Path source = Paths.get("helloworld/data.txt");
Path target = Paths.get("helloworld/target.txt");Files.copy(source, target);
如果希望用 source 覆盖掉 target,需要用 StandardCopyOption 来空值
Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
移动文件
Path source = Paths.get("helloworld/data.txt");
Path target = Paths.get("helloworld/data.txt");Files.move(source, target, StandardCopyOption.ATOMIC_MOVE);
删除文件
Path target = Paths.get("helloworld/target.txt");
Files.delete(target);
删除目录
Path target = Paths.get("hellowworld/d1");
Files.delete(target);
遍历目录文件
public static void main(String[] args) throws IOException{//计数器,需要使用原子类,匿名类如果要使用外部变量,则该变量的地址值不能发生改变AtomicInteger dirCount = new AtomicInteger();AtomicInteger fileCount = new AtomicInteger();Files.walkFileTree(Paths.get("C:\\Program Files\\Java\\jdk1.8.0_91"), new SimpleFileVistor(){@Overridepublic FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException{System.out.println("====="+dir);dirCount.incrementAndGet();return super.preVisitDirectory(dir, attrs);}@Overridepublic FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException{System.out.println("====="+file);fileCount.incrementAndGet();return super.visitFile(file, attrs);}});System.out.println("dir count:" + dirCount);System.out.println("file count:" + fileCount);
}
查看文件夹下有多少个指定类型的文件
public static void main(String[] args) throws IOException{//计数器,需要使用原子类,匿名类如果要使用外部变量,则该变量的地址值不能发生改变AtomicInteger jarCount = new AtomicInteger();Files.walkFileTree(Paths.get("C:\\Program Files\\Java\\jdk1.8.0_91"), new SimpleFileVistor(){@Overridepublic FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException{if(file.toString().endWith(".jar")){System.out.println(file);}return super.visitFile(file, attrs);}});System.out.println("jar count:" + jarCount);
}
删除多级目录
(友情提示:不要直接执行!删掉后不会进回收站!)
public static void main(String[] args) throws IOException{Files.walkFileTree(Paths.get("C:\\Program Files\\Java\\jdk1.8.0_91"), new SimpleFileVistor(){@Overridepublic FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException{Files.delete(file)return super.visitFile(file, attrs);}@Overridepublic FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException{Files.delete(dir);return super.postVisitDirectory(file, exc);}});
}
拷贝多级目录
public static void main(String[] args) throws IOException{String source = "D:\\sourceDir";String target = "D:\\targetDir";Files.walk(Paths.get(source)).forEach(path->{try{String targetName = path.toString().replace(source, target);//是目录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();}});
}