欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

JAVA?拷貝文件的幾種方式小結

 更新時間:2024年03月26日 09:37:26   作者:碼頭的薯條  
本文主要介紹了JAVA拷貝文件的幾種方式,包含普通拷貝,mmap內存映射的方式拷貝,零拷貝sendFile方式實現(xiàn)和多線程的方式實現(xiàn)拷貝,具有一定的參考價值,感興趣的可以了解一下

1. 前言

閑話少敘,今天主要講講 JAVA 四種拷貝文件的方式,分析一下他們對內存使用的方式和各自應用的場景,其實也是對之前學過的知識做一個回顧吧,畢竟太久不回顧的話,記憶就像拼圖,隨著時間流逝就只剩下散落一地的碎片了。

2. 普通拷貝

protected void copyFile(File source, File target) {
    try (FileInputStream is = new FileInputStream(source);
         FileOutputStream os = new FileOutputStream(target);) {
         // 分配內存空間
        byte[] buffer = new byte[4096];
        while (is.read(buffer) != -1) {
            os.write(buffer);
        }
    } catch (Exception e) {
        logger.error(e);
    }
}

第一種是最簡單的,就是初始化一個輸入輸出流,然后在 JAVA 內部分配一塊 4096 字節(jié)的內存空間,然后不斷將文件寫入這個內存空間中,并輸出到指定文件。

但是需要注意的是,這樣的方式雖然簡單,但是它的數(shù)據(jù)流實際上是經過了 4 層傳輸?shù)?/p>

image.png

也就是我們的文件需要經過內核到我們 JAVA 虛擬機內部的內存 再到 內核的 socket 緩沖區(qū),再到文件。

3. mmap 內存映射的方式拷貝

protected void copyFile(File source, File target) {
    try (FileInputStream is = new FileInputStream(source);
         FileOutputStream os = new FileOutputStream(target);
         FileChannel ic = is.getChannel();
         FileChannel oc = os.getChannel();) {
        // 這里開辟的內存直接映射在內核中
        ByteBuffer buffer = ByteBuffer.allocateDirect(4096);
        while (ic.read(buffer) != -1) {
            buffer.flip();
            oc.write(buffer);
            buffer.clear();
        }
    } catch (Exception e) {
        logger.error(e);
    }
}

第二種由于直接將內存映射在了堆外,也就可以節(jié)省普通拷貝中第二步的過程,即不在需要將內核緩沖區(qū)中的內容再讀到給 java 虛擬機分配的內存中了,比較適合需要 JAVA程序進行文件處理,或者一些小文件的傳輸

image.png

或者也可以通過封裝好的

new RandomAccessFile(file, "r").getChannel().map(FileChannel.MapMode.READ_ONLY, 0, 1024);

來實現(xiàn),底層是通過 directByteBufferConstructor.newInstance 分配堆外內存來實現(xiàn)的。

4. 零拷貝 sendFile 方式實現(xiàn)

protected void copyFile(File source, File target) {
    try {
        if (!target.exists()) {
            target.createNewFile();
        }
    } catch (Exception e) {
        logger.error(e);
    }
    try (FileChannel is = new RandomAccessFile(source, "r").getChannel();
        FileChannel os = new RandomAccessFile(target, "rw").getChannel()) {
        is.transferTo(0, source.length(), os);
    } catch (Exception e) {
        logger.error(e);
    }
}

第三種其實也就是我們俗稱的零拷貝的方式,在 Linux 2.1 版本中,引入了 sendFile 方法,也就是可以跳過用戶空間直接實現(xiàn)傳輸,java 程序中通過 新io 中 file 的 transformTo 方法,底層調用 liunx 內核級的 sendFile 方法,將內核數(shù)據(jù)直接拷貝到了 socket 緩沖區(qū),從而節(jié)省了拷貝次數(shù)和消耗。

image.png

既然第三種方式相對于第一種和第二種來說,可以完全不經過 java 應用程序,為什么不都直接都用第三種就好了呢?

正是因為它完全不經過 java 程序,也就是說我們無法對文件內容進行二次修改了,第三種方式比較適用于我們將無需經過程序處理的大文件。

需要注意的是,之所以會有程序的內存空間和內核的內存空間的區(qū)別,其實主要就是為了隔離,防止惡意程序可以直接訪問內核的內存空間

5. 多線程的方式實現(xiàn)拷貝

// 定義一個線程的數(shù)量
private Integer threadCount = 5;

protected void copyFile(File source, File target) {
    long workLoad = source.length() / threadCount;
    for (Integer i = 0; i < threadCount; i++) {
        ThreadFileRunnable threadFileRunnable = new ThreadFileRunnable(source, target, i * workLoad, workLoad);
        new Thread(threadFileRunnable, "copy-thread" + i).start();
    }
}

private class ThreadFileRunnable implements Runnable {
    private File source;
    private File target;
    // 定義每個線程開始復制時跳過的字節(jié)長度和工作負載大小
    private long skipLen;
    private long workLoad;
    // 定義IO操作的單位大小,這里設置為1024字節(jié)
    private final int IO_UNIT = 1024;

    public ThreadFileRunnable (File source, File target, long skipLen, long workLoad) {
        this.source = source;
        this.target = target;
        this.skipLen = skipLen;
        this.workLoad = workLoad;
    }

