【类拷贝文件的运用】

常用示例

当我们面临将文本文件分成最大大小块的时,我们可能会尝试编写如下代码:

public class TestSplit {private static final long maxFileSizeBytes = 10 * 1024 * 1024; // 默认10MBpublic 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);}public static void main(String[] args) {String inputFilePath = "C:\Users\fei\Desktop\testTwo.txt";String outputDirPath = "C:\Users\fei\Desktop\testTwo";TestSplit splitter = new TestSplit();try {long startTime = System.currentTimeMillis();splitter.split(Paths.get(inputFilePath), Paths.get(outputDirPath));long endTime = System.currentTimeMillis();long duration = endTime - startTime;System.out.println("文件拆分完成!");System.out.printf("总耗时:%d 毫秒%n", duration);} catch (IOException e) {System.out.println("文件拆分过程中发生错误:" + e.getMessage());}}
}

效率分析

此代码在技术上是可以的,但是将大文件拆分为多个块的效率非常低。具体如下

  1. 它执行许多堆分配 (行),导致创建和丢弃大量临时对象 (字符串、字节数组) 。
  2. 还有一个不太明显的问题,它将数据复制到多个缓冲区,并在用户和内核模式之间执行上下文切换。

代码详细分析

BufferedReader: BufferedReader 的 BufferedReader 中:

  • 在底层 FileReaderInputStreamReader 上调用 read()
  • 数据从内核空间用户空间缓冲区复制。
  • 然后解析为 Java 字符串(堆分配)。

getBytes() : getBytes()

  • String 转换为新的 byte[] →更多的堆分配。

BufferedWriter: BufferedWriter 的 BufferedWriter 中:

  • 从用户空间获取 byte/char 数据。
  • 调用 write()这又涉及将用户空间复制到内核空间→。
  • 最终刷新到磁盘。

因此,数据在内核和用户空间之间来回移动多次,并产生额外的堆改动。除了垃圾收集压力外,它还具有以下后果:

  • 内存带宽浪费在缓冲区之间进行复制。
  • 磁盘到磁盘传输的 CPU 利用率较高。
  • 操作系统本可直接处理批量拷贝(通过DMA或优化I/O),但Java代码通过引入用户空间逻辑拦截了这种高效性。

方案

那么,我们如何避免上述问题呢?

答案是尽可能使用 zero copy,即尽可能避免离开 kernel 空间。这可以通过使用 FileChannel 方法 long transferTo(long position, long count, WritableByteChannel target) 在 java 中完成。它直接是磁盘到磁盘的传输,还会利用作系统的一些 IO 优化。

有问题就是所描述的方法对字节块进行作,可能会破坏行的完整性。为了解决这个问题,我们需要一种策略来确保即使通过移动字节段处理文件时,行也保持完整

没有上述的问题就很容易,只需为每个块调用 transferTo,将position递增为 position = position + maxFileSize,直到无法传输更多数据。

