netty(三)nio之文件編程

本篇文章主要討論一下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è)贊

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末壁榕,一起剝皮案震驚了整個(gè)濱河市矛紫,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌牌里,老刑警劉巖颊咬,帶你破解...
    沈念sama閱讀 216,744評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異牡辽,居然都是意外死亡喳篇,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,505評論 3 392
  • 文/潘曉璐 我一進(jìn)店門态辛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來麸澜,“玉大人,你說我怎么就攤上這事因妙√翟鳎” “怎么了?”我有些...
    開封第一講書人閱讀 163,105評論 0 353
  • 文/不壞的土叔 我叫張陵攀涵,是天一觀的道長铣耘。 經(jīng)常有香客問我,道長以故,這世上最難降的妖魔是什么蜗细? 我笑而不...
    開封第一講書人閱讀 58,242評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上炉媒,老公的妹妹穿的比我還像新娘踪区。我一直安慰自己,他們只是感情好吊骤,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,269評論 6 389
  • 文/花漫 我一把揭開白布缎岗。 她就那樣靜靜地躺著,像睡著了一般白粉。 火紅的嫁衣襯著肌膚如雪传泊。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,215評論 1 299
  • 那天鸭巴,我揣著相機(jī)與錄音眷细,去河邊找鬼。 笑死鹃祖,一個(gè)胖子當(dāng)著我的面吹牛溪椎,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播恬口,決...
    沈念sama閱讀 40,096評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼校读,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了楷兽?” 一聲冷哼從身側(cè)響起地熄,我...
    開封第一講書人閱讀 38,939評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎芯杀,沒想到半個(gè)月后端考,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,354評論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡揭厚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,573評論 2 333
  • 正文 我和宋清朗相戀三年却特,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片筛圆。...
    茶點(diǎn)故事閱讀 39,745評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡裂明,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出太援,到底是詐尸還是另有隱情闽晦,我是刑警寧澤,帶...
    沈念sama閱讀 35,448評論 5 344
  • 正文 年R本政府宣布提岔,位于F島的核電站仙蛉,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏碱蒙。R本人自食惡果不足惜荠瘪,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,048評論 3 327
  • 文/蒙蒙 一夯巷、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧哀墓,春花似錦趁餐、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,683評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至阶牍,卻和暖如春喷面,著一層夾襖步出監(jiān)牢的瞬間星瘾,已是汗流浹背走孽。 一陣腳步聲響...
    開封第一講書人閱讀 32,838評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留琳状,地道東北人磕瓷。 一個(gè)月前我還...
    沈念sama閱讀 47,776評論 2 369
  • 正文 我出身青樓,卻偏偏與公主長得像念逞,于是被迫代替她去往敵國和親困食。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,652評論 2 354

推薦閱讀更多精彩內(nèi)容