本篇文章主要討論一下NIO中的文件變成低飒,主要是FileChannel的用法。
一桑驱、FileChannel常用操作
1.1 獲取FileChannel
有一個(gè)文件text.txt竭恬,其內(nèi)容如下:
abcdef
不能直接打開 FileChannel,必須通過 FileInputStream熬的、FileOutputStream 或者 RandomAccessFile 來獲取 FileChannel痊硕,它們都有 getChannel 方法
1.1.1 通過 FileInputStream 獲取
public static void main(String[] args) throws Exception {
//使用FileInputStream獲取channel
FileInputStream fileInputStream = new FileInputStream(new File("C:\\Users\\P50\\Desktop\\text.txt"));
FileChannel channel1 = fileInputStream.getChannel();
ByteBuffer buffer= ByteBuffer.allocate(10);
//channel1.write(buffer);
channel1.read(buffer);
buffer.flip();
System.out.println((print(buffer)));
}
static String print(ByteBuffer b) {
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < b.limit(); i++) {
stringBuilder.append((char) b.get(i));
}
return stringBuilder.toString();
}
結(jié)果:
abcdef
通過 FileInputStream 獲取的 channel 只能讀,如果使用寫入write方法押框,會(huì)拋出異常:
Exception in thread "main" java.nio.channels.NonWritableChannelException
at sun.nio.ch.FileChannelImpl.write(FileChannelImpl.java:201)
at com.cloud.bssp.nio.FileChannel.GetFileChannel.main(GetFileChannel.java:21)
1.1.2 通過 FileOutputStream 獲取
public static void main(String[] args) throws Exception {
//使用FileOutputStream獲取channel
FileOutputStream fileOutputStream = new FileOutputStream(new File("C:\\Users\\P50\\Desktop\\text.txt"),true);
FileChannel channel2 = fileOutputStream.getChannel();
ByteBuffer buffer= ByteBuffer.allocate(10);
buffer.put(StandardCharsets.UTF_8.encode("helloworld"));
buffer.flip();
channel2.write(buffer);
}
static String print(ByteBuffer b) {
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < b.limit(); i++) {
stringBuilder.append((char) b.get(i));
}
return stringBuilder.toString();
}
文件被寫入寿桨,這里注意FileOutputStream 的屬性append,如果是true强戴,表示追加亭螟,否則覆蓋。本文使用的追加骑歹。
abcdefhelloworld
通過 FileOutputStream 獲取的 channel 只能寫预烙,如果使用read方法,會(huì)拋出異常:
Exception in thread "main" java.nio.channels.NonReadableChannelException
at sun.nio.ch.FileChannelImpl.read(FileChannelImpl.java:149)
at com.cloud.bssp.nio.FileChannel.GetFileChannel.main(GetFileChannel.java:28)
1.1.3 通過 RandomAccessFile獲取
public static void main(String[] args) throws Exception {
//使用RandomAccessFile獲取channel
RandomAccessFile file = new RandomAccessFile("C:\\Users\\P50\\Desktop\\text.txt", "rw");
FileChannel channel3 = file.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(15);
//讀取文件內(nèi)容到buffer
channel3.read(buffer);
buffer.flip();
System.out.println(print(buffer));
// 切換為寫模式道媚,并且清空buffer
buffer.clear();
//寫入helloworld到文件
buffer.put(StandardCharsets.UTF_8.encode("helloworld"));
buffer.flip();
channel3.write(buffer);
}
// 切換為寫模式扁掸,并且清空buffer
buffer.clear();
//寫入helloworld到文件
buffer.put(StandardCharsets.UTF_8.encode("helloworld"));
buffer.flip();
channel3.write(buffer);
結(jié)果:這里讀取的少了一個(gè)字節(jié),因?yàn)槲抑付ǖ腷uffer只有15最域,文檔中是16谴分,只讀取了一次,。
abcdefhelloworl
文檔內(nèi)容被修改為如下镀脂,將channel讀取到的內(nèi)容以及新加入的內(nèi)容拼接在了一起
abcdefhelloworlhelloworld
通過 RandomAccessFile 是否能讀寫根據(jù)構(gòu)造 RandomAccessFile 時(shí)的讀寫模式?jīng)Q定牺蹄,指定rw(讀寫模式)。
1.2 讀取和寫入
1.2.1 讀取
在前面的獲取例子中已經(jīng)給出了關(guān)于讀取的方式薄翅,如下所示沙兰,會(huì)返回int類型氓奈,從 channel 讀取數(shù)據(jù)填充ByteBuffer,返回值表示讀到了多少字節(jié)鼎天,返回值為-1 表示到達(dá)了文件的末尾舀奶。
int readBytes = channel.read(buffer);
仍然使用上面的第一個(gè)例子,如果文檔是空的話斋射,則會(huì)返回-1
int read = channel1.read(buffer);
System.out.println(read);
-1
1.2.2 寫入
如上一章節(jié)的例子育勺,已經(jīng)演示了如何寫入數(shù)據(jù),利用write方法罗岖,將buffer的數(shù)據(jù)寫入channel涧至,但是正確的寫入方式應(yīng)該如下所示:
while(buffer.hasRemaining()) {
channel.write(buffer);
}
hasRemaining()是buffer的一個(gè)方法,判斷position是否小于limit呀闻,是則返回true化借,表示buffer仍然有未讀取的數(shù)據(jù)。
在 while 中調(diào)用 channel.write 是因?yàn)?write 方法并不能保證一次將 buffer 中的內(nèi)容全部寫入 channel捡多。
1.2.3 強(qiáng)制寫入
操作系統(tǒng)出于性能的考慮蓖康,會(huì)將數(shù)據(jù)緩存,不是立刻寫入磁盤垒手∷夂福可以調(diào)用 channel.force(true) 方法將文件內(nèi)容和元數(shù)據(jù)(文件的權(quán)限等信息)立刻寫入磁盤。
public abstract void force(boolean metaData) throws IOException;
1.3 關(guān)閉
像我們上面寫的代碼實(shí)際上都沒有去關(guān)閉流和channel的科贬,這如果在生產(chǎn)環(huán)境都是會(huì)產(chǎn)生嚴(yán)重的問題泳梆。
channel是必須要關(guān)閉的,不過調(diào)用了 FileInputStream榜掌、FileOutputStream 或者 RandomAccessFile 的 close() 方法會(huì)間接地調(diào)用 channel 的 close 方法优妙。
看下FileInputStream的close方法:
public void close() throws IOException {
synchronized (closeLock) {
if (closed) {
return;
}
closed = true;
}
if (channel != null) {
channel.close();
}
fd.closeAll(new Closeable() {
public void close() throws IOException {
close0();
}
});
}
1.4 FileChannel的位置
獲取當(dāng)前位置
long pos = channel.position();
設(shè)置當(dāng)前位置
long newPos = ...;
channel.position(newPos);
如下獲取文件channel:
// 文件內(nèi)容為10個(gè)字節(jié)的helloworld
RandomAccessFile file = new RandomAccessFile("C:\\Users\\P50\\Desktop\\text.txt", "rw");
FileChannel channel = file.getChannel();
打印不同設(shè)置時(shí)的位置:
// 打印位置,沒有讀取時(shí)是0
System.out.println(channel.position());
// 讀取后是文件的長度
ByteBuffer buffer = ByteBuffer.allocate(10);
channel.read(buffer);
System.out.println(channel.position());
// 設(shè)置位置后的長度
FileChannel position = channel.position(5);
System.out.println(position.position());
結(jié)果:
0
10
5
1.5 獲取文件大小
channel.size();
二憎账、channel的相互傳輸
channel提供兩個(gè)用來channel相互傳輸數(shù)據(jù)的方法:
/**
* 將一個(gè)channel的數(shù)據(jù)傳輸?shù)絫arget這個(gè)channel中套硼,其中position,count胞皱,都是調(diào)用此方法的channel的
* in.transferTo(0, in.size(), out);
*/
transferTo(long position, long count, WritableByteChannel target)
/**
* 一個(gè)channel從src這個(gè)channel獲取數(shù)據(jù)邪意,其中position,count反砌,都是src這個(gè)channel的
* out.transferFrom(in,0,in.size());
*/
transferFrom(ReadableByteChannel src, long position, long count)
使用例子如下:
public class TestCopyFileByNIO {
public static void fileChannelCopy(String sfPath, String tfPath) {
FileInputStream fi = null;
FileOutputStream fo = null;
FileChannel in = null;
FileChannel out = null;
try {
fi = new FileInputStream(new File(sfPath));
fo = new FileOutputStream(new File(tfPath));
in = fi.getChannel();//得到對應(yīng)的文件通道
out = fo.getChannel();//得到對應(yīng)的文件通道
in.transferTo(0, in.size(), out);//連接兩個(gè)通道雾鬼,并且從in通道讀取,然后寫入out通道
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
fi.close();
fo.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
long start = System.currentTimeMillis();
String sPath = "E:\\workspace\\comprehend-service.rar";
String tPath = "E:\\workspace\\comprehend-service-" + System.currentTimeMillis() + "-bak.rar";
fileChannelCopy(sPath, tPath);
long end = System.currentTimeMillis();
System.out.println("用時(shí)為:" + (end - start) + "ms");
}
}
結(jié)果:
用時(shí)為:194ms
2.1 channel的最大傳輸值
channel的傳輸是有大小限制的宴树,最大為2個(gè)g策菜,超過會(huì)導(dǎo)致數(shù)據(jù)丟失。所以需要使用循環(huán)去多次傳輸數(shù)據(jù)。
public class TestCopyFileByNIO {
public static void fileChannelCopy(String sfPath, String tfPath) {
FileInputStream fi = null;
FileOutputStream fo = null;
FileChannel in;
FileChannel out;
try {
fi = new FileInputStream(new File(sfPath));
fo = new FileOutputStream(new File(tfPath));
in = fi.getChannel();
out = fo.getChannel();
// 總文件大小
long size = in.size();
// left 剩余文件的數(shù)量
for (long left = size; left > 0;){
System.out.println("position = " + (size - left) + "做入,left = " + left);
// transferTo返回傳輸?shù)臄?shù)量冒晰,剩余的減去傳輸?shù)耐拢褪钱?dāng)前剩余的數(shù)量
left -= in.transferTo((size -left), left, out);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
fi.close();
fo.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
long start = System.currentTimeMillis();
String sPath = "E:\\workspace\\workspace.zip";
String tPath = "E:\\workspace\\workspace-" + System.currentTimeMillis() + "-bak.zip";
fileChannelCopy(sPath, tPath);
long end = System.currentTimeMillis();
System.out.println("用時(shí)為:" + (end - start) + "ms");
}
結(jié)果:
position = 0竟块,left = 2925330022
position = 2147483647,left = 777846375
用時(shí)為:13664ms
三耐齐、Path 和 Paths 類
jdk7 引入了 Path 和 Paths 類
Path 用來表示文件路徑
Paths 是工具類浪秘,用來獲取 Path 實(shí)例
// 相對路徑 使用 user.dir 環(huán)境變量來定位 1.txt
Path source = Paths.get("1.txt");
// 絕對路徑 代表了 d:\1.txt
Path source = Paths.get("d:\\1.txt");
// 絕對路徑 同樣代表了 d:\1.txt
Path source = Paths.get("d:/1.txt");
// 代表了 d:\data\projects
Path projects = Paths.get("d:\\data", "projects");
-
.
代表了當(dāng)前路徑 -
..
代表了上一級路徑
例如目錄結(jié)構(gòu)如下
d:
|- data
|- projects
|- a
|- b
代碼
Path path = Paths.get("d:\\data\\projects\\a\\..\\b");
System.out.println(path);
// 正常化路徑
System.out.println(path.normalize());
會(huì)輸出
d:\data\projects\a\..\b
d:\data\projects\b
四埠况、Files類
檢查文件是否存在
Path path = Paths.get("helloword/data.txt");
System.out.println(Files.exists(path));
創(chuàng)建一級目錄
Path path = Paths.get("helloword/d1");
Files.createDirectory(path);
- 如果目錄已存在耸携,會(huì)拋異常 FileAlreadyExistsException
- 不能一次創(chuàng)建多級目錄,否則會(huì)拋異常 NoSuchFileException
創(chuàng)建多級目錄用
Path path = Paths.get("helloword/d1/d2");
Files.createDirectories(path);
拷貝文件
Path source = Paths.get("helloword/data.txt");
Path target = Paths.get("helloword/target.txt");
Files.copy(source, target);
- 如果文件已存在辕翰,會(huì)拋異常 FileAlreadyExistsException
如果希望用 source 覆蓋掉 target夺衍,需要用 StandardCopyOption 來控制
Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
移動(dòng)文件
Path source = Paths.get("helloword/data.txt");
Path target = Paths.get("helloword/data.txt");
Files.move(source, target, StandardCopyOption.ATOMIC_MOVE);
- StandardCopyOption.ATOMIC_MOVE 保證文件移動(dòng)的原子性
刪除文件
Path target = Paths.get("helloword/target.txt");
Files.delete(target);
- 如果文件不存在,會(huì)拋異常 NoSuchFileException
刪除目錄
Path target = Paths.get("helloword/d1");
Files.delete(target);
- 如果目錄還有內(nèi)容喜命,會(huì)拋異常 DirectoryNotEmptyException
遍歷目錄文件
public static void main(String[] args) throws IOException {
Path path = Paths.get("C:\\Program Files\\Java\\jdk1.8.0_91");
AtomicInteger dirCount = new AtomicInteger();
AtomicInteger fileCount = new AtomicInteger();
Files.walkFileTree(path, new SimpleFileVisitor<Path>(){
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
throws IOException {
System.out.println(dir);
dirCount.incrementAndGet();
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(dirCount); // 133
System.out.println(fileCount); // 1479
}
統(tǒng)計(jì) jar 的數(shù)目
Path path = Paths.get("C:\\Program Files\\Java\\jdk1.8.0_91");
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
刪除多級目錄
Path path = Paths.get("d:\\a");
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);
}
});
刪除是危險(xiǎn)操作沟沙,確保要遞歸刪除的文件夾沒有重要內(nèi)容
拷貝多級目錄
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);
關(guān)于NIO文件編程此處就寫到這了,有幫助的話朋友個(gè)點(diǎn)個(gè)贊