为了保持行的完整性,我们需要确定每个字节块中最后一个完整行的结尾。为此,我们首先查找 chunk 的预期末尾,然后向后扫描以找到前面的换行符。这将为我们提供 chunk 的准确字节计数,确保包含最后的、不间断的行。这将是执行缓冲区分配和复制的代码的唯一部分,并且由于这些作应该最小,因此预计性能影响可以忽略不计。

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 sizelong 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 newlinefor (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 系统 (\n),非常长的行可能会导致大量向后读取迭代,并且包含超过 maxSizePerFileInBytes 的行的文件无法拆分。但是,它非常适合拆分访问日志文件等场景,这些场景通常具有短行和大量条目。

性能分析

理论上,我们zero copy拆分文件应该【常用方式】更快,现在是时候衡量它能有多快了。为此,我为这两个实现运行了一些基准测试,这些是结果。

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

以下是用于上述结果的基准测试代码和文件大小。

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 existif (!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表现出相当大的加速,仅用了 77 毫秒,而对于这种特定情况,【常用方式】需要 1179 毫秒。在处理大量数据或许多文件时,这种性能优势可能至关重要。

结论

高效拆分大型文本文件需要系统级性能考虑,而不仅仅是逻辑。虽然基本方法突出了内存作过多的问题,但重新设计的解决方案利用零拷贝技术并保持行完整性,可以显著提高性能。

这证明了系统感知编程和理解 I/O 机制在创建更快、更节省资源的工具来处理大型文本数据(如日志或数据集)方面的影响。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/82707.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/82707.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

打破产品思维--被讨厌的勇气--实战5

课程&#xff1a;B站大学 记录产品经理实战项目系统性学习&#xff0c;从产品思维&#xff0c;用户画像&#xff0c;用户体验&#xff0c;增长数据驱动等不同方向理解产品&#xff0c;从0到1去理解产品从需求到落地的全过程&#xff0c;测试左移方向&#xff08;靠近需求、设计…

【Autosar SecOC 1.信息安全原理介绍】

这里写目录标题 1 背景2 了解黑客攻击原理3 SecOC实现数据的真实性与完整性校验3.1 数据身份验证完成真实性验证3.2 防止重放攻击 1 背景 在今天的车载网络中&#xff0c;大部分数据传输是在没有任何特殊安全措施的情况下进行的。因此&#xff0c;一旦能够直接访问车辆的总线&a…

基于SpringBoot的校园周边美食探索及分享平台【附源码+数据库+文档下载】

一、项目简介 本项目是一个基于 SpringBoot Vue 的校园周边美食探索与分享平台&#xff0c;专为在校大学生开发&#xff0c;集美食推荐、好友互动、收藏分享于一体。 通过平台&#xff0c;用户可以探索学校周边的美食店铺、发布美食鉴赏、添加好友进行交流分享。同时&#x…

无偿帮写毕业论文

以下教程教你如何利用相关网站和AI免费帮你写一个毕业论文。毕竟毕业论文只要过就行&#xff0c;脱产学习这么多年&#xff0c;终于熬出头了&#xff0c;完成毕设后有空就去多看看亲人好友&#xff0c;祝好&#xff01; 一、找一个论文模板(最好是overleaf) 废话不多说&#…

15 个 Azure DevOps 场景化面试问题及解答

问题 1. 解释 Azure DevOps YAML 管道的典型结构。 您可以从管道的整体结构开始&#xff0c;从触发器开始。您也可以选择解释它可能包含的不同类型的阶段&#xff1a;构建、测试、扫描、部署等。 Azure DevOps YAML 管道结构示例 触发器指示管道运行。它可以是持续集成 (CI) 或…

Java 大视界 -- Java 大数据机器学习模型在元宇宙虚拟场景智能交互中的关键技术(239)

&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎来到 青云交的博客&#xff01;能与诸位在此相逢&#xff0c;我倍感荣幸。在这飞速更迭的时代&#xff0c;我们都渴望一方心灵净土&#xff0c;而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识&#xff0c;也…

本地不安装oracle,还想连oracle

1.首先要用navicat,或者toad打开连接数据库 2.安装oracle客户端&#xff0c;有时候OCI.dll需要看数据库版本&#xff0c;我们Oracle数据库是12C&#xff0c;可以用这个版本 3. 4.配置环境变量 变量名&#xff1a;NLS_LANG变量值&#xff1a;SIMPLIFIED CHINESE_CHINA.ZHS16GBK …

LabVIEW车牌自动识别系统

在智能交通快速发展的时代&#xff0c;车牌自动识别系统成为提升交通管理效率的关键技术。本案例详细介绍了基于 LabVIEW 平台&#xff0c;搭配大恒品牌相机构建的车牌自动识别系统&#xff0c;该系统在多个场景中发挥着重要作用&#xff0c;为交通管理提供了高效、精准的解决方…

deque底层数据结构以及和queue的异同

文章目录 底层数据结构原理关键组成部分操作效率与其他容器的对比适用场景C STL中的实现细节总结 deque和queue的异同相同点不同点 deque&#xff08;双端队列&#xff09;是一种具有高效两端插入和删除操作的数据结构&#xff0c;常见于C标准库&#xff08;STL&#xff09;和其…

WordPress 网站上的 jpg、png 和 WebP 图片插件

核心功能 1. 转换 AVIF 并压缩 AVIF 将您 WordPress 网站上的 jpg、png 和 WebP 图片转换为 AVIF 格式&#xff0c;并根据您设置的压缩级别压缩 AVIF 图片。如果原始图片已经是 WordPress 6.5 以上支持的 AVIF 格式&#xff0c;则原始 AVIF 图片将仅被压缩。 2. 转换 WebP 并…

Docker Volumes

Docker Volumes 是 Docker 提供的一种机制&#xff0c;用于持久化存储容器数据。与容器的生命周期不同&#xff0c;Volumes 可以独立存在&#xff0c;即使容器被删除&#xff0c;数据仍然保留。以下是关于 Docker Volumes 的详细说明&#xff1a; 1. 为什么需要 Volumes&#…

西电 | 2025年拟录取研究生个人档案录取通知书邮寄通知

各位考生&#xff1a; 我校2025年硕士研究生录取工作已结束&#xff0c;根据相关工作管理规定&#xff0c;现将个人档案转调及录取通知书邮寄信息确认等有关事宜通知如下&#xff1a; 一、个人档案转调 &#xff08;邮寄档案请务必使用EMS&#xff09; 1.全日制考生 录取类…

ExcelJS库的使用

ExcelJS 安装 npm install exceljs新的功能! Merged fix: styles rendering in case when “numFmt” is present in conditional formatting rules (resolves #1814) #1815. Many thanks to andreykrupskii for this contribution!Merged inlineStr cell type support #15…

时空注意力机制深度解析:理论、技术与应用全景

时空注意力机制作为深度学习领域的关键技术&#xff0c;通过捕捉数据在时间和空间维度上的依赖关系&#xff0c;显著提升了时序数据处理和时空建模能力。本文从理论起源、数学建模、网络架构、工程实现到行业应用&#xff0c;系统拆解时空注意力机制的核心原理&#xff0c;涵盖…

wxWidgets 3.2.8 发布,修复了GTK下,wxStaticText显示文本异常的问题

详细如下&#xff1a; 3.2.8 是稳定的 3.2 系列中的最新维护版本&#xff0c;现已在 GitHub 上提供&#xff0c;您可以从中下载带有 所选 Windows 的库源和文档以及二进制文件 编译器&#xff0c;例如 Microsoft Visual C、MinGW-w64 和 TDM-GCC。您还可以阅读更新的文档 版本&…

网页Web端无人机直播RTSP视频流,无需服务器转码,延迟300毫秒

随着无人机技术的飞速发展&#xff0c;全球无人机直播应用市场也快速扩张&#xff0c;从农业植保巡检到应急救援指挥&#xff0c;从大型活动直播到智慧城市安防&#xff0c;实时视频传输已成为刚需。预计到2025年&#xff0c;全球将有超过1000万架商用无人机搭载直播功能&#…

思维链框架:LLMChain,OpenAI,PromptTemplate

什么是思维链,怎么实现 目录 什么是思维链,怎么实现思维链(Chain of Thought)在代码中的实现方式1. 手动构建思维链提示2. 少样本思维链提示3. 自动思维链生成4. 思维链与工具使用结合5. 使用现有思维链框架:LLMChain,OpenAI,PromptTemplate思维链实现的关键要点思维链(C…

杰理强制烧录拨码开关

5.3. 工具拨码开关说明 — JL Project Documentation

智能手表关键技术评估报告

📘 智能手表关键技术评估报告 产品名称:Aurora Watch S1 智能手表 编写日期:2025年5月6日 版本号:v1.0 编写人:XXX(技术负责人) 一、报告目的 本报告旨在对智能手表核心技术模块进行全面评估,识别项目研发过程中可能存在的技术风险、供应链瓶颈和开发难点,并为架构…

基于RT-Thread驱动EEPROM_AD24C02

基于RT-Thread驱动EEPROM_AD24C02 前言一、硬件设计二、软件设计三、测试1、eeprom_test&#xff08;&#xff09;测试2、基础操作字节实验3、多字节读写 前言 存储容量2048位&#xff0c;内部组织256x8&#xff08;2K&#xff09;&#xff0c;即256个字节的存储单元&#xff…