    @Override
    public void run() {
        try {
            try (FileInputStream is = new FileInputStream(this.source);
                 BufferedInputStream bis = new BufferedInputStream(is);
                 // 創(chuàng)建目標文件的RandomAccessFile,以讀寫模式打開
                 RandomAccessFile rof = new RandomAccessFile(this.target, "rw");) {
                // 跳過指定偏移量
                bis.skip(this.skipLen);
                // 將讀寫指針移動到指定偏移量
                rof.seek(this.skipLen);
                byte[] bytes = new byte[IO_UNIT];
                // 計算需要進行的IO操作次數(shù)
                long io_num = this.workLoad / IO_UNIT + 1;
                // 如果工作負載大小能被IO_UNIT整除,則IO操作次數(shù)減1
                if (this.workLoad % IO_UNIT == 0) {
                    io_num--;
                }
                int count = bis.read(bytes);
                while (io_num != 0) {
                    rof.write(bytes,0,count);
                    count = bis.read(bytes,0,count);
                    io_num--;
                }
            }
        } catch (Exception e) {
            // 捕獲并打印異常信息
            e.printStackTrace();
        }
    }
}

第四種如果文件特別大的時候,我們還可以通過多線程的方式來進行文件的讀寫,可以充分利用 CPU 多核效率來進一步提升文件的處理效率。

5. 總結

對于 JAVA 文件拷貝來說,本文只是展示和介紹了冰山一角,實際上對于讀寫流操作,操作系統(tǒng)的實現(xiàn)經過了長時間的演化,從 CPU 中斷pagecache,從 sendFileDMA,以及網絡傳輸過程中的 bio nio pollepoll,操作系統(tǒng)經過很多年的演化其中文件和網絡的傳輸處理的復雜程度可想而知。

我想我們可以通過一些小的點管中窺豹,了解一些基礎的知識,不用太深入,也能對日常的開發(fā)工作和面試有一定幫助。

參考鏈接 Linux 中的零拷貝

到此這篇關于JAVA 拷貝文件的幾種方式的文章就介紹到這了,更多相關JAVA 拷貝文件的幾種方式內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • 基于springboot實現(xiàn)一個簡單的aop實例

    基于springboot實現(xiàn)一個簡單的aop實例

    這篇文章主要介紹了基于springboot實現(xiàn)一個簡單的aop,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-11-11
  • Java原子類中的AtomicInteger類詳解

    Java原子類中的AtomicInteger類詳解

    這篇文章主要介紹了Java原子類中的AtomicInteger類詳解,原子類可以保證對"變量"操作的,原子性、有序性、可見性,我們可以通過AtomicInteger類,來看看它們是怎樣工作的,需要的朋友可以參考下
    2023-10-10
  • 深入理解java代碼實現(xiàn)分治算法

    深入理解java代碼實現(xiàn)分治算法

    分治算法是一種遞歸算法,它將問題劃分為幾個獨立的子問題,然后遞歸地解決這些子問題,最后將子問題的解合并起來得到原問題的解,本文詳細的介紹java分治算法,感興趣的可以了解一下
    2023-09-09
  • 使用EasyExcel實現(xiàn)簡單的Excel表格解析操作

    使用EasyExcel實現(xiàn)簡單的Excel表格解析操作

    這篇文章主要介紹了如何使用EasyExcel完成簡單的表格解析操作,同時實現(xiàn)了大量數(shù)據(jù)情況下數(shù)據(jù)的分次批量入庫,并記錄每條數(shù)據(jù)入庫的狀態(tài),感興趣的可以了解下
    2025-03-03
  • MyBatis源碼剖析之Mapper代理方式詳解

    MyBatis源碼剖析之Mapper代理方式詳解

    這篇文章主要為大家詳細介紹了MyBatis中Mapper代理的方式,文中將通過源碼為大家進行詳細的剖析,感興趣的小伙伴可以跟隨小編一起學習一下
    2022-07-07
  • RocketMQ生產消息與消費消息超詳細講解

    RocketMQ生產消息與消費消息超詳細講解

    這篇文章主要介紹了RocketMQ生產消息與消費消息,RocketMQ可用于以三種方式發(fā)送消息:可靠的同步、可靠的異步和單向傳輸。前兩種消息類型是可靠的,因為無論它們是否成功發(fā)送都有響應
    2022-12-12
  • Java數(shù)據(jù)結構及算法實例:快速計算二進制數(shù)中1的個數(shù)(Fast Bit Counting)

    Java數(shù)據(jù)結構及算法實例:快速計算二進制數(shù)中1的個數(shù)(Fast Bit Counting)

    這篇文章主要介紹了Java數(shù)據(jù)結構及算法實例:快速計算二進制數(shù)中1的個數(shù)(Fast Bit Counting),本文直接給出實現(xiàn)代碼,代碼中包含詳細注釋,需要的朋友可以參考下
    2015-06-06
  • Java 面試題基礎知識集錦

    Java 面試題基礎知識集錦

    本文主要介紹Java基礎面試題集錦,這里整理了面試java工程師的基礎知識題錦,有需要的小伙伴可以參考下
    2016-09-09
  • Linux下java環(huán)境配置圖文方法

    Linux下java環(huán)境配置圖文方法

    這篇文章主要介紹了Linux下java環(huán)境配置圖文方法,需要的朋友可以參考下
    2023-06-06
  • 解決kafka消息堆積及分區(qū)不均勻的問題

    解決kafka消息堆積及分區(qū)不均勻的問題

    這篇文章主要介紹了解決kafka消息堆積及分區(qū)不均勻的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-09-09

最新評論