文件编程

FileChannel

工作模式

FileChannel只能在阻塞模式下工作,所以无法搭配Selector

获取

不能直接打开FileChannel,必须通过FileInputStream、FileOutputStream或者RandomAccessFile来获取FileChannel,它们都有getChannel方法

通过FileInputStream获取的channel只能读通过FileOutputStream获取的channel只能写通过RandomAccessFile获取的channel能否读写需要根据构造RandomAccessFile时的读写模式决定

读取

通过 FileInputStream 获取channel,通过read方法将数据写入到ByteBuffer中

read方法的返回值表示读到了多少字节,若读到了文件末尾则返回-1

int readBytes = channel.read(buffer);

可根据返回值判断是否读取完毕

while(channel.read(buffer) > 0) {

// 进行对应操作

...

}

写入

因为channel也是有大小的,所以 write 方法并不能保证一次将 buffer 中的内容全部写入 channel。必须需要按照以下规则进行写入

// 通过hasRemaining()方法查看缓冲区中是否还有数据未写入到通道中

while(buffer.hasRemaining()) {

channel.write(buffer);

}

关闭

通道需要close,一般情况通过try-with-resource进行关闭,最好使用以下方法获取stream以及channel,避免某些原因使得资源未被关闭

public class TestChannel {

public static void main(String[] args) throws IOException {

try (FileInputStream fis = new FileInputStream("xx.txt");

FileOutputStream fos = new FileOutputStream("xxx.txt");

FileChannel inputChannel = fis.getChannel();

FileChannel outputChannel = fos.getChannel()) {

...

}

}

}

位置

position

channel也拥有一个保存读取数据位置的属性,即position

long pos = channel.position();

可以通过position(int pos)设置channel中position的值

long newPos = ...;

channel.position(newPos);

设置当前位置时,如果设置为文件的末尾

这时读取会返回 -1这时写入,会追加内容,但要注意如果 position 超过了文件末尾,再写入时在新内容和原末尾之间会有空洞(00)

强制写入

操作系统出于性能的考虑,会将数据缓存,不是立刻写入磁盘,而是等到缓存满了以后将所有数据一次性的写入磁盘。可以调用 force(true) 方法将文件内容和元数据(文件的权限等信息)立刻写入磁盘

两个Channel传输数据

transferTo方法

使用transferTo方法可以快速、高效的将一个channel中的数据传输到另一个channel中,但是一次只能传输2g的内容

transferTo底层使用了零拷贝技术

public class TestChannelTransferTo {

public static void main(String[] args) {

try (FileChannel channel = new RandomAccessFile("b.txt", "r").getChannel();

FileChannel channel1 = new RandomAccessFile("g.txt", "rw").getChannel()

) {

channel.transferTo(0,channel.size(),channel1);

} catch (IOException e) {

e.printStackTrace();

}

}

}

当传输的文件大于2G时,需要通过以下方法进行多次传输

/**

* @apiNote 解决transferTo方法一次只能写入2G数据

*/

@Slf4j

public class TestChannelTransferToGt2G {

public static void main(String[] args) {

try (FileChannel channel = new RandomAccessFile("b.txt", "r").getChannel();

FileChannel channel1 = new RandomAccessFile("d.txt", "rw").getChannel()

) {

long size = channel.size();

long hasNotReadSize = size;

while (hasNotReadSize > 0) {

hasNotReadSize -= channel.transferTo((size - hasNotReadSize), hasNotReadSize, channel1);

log.info("has not read size:{} post:{}", hasNotReadSize, (size - hasNotReadSize));

}

} catch (IOException e) {

e.printStackTrace();

}

}

}

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()); // 正常化路径 会去除 . 以及 ..

Files

查找

检查文件是否存在

Path path = Paths.get("a.txt");

System.out.println(Files.exists(path));

创建

创建一级目录

Path path= Path.get("C:/files");

Files.createDirectory(path);

如果目录已存在,会抛异常 FileAlreadyExistsException不能一次创建多级目录,否则会抛异常 NoSuchFileException

创建多级目录

Path path = Paths.get("C:/f1/f2");

Files.createDirectories(path);

拷贝和移动

拷贝文件

Path source = Paths.get("helloword/data.txt");

Path target = Paths.get("helloword/target.txt");

Files.copy(source, target);

如果文件已存在,会抛异常 FileAlreadyExistsException

如果希望覆盖掉文件,需要使用StandardCopyOption 来控制

Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING)

移动文件

Path source = Paths.get("helloword/data.txt");

Path target = Paths.get("helloword/data.txt");

Files.move(source, target, StandardCopyOption.ATOMIC_MOVE)

StandardCopyOption.ATOMIC_MOVE 保证文件移动的原子性

删除

删除文件

Path target = Paths.get("t.txt");

Files.delete(target);

如果文件不存在,会抛异常 NoSuchFileException

删除目录

Path target = Paths.get("d/d1");

Files.delete(target);

如果目录还有内容,会抛异常 DirectoryNotEmptyException

遍历

可以使用Files工具类中的walkFileTree(Path, FileVisitor)方法,其中需要传入两个参数

Path:文件起始路径FileVisitor:文件访问器,使用访问者模式,可以使用实现类SimpleFileVisitor

preVisitDirectory:访问目录前的操作visitFile:访问文件的操作visitFileFailed:访问文件失败时的操作postVisitDirectory:访问目录后的操作

统计文件数量

@Slf4j

public class TestFilleWalkFileTree {

public static void main(String[] args) throws IOException {

AtomicInteger dirCount=new AtomicInteger();

AtomicInteger fileCount=new AtomicInteger();

Files.walkFileTree(Paths.get("C:\\Java\\jdk1.8.0_291"),new SimpleFileVisitor(){

@Override

public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {

dirCount.incrementAndGet();

log.info("=====> dir:{}",dir);

return super.preVisitDirectory(dir, attrs);

}

@Override

public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {

log.info("file:{}",file);

fileCount.incrementAndGet();

return super.visitFile(file, attrs);

}

});

log.info("dir_count:{} file_count:{}",dirCount,fileCount);

}

}

批量删除文件

@Slf4j

public class TestFilesWalkFileTree {

//执行顺序 1.preVisitDirectory 2.visitFile 3.postVisitDirectory

public static void main(String[] args) throws IOException {

Files.walkFileTree(Paths.get("E:\\by_delete"),new SimpleFileVisitor(){

@Override

public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {

log.info("enter:{}",dir);

return super.preVisitDirectory(dir, attrs);

}

@Override

public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {

log.info("delete file:{}",file);

Files.delete(file);

return super.visitFile(file, attrs);

}

@Override

public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {

log.info("delete dir:{}",dir);

Files.delete(dir);

return super.postVisitDirectory(dir, exc);

}

});

}

}

精彩内容

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: