最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java高效地分割文本文件的方法技巧

 更新時間:2025年05月13日 08:29:57   作者:提前退休的java猿  
這篇文章介紹了Java中零拷貝技術(shù)的原理和應(yīng)用,通過比較傳統(tǒng)方法和零拷貝方法的性能,展示了如何使用FileChannel的transferTo方法高效地分割大型文本文件,特別是在保持行完整性的同時,顯著提高了處理速度,需要的朋友可以參考下

前言

之前聽到零拷貝的技術(shù),都感覺好高深好遙遠呀??

都是看什么什么框架用了零拷貝技術(shù),比如netty就使用零拷貝技術(shù)。

看到一篇文章讓我對接零拷貝技術(shù)去魅了,原來我也可以再工作中去使用零拷貝技術(shù),今天把這篇文章分享給大家

低效常用示例

當(dāng)我們面臨將文本文件分成最大大小塊的時,我們可能會嘗試編寫如下代碼:

    private static final long maxFileSizeBytes = 10 * 1024 * 1024; // 默認(rèn)10MB


    public void split(Path inputFile, Path outputDir) throws IOException {
        if (!Files.exists(inputFile)) {
            throw new IOException("輸入文件不存在: " + inputFile);
        }
        if (Files.size(inputFile) == 0) {
            throw new IOException("輸入文件為空: " + inputFile);
        }

        Files.createDirectories(outputDir);

        try (BufferedReader reader = Files.newBufferedReader(inputFile)) {
            int fileIndex = 0;
            long currentSize = 0;
            BufferedWriter writer = null;
            try {
                writer = newWriter(outputDir, fileIndex++);

                String line;
                while ((line = reader.readLine()) != null) {
                byte[] lineBytes = (line + System.lineSeparator()).getBytes();
                if (currentSize + lineBytes.length > maxFileSizeBytes) {
                    if (writer != null) {
                        writer.close();
                    }
                    writer = newWriter(outputDir, fileIndex++);
                    currentSize = 0;
                }
                writer.write(line);
                writer.newLine();
                currentSize += lineBytes.length;
                }
            } finally {
                if (writer != null) {
                    writer.close();
                }
            }
        }
    }

    private BufferedWriter newWriter(Path dir, int index) throws IOException {
        Path filePath = dir.resolve("part_" + index + ".txt");
        return Files.newBufferedWriter(filePath);
    }

效率分析

此代碼在技術(shù)上是可以的,但是將大文件拆分為多個塊的效率非常低。

它執(zhí)行許多堆分配 (行),導(dǎo)致創(chuàng)建和丟棄大量臨時對象 (字符串、字節(jié)數(shù)組) 。
還有一個不太明顯的問題,它將數(shù)據(jù)復(fù)制到多個緩沖區(qū),并在用戶和內(nèi)核模式之間執(zhí)行上下文切換。

具體如下:

BufferedReader: BufferedReader 的 BufferedReader 中:

  • 在底層 FileReaderInputStreamReader 上調(diào)用 read()
  • 數(shù)據(jù)從內(nèi)核空間用戶空間緩沖區(qū)復(fù)制。
  • 然后解析為 Java 字符串(堆分配)。

getBytes() : getBytes()

  • String 轉(zhuǎn)換為新的 byte[] 更多的堆分配。

BufferedWriter: BufferedWriter 的 BufferedWriter 中:

  • 從用戶空間獲取 byte/char 數(shù)據(jù)。
  • 調(diào)用 write()這又涉及將用戶空間復(fù)制到內(nèi)核空間。
  • 最終刷新到磁盤。

因此,數(shù)據(jù)在內(nèi)核和用戶空間之間來回移動多次,并產(chǎn)生額外的堆改動。除了垃圾收集壓力外,它還具有以下后果:

  • 內(nèi)存帶寬浪費在緩沖區(qū)之間進行復(fù)制。
  • 磁盤到磁盤傳輸?shù)?CPU 利用率較高。
  • 操作系統(tǒng)本可直接處理批量拷貝(通過DMA或優(yōu)化I/O),但Java代碼通過引入用戶空間邏輯攔截了這種高效性。

高效處理方案

那么,我們?nèi)绾伪苊馍鲜鰡栴}呢?

答案是盡可能使用 zero copy,即盡可能避免離開 kernel 空間。這可以通過使用 FileChannel 方法 long transferTo(long position, long count, WritableByteChannel target) 在 java 中完成。它直接是磁盤到磁盤的傳輸,還會利用作系統(tǒng)的一些 IO 優(yōu)化。

有問題就是所描述的方法對字節(jié)塊進行作,可能會破壞行的完整性。為了解決這個問題,我們需要一種策略來確保即使通過移動字節(jié)段處理文件時,行也保持完整

沒有上述的問題就很容易,只需為每個塊調(diào)用 transferTo,將position遞增為 position = position + maxFileSize,直到無法傳輸更多數(shù)據(jù)。

為了保持行的完整性,我們需要確定每個字節(jié)塊中最后一個完整行的結(jié)尾。為此,我們首先查找 chunk 的預(yù)期末尾,然后向后掃描以找到前面的換行符。這將為我們提供 chunk 的準(zhǔn)確字節(jié)計數(shù),確保包含最后的、不間斷的行。這將是執(zhí)行緩沖區(qū)分配和復(fù)制的代碼的唯一部分,并且由于這些作應(yīng)該最小,因此預(yù)計性能影響可以忽略不計。

private static final int LINE_ENDING_SEARCH_WINDOW = 8 * 1024;
?
private long maxSizePerFileInBytes;
private Path outputDirectory;
private Path tempDir;
?
private void split(Path fileToSplit) throws IOException {
    try (RandomAccessFile raf = new RandomAccessFile(fileToSplit.toFile(), "r");
            FileChannel inputChannel = raf.getChannel()) {
?
        long fileSize = raf.length();
        long position = 0;
        int fileCounter = 1;
?
        while (position < fileSize) {
            // Calculate end position (try to get close to max size)
            long targetEndPosition = Math.min(position + maxSizePerFileInBytes, fileSize);
?
            // If we're not at the end of the file, find the last line ending before max size
            long endPosition = targetEndPosition;
            if (endPosition < fileSize) {
                endPosition = findLastLineEndBeforePosition(raf, position, targetEndPosition);
            }
?
            long chunkSize = endPosition - position;
            var outputFilePath = tempDir.resolve("_part" + fileCounter);
            try (FileOutputStream fos = new FileOutputStream(outputFilePath.toFile());
                    FileChannel outputChannel = fos.getChannel()) {
                inputChannel.transferTo(position, chunkSize, outputChannel);
            }
?
            position = endPosition;
            fileCounter++;
        }
?
    }
}
?
private long findLastLineEndBeforePosition(RandomAccessFile raf, long startPosition, long maxPosition)
        throws IOException {
    long originalPosition = raf.getFilePointer();
?
    try {
        int bufferSize = LINE_ENDING_SEARCH_WINDOW;
        long chunkSize = maxPosition - startPosition;
?
        if (chunkSize < bufferSize) {
            bufferSize = (int) chunkSize;
        }
?
        byte[] buffer = new byte[bufferSize];
        long searchPos = maxPosition;
?
        while (searchPos > startPosition) {
            long distanceToStart = searchPos - startPosition;
            int bytesToRead = (int) Math.min(bufferSize, distanceToStart);
?
            long readStartPos = searchPos - bytesToRead;
            raf.seek(readStartPos);
?
            int bytesRead = raf.read(buffer, 0, bytesToRead);
            if (bytesRead <= 0)
                break;
?
            // Search backwards through the buffer for newline
            for (int i = bytesRead - 1; i >= 0; i--) {
                if (buffer[i] == '\n') {
                    return readStartPos + i + 1;
                }
            }
?
            searchPos -= bytesRead;
        }
?
        throw new IllegalArgumentException(
                "File " + fileToSplit + " cannot be split. No newline found within the limits.");
    } finally {
        raf.seek(originalPosition);
    }
}

findLastLineEndBeforePosition 方法具有某些限制。具體來說,它僅適用于類 Unix 系統(tǒng) (\n),非常長的行可能會導(dǎo)致大量向后讀取迭代,并且包含超過 maxSizePerFileInBytes 的行的文件無法拆分。但是,它非常適合拆分訪問日志文件等場景,這些場景通常具有短行和大量條目。

性能分析

理論上,我們zero copy拆分文件應(yīng)該【常用方式】更快,現(xiàn)在是時候衡量它能有多快了。為此,我為這兩個實現(xiàn)運行了一些基準(zhǔn)測試,這些是結(jié)果。

Benchmark                                                    Mode  Cnt           Score      Error   Units
FileSplitterBenchmark.splitFile                              avgt   15        1179.429 ±   54.271   ms/op
FileSplitterBenchmark.splitFile:·gc.alloc.rate               avgt   15        1349.613 ±   60.903  MB/sec
FileSplitterBenchmark.splitFile:·gc.alloc.rate.norm          avgt   15  1694927403.481 ± 6060.581    B/op
FileSplitterBenchmark.splitFile:·gc.count                    avgt   15         718.000             counts
FileSplitterBenchmark.splitFile:·gc.time                     avgt   15         317.000                 ms
FileSplitterBenchmark.splitFileZeroCopy                      avgt   15          77.352 ±    1.339   ms/op
FileSplitterBenchmark.splitFileZeroCopy:·gc.alloc.rate       avgt   15          23.759 ±    0.465  MB/sec
FileSplitterBenchmark.splitFileZeroCopy:·gc.alloc.rate.norm  avgt   15     2555608.877 ± 8644.153    B/op
FileSplitterBenchmark.splitFileZeroCopy:·gc.count            avgt   15          10.000             counts
FileSplitterBenchmark.splitFileZeroCopy:·gc.time             avgt   15           5.000                 ms

以下是用于上述結(jié)果的基準(zhǔn)測試代碼和文件大小(200+MB)。

int maxSizePerFileInBytes = 1024 * 1024 // 1 MB chunks
?
public void setup() throws Exception {
    inputFile = Paths.get("/tmp/large_input.txt");
    outputDir = Paths.get("/tmp/split_output");
    // Create a large file for benchmarking if it doesn't exist
    if (!Files.exists(inputFile)) {
        try (BufferedWriter writer = Files.newBufferedWriter(inputFile)) {
            for (int i = 0; i < 10_000_000; i++) {
                writer.write("This is line number " + i);
                writer.newLine();
            }
        }
    }
}
?
public void splitFile() throws Exception {
    splitter.split(inputFile, outputDir);
}
?
public void splitFileZeroCopy() throws Exception {
    zeroCopySplitter.split(inputFile);
}

zeroCopy表現(xiàn)出相當(dāng)大的加速,僅用了 77 毫秒,而對于這種特定情況,【常用方式】需要 1179 毫秒。在處理大量數(shù)據(jù)或許多文件時,這種性能優(yōu)勢可能至關(guān)重要。

結(jié)論

高效拆分大型文本文件需要系統(tǒng)級性能考慮,而不僅僅是邏輯。雖然基本方法突出了內(nèi)存作過多的問題,但重新設(shè)計的解決方案利用零拷貝技術(shù)并保持行完整性,可以顯著提高性能。

這證明了系統(tǒng)感知編程和理解 I/O 機制在創(chuàng)建更快、更節(jié)省資源的工具來處理大型文本數(shù)據(jù)(如日志或數(shù)據(jù)集)方面的影響。

以上就是Java高效地分割文本文件的方法技巧的詳細(xì)內(nèi)容,更多關(guān)于Java分割文本文件的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • SpringBoot在 POM 中引入本地 JAR 包的方法

    SpringBoot在 POM 中引入本地 JAR 包的方法

    在開發(fā) Spring Boot 應(yīng)用程序時,您可能需要使用本地 JAR 包來添加自定義庫或功能,本文將介紹在 Spring Boot 項目的 POM 文件中如何引入本地 JAR 包,感興趣的朋友跟隨小編一起看看吧
    2023-08-08
  • Java中使用Files類的copy()方法實現(xiàn)復(fù)制文件

    Java中使用Files類的copy()方法實現(xiàn)復(fù)制文件

    這篇文章主要介紹了Java中使用Files類的copy()方法實現(xiàn)復(fù)制文件,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2025-05-05
  • 深入探究Java線程的狀態(tài)與生命周期

    深入探究Java線程的狀態(tài)與生命周期

    在java中,任何對象都要有生命周期,線程也不例外,它也有自己的生命周期。線程的整個生命周期可以分為5個階段,分別是新建狀態(tài)、就緒狀態(tài)、運行狀態(tài)、阻塞狀態(tài)和死亡狀態(tài)
    2022-04-04
  • SpringBoot使用flyway初始化數(shù)據(jù)庫

    SpringBoot使用flyway初始化數(shù)據(jù)庫

    這篇文章主要介紹了SpringBoot如何使用flyway初始化數(shù)據(jù)庫,幫助大家更好的理解和學(xué)習(xí)使用SpringBoot框架,感興趣的朋友可以了解下
    2021-03-03
  • Maven resrouce下filtering的使用方法

    Maven resrouce下filtering的使用方法

    本文介紹了Maven的resource插件中的filtering功能,該功能用于在構(gòu)建過程中將資源目錄下的文件中的tokens進行參數(shù)替換,tokens的來源可以是pom文件中的properties屬性或外部的.properties文件,通過這種方式,可以靈活地切換不同開發(fā)環(huán)境下的配置屬性
    2024-11-11
  • Spring?Cloud?Ribbon?負(fù)載均衡使用策略示例詳解

    Spring?Cloud?Ribbon?負(fù)載均衡使用策略示例詳解

    Spring?Cloud?Ribbon?是基于Netflix?Ribbon?實現(xiàn)的一套客戶端負(fù)載均衡工具,Ribbon客戶端組件提供了一系列的完善的配置,如超時,重試等,這篇文章主要介紹了Spring?Cloud?Ribbon?負(fù)載均衡使用策略示例詳解,需要的朋友可以參考下
    2023-03-03
  • SpringBoot的啟動過程源碼詳細(xì)分析

    SpringBoot的啟動過程源碼詳細(xì)分析

    這篇文章主要介紹了SpringBoot的啟動過程源碼詳細(xì)分析,SpringBoot啟動的時候,會構(gòu)造一個SpringApplication的實例,構(gòu)造SpringApplication的時候會進行初始化的工作,需要的朋友可以參考下
    2023-11-11
  • java對數(shù)據(jù)庫更新的操作方式及注意事項

    java對數(shù)據(jù)庫更新的操作方式及注意事項

    這篇文章主要介紹了java對數(shù)據(jù)庫更新的操作方式及注意事項,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2025-04-04
  • 一文詳解JAVA中InputStreamReader流

    一文詳解JAVA中InputStreamReader流

    本文主要介紹了一文詳解JAVA中InputStreamReader流,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-04-04
  • 解決反射調(diào)用方法時獲取bean失敗的問題

    解決反射調(diào)用方法時獲取bean失敗的問題

    文章描述了通過反射機制調(diào)用類方法時遇到的@Autowired注入失敗和事務(wù)回滾失敗的問題,原因是反射生成的對象未被SpringIOC容器管理,解決方案是通過applicationContext.getBean("className")方法獲取Spring管理的bean來解決注入和事務(wù)問題
    2025-10-10

最新評論

陕西省| 印江| 四子王旗| 浪卡子县| 吐鲁番市| 蓬溪县| 彰武县| 资阳市| 吴江市| 银川市| 苏尼特右旗| 苏尼特左旗| 井研县| 榆中县| 清河县| 旌德县| 宁阳县| 临西县| 靖江市| 樟树市| 天柱县| 会东县| 本溪| 泰安市| 香格里拉县| 乐昌市| 宜兰市| 防城港市| 黔东| 阜新市| 桃园市| 健康| 开平市| 遂川县| 克东县| 克山县| 建昌县| 棋牌| 大关县| 衡阳市| 平南县|