文件编程
即对文件进行操作
FileChannel
FileChannel 只能工作在阻塞模式下
获取getChannel
不能直接打开 FileChannel,只能通过下面的方法进行获取:
- 通过 FileInputStream 获取的 channel 只能读
- 通过 FileOutputStream 获取的 channel 只能写
- 通过 RandomAccessFile 是否能读写根据构造 RandomAccessFile 时的读写模式决定,并指定其读写模式为rw
//可读可写 RandomAccessFile file = new RandomAccessFile("test.txt", "rw").getChannel(); //可读 RandomAccessFile file = new RandomAccessFile("test.txt", "r").getChannel(); //可写 RandomAccessFile file = new RandomAccessFile("test.txt", "w").getChannel();
读取read
从 channel 读取数据填充 ByteBuffer,返回值表示读到了多少字节,-1 表示到达了文件的末尾
// readBytes=-1 的话表示到达了文件的末尾
int readBytes = channel.read(buffer);
写入write
以后使用的多是SocketChannel,SocketChannel传输数据的能力是有限的,不能因为buffer有多少数据,就一次性将这些数据全部写入到SocketChannel中,正确的写入步骤如下:
//正确的写入步骤
//在 while 中调用 channel.write 是因为 write 方法并不能保证一次将 buffer 中的内容全部写入 chann
while(buffer.hasRemaining()) {
channel.write(buffer);
}
//fileChannel二者都可以
ByteBuffer buffer = ...;
buffer.put(...); // 存入数据
buffer.flip(); // 切换读模式
关闭close
channel 必须关闭
关闭有两种关闭方式:
- 调用FileInputStream、FileOutputStream 或者 RandomAccessFile 的 close 方法(会间接调用 channel 的 close 方法)
- 调用 channel 的 close 方法
以后直接使用channel.close()去关闭即可
位置position
获取当前位置
long pos = channel.position();
设置当前位置
long newPos = ...;
channel.position(newPos);
设置当前位置时,如果设置为文件的末尾
- 这时读取会返回 -1
- 这时写入,会追加内容,但要注意如果 position 超过了文件末尾,再写入时在新内容和原末尾之间会有空洞(00)
大小size
使用 size 方法获取文件的大小
强制写入force
操作系统出于性能的考虑,会将数据缓存,不是立刻写入磁盘,当channel关闭的时候才会把所有的缓存数据同步到磁盘中。
可以调用 force(true) 方法将文件内容和元数据(文件的权限等信息)立刻写入磁盘
channel传输工具
transferTo(不超过2g)
从一个channel将数据传输到另一个channel
public static void main(String[] args) {
try (
//数据从哪来
FileChannel from = new FileInputStream("data.txt").getChannel();
//往哪写
FileChannel to = new FileOutputStream("to.txt").getChannel();
) {
// 效率高,底层会利用操作系统的零拷贝进行优化, 一次只能传2g 数据,多的传输不过去
//从0开始传
from.transferTo(0, left, to);
} catch (IOException e) {
e.printStackTrace();
}
}
超过2g
public static void main(String[] args) {
try (
//数据从哪来
FileChannel from = new FileInputStream("data.txt").getChannel();
//往哪写
FileChannel to = new FileOutputStream("to.txt").getChannel();
) {
//文件大小
long size = from.size();
// left 变量代表还剩余多少字节
for (long left = size; left > 0; ) {
System.out.println("position:" + (size - left) + " left:" + left);
//transferTo返回的结果是实际传输的字节数
left -= from.transferTo((size - left), left, to);
}
} catch (IOException e) {
e.printStackTrace();
}
}
Path
jdk7 引入了 Path 和 Paths 类
-
Path 用来表示文件路径
-
Paths 是工具类,用来获取 Path 实例
Path source = Paths.get("1.txt"); // 相对路径 使用 user.dir 环境变量来定位 1.txt //这种 \ 需要转义,所有是\\ Path source = Paths.get("d:\\1.txt"); // 绝对路径 代表了 d:\1.txt Path source = Paths.get("d:/1.txt"); // 绝对路径 同样代表了 d:\1.txt Path projects = Paths.get("d:\\data", "projects"); // 代表了 d:\data\projects
.
代表了当前路径
..
代表了上一级路径
测试:
Path path = Paths.get("d:\\data\\projects\\a\\..\\b");
System.out.println(path);//
System.out.println(path.normalize()); // 正常化路径
/*
目录结构:
d:
|- data
|- projects
|- a
|- b
normalize():将路径正常化
会输出如下内容:
d:\data\projects\a\..\b
d:\data\projects\b
*/
Files
检查文件是否存在exists
存在返回true
Path path = Paths.get("helloword/data.txt");
System.out.println(Files.exists(path));
创建一级目录createDirectory
如果目录已存在,会抛异常 FileAlreadyExistsException
不能一次创建多级目录
,否则会抛异常 NoSuchFileException
只能创建一级目录
Path path = Paths.get("helloword/d1");
Files.createDirectory(path);
创建多级目录createDirectories
创建多级目录,即使中间那一级目录不存在也会创建
Path path = Paths.get("helloword/d1/d2");
Files.createDirectories(path);
拷贝文件
copy
如果文件已存在,会抛异常 FileAlreadyExistsException
Path source = Paths.get("helloword/data.txt");
Path target = Paths.get("helloword/target.txt");
//从source复制到target,效率比较高,操作系统做的实现
Files.copy(source, target);
如果希望用 source 覆盖掉 target,需要用 StandardCopyOption 来控制(不管目标存不存在,都会替换掉target)
Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
以后拷贝文件,要么使用transferTo,要么使用copy,二者效率差不多
移动文件move
Path source = Paths.get("helloword/data.txt");
Path target = Paths.get("helloword/data.txt");
Files.move(source, target);
//StandardCopyOption.ATOMIC_MOVE 保证文件移动的原子性
Files.move(source, target, StandardCopyOption.ATOMIC_MOVE);
删除文件delete
如果文件不存在,会抛异常 NoSuchFileException
Path target = Paths.get("helloword/target.txt");
Files.delete(target);
删除目录delete
如果目录还有内容(这个目录里面还有文件或文件夹),会抛异常 DirectoryNotEmptyException
也就是说,只能删除空的目录
Path target = Paths.get("helloword/d1");
Files.delete(target);
遍历目录文件walkFileTree
private static void m1() throws IOException {
AtomicInteger dirCount = new AtomicInteger();
AtomicInteger fileCount = new AtomicInteger();
//walkFileTree:该方法有几种重载方法,基本上都可以通过类名判断出是什么用途
/*
preVisitDirectory:进入文件前
visitFile:进入文件后
postVistiDirectory:退出文件前
*/
Files.walkFileTree(Paths.get("C:\\xxx"), new SimpleFileVisitor<Path>(){
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
//这个遍历文件夹时包括当前的文件夹
System.out.println("====>"+dir);
dirCount.incrementAndGet();
//return的内容不能修改,需要添加的逻辑在其上面执行即可
return super.preVisitDirectory(dir, attrs);
}
@Override
public 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);
}
统计 jar 的数目
Path path = Paths.get("C:\\xxx");
AtomicInteger fileCount = new AtomicInteger();
Files.walkFileTree(path, new SimpleFileVisitor<Path>(){
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
throws IOException {
if (file.toFile().getName().endsWith(".jar")) {
fileCount.incrementAndGet();
}
return super.visitFile(file, attrs);
}
});
System.out.println(fileCount); // 724
删除多级目录walkFileTree
删除是危险操作,确保要递归删除的文件夹没有重要内容
Path path = Paths.get("d:\\xxx");
Files.walkFileTree(path, new SimpleFileVisitor<Path>(){
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
throws IOException {
Files.delete(file);
return super.visitFile(file, attrs);
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc)
throws IOException {
Files.delete(dir);
return super.postVisitDirectory(dir, exc);
}
});
拷贝多级目录walk
long start = System.currentTimeMillis();
String source = "D:\\Snipaste-1.16.2-x64";
String target = "D:\\Snipaste-1.16.2-x64aaa";
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();
}
});
long end = System.currentTimeMillis();
System.out.println(end - start);