热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

高效处理大文件:单线程与多线程下的词频统计方法

本文探讨了在处理大文件时,如何通过单线程和多线程的方式使用Buffer流进行词频统计,以避免一次性加载文件导致的内存溢出问题,并提供了具体的实现代码。

一、背景介绍

当处理较小的文件时,可以直接将其加载到内存中进行处理。然而,对于大文件,如果尝试一次性加载全部内容,将会遇到内存不足的问题。例如,对于一个4GB的文本文件,尝试一次性读取会导致程序崩溃,如下图所示:

为了解决这一问题,我们需要采用分块读取的方法,逐步处理文件内容,避免内存溢出。

二、解决方案

2.1 单线程处理方案

单线程处理大文件时,可以使用缓冲流(BufferedInputStream)逐块读取文件内容,并在每次读取后立即进行词频统计。这种方式虽然简单,但效率较低,尤其是在处理非常大的文件时。下面是一个简单的实现示例:

public void singleThreadWordCount() throws IOException { BufferedInputStream in = new BufferedInputStream(new FileInputStream("word")); byte[] buf = new byte[4 * 1024]; int len = 0; Map total = new HashMap<>(); long start = System.currentTimeMillis(); while ((len = in.read(buf)) != -1) { byte[] bytes = Arrays.copyOfRange(buf, 0, len); String str = new String(bytes); StringTokenizer stringTokenizer = new StringTokenizer(str); while (stringTokenizer.hasMoreTokens()) { String strTemp = stringTokenizer.nextToken(); total.put(strTemp, total.getOrDefault(strTemp, 0) + 1); } } System.out.println(total.get("aabaa")); System.out.println("time: " + (System.currentTimeMillis() - start) + "ms"); }

上述代码展示了如何使用缓冲流逐块读取文件并进行词频统计。测试结果显示,单线程处理4GB文件的时间约为81740毫秒,统计到的词频次数为319783次。

2.2 多线程处理方案

为了提高处理大文件的效率,可以采用多线程的方式。通过将文件分成多个部分,每个部分由不同的线程独立处理,最后汇总各个线程的统计结果。这种方式可以显著提升处理速度。下面是一个多线程处理的示例:

public class MultiThreadWordCount { private static final ForkJoinPool pool = ForkJoinPool.commonPool(); public void run(String fileName, long chunkSize) throws ExecutionException, InterruptedException { File file = new File(fileName); long fileSize = file.length(); long position = 0; long start = System.currentTimeMillis(); ArrayList>> tasks = new ArrayList<>(); while (position > future = pool.submit(task); tasks.add(future); } HashMap totalMap = new HashMap<>(); for (Future> task : tasks) { HashMap map = task.get(); for (Map.Entry entry : map.entrySet()) { if (totalMap.containsKey(entry.getKey())) { totalMap.put(entry.getKey(), totalMap.get(entry.getKey()) + entry.getValue()); } else { totalMap.put(entry.getKey(), entry.getValue()); } } } System.out.println("time: " + (System.currentTimeMillis() - start) + "ms"); System.out.println(totalMap.get("aabaa")); } private static class CountTask implements Callable> { private final long start; private final long end; private final String fileName; public CountTask(String fileName, long start, long end) { this.start = start; this.end = end; this.fileName = fileName; } @Override public HashMap call() throws Exception { HashMap map = new HashMap<>(); FileChannel channel = new RandomAccessFile(this.fileName, "rw").getChannel(); MappedByteBuffer mbuf = channel.map(FileChannel.MapMode.READ_ONLY, this.start, this.end - this.start); String str = StandardCharsets.US_ASCII.decode(mbuf).toString(); StringTokenizer stringTokenizer = new StringTokenizer(str); while (stringTokenizer.hasMoreTokens()) { String strTemp = stringTokenizer.nextToken(); map.put(strTemp, map.getOrDefault(strTemp, 0) + 1); } return map; } } @Test public void testMultiThreadWordCount() throws ExecutionException, InterruptedException { MultiThreadWordCount counter = new MultiThreadWordCount(); counter.run("word", 1024 * 1024); } }

测试结果显示,多线程处理4GB文件的时间为115909毫秒,统计到的词频次数为320106次。尽管多线程处理的时间略长于单线程,但这主要是因为在合并各个线程的统计结果时存在一定的开销。通过调整分块大小,可以进一步优化性能。例如,将分块大小增加到20MB时,处理时间减少到26671毫秒,词频次数为320107次。

读者可以尝试调整分块大小,观察其对性能的影响。


推荐阅读
  • 从 .NET 转 Java 的自学之路:IO 流基础篇
    本文详细介绍了 Java 中的 IO 流,包括字节流和字符流的基本概念及其操作方式。探讨了如何处理不同类型的文件数据,并结合编码机制确保字符数据的正确读写。同时,文中还涵盖了装饰设计模式的应用,以及多种常见的 IO 操作实例。 ... [详细]
  • 本文详细介绍了Java编程语言中的核心概念和常见面试问题,包括集合类、数据结构、线程处理、Java虚拟机(JVM)、HTTP协议以及Git操作等方面的内容。通过深入分析每个主题,帮助读者更好地理解Java的关键特性和最佳实践。 ... [详细]
  • 本文深入探讨了 Java 中的 Serializable 接口,解释了其实现机制、用途及注意事项,帮助开发者更好地理解和使用序列化功能。 ... [详细]
  • 本文详细解析了Python中的os和sys模块,介绍了它们的功能、常用方法及其在实际编程中的应用。 ... [详细]
  • 本文探讨了如何优化和正确配置Kafka Streams应用程序以确保准确的状态存储查询。通过调整配置参数和代码逻辑,可以有效解决数据不一致的问题。 ... [详细]
  • 2023年京东Android面试真题解析与经验分享
    本文由一位拥有6年Android开发经验的工程师撰写,详细解析了京东面试中常见的技术问题。涵盖引用传递、Handler机制、ListView优化、多线程控制及ANR处理等核心知识点。 ... [详细]
  • 并发编程:深入理解设计原理与优化
    本文探讨了并发编程中的关键设计原则,特别是Java内存模型(JMM)的happens-before规则及其对多线程编程的影响。文章详细介绍了DCL双重检查锁定模式的问题及解决方案,并总结了不同处理器和内存模型之间的关系,旨在为程序员提供更深入的理解和最佳实践。 ... [详细]
  • MySQL索引详解与优化
    本文深入探讨了MySQL中的索引机制,包括索引的基本概念、优势与劣势、分类及其实现原理,并详细介绍了索引的使用场景和优化技巧。通过具体示例,帮助读者更好地理解和应用索引以提升数据库性能。 ... [详细]
  • 最近团队在部署DLP,作为一个技术人员对于黑盒看不到的地方还是充满了好奇心。多次咨询乙方人员DLP的算法原理是什么,他们都以商业秘密为由避而不谈,不得已只能自己查资料学习,于是有了下面的浅见。身为甲方,虽然不需要开发DLP产品,但是也有必要弄明白DLP基本的原理。俗话说工欲善其事必先利其器,只有在懂这个工具的原理之后才能更加灵活地使用这个工具,即使出现意外情况也能快速排错,越接近底层,越接近真相。根据DLP的实际用途,本文将DLP检测分为2部分,泄露关键字检测和近似重复文档检测。 ... [详细]
  • 在多线程编程环境中,线程之间共享全局变量可能导致数据竞争和不一致性。为了解决这一问题,Linux提供了线程局部存储(TLS),使每个线程可以拥有独立的变量副本,确保线程间的数据隔离与安全。 ... [详细]
  • 在 Flutter 开发过程中,开发者经常会遇到 Widget 构造函数中的可选参数 Key。对于初学者来说,理解 Key 的作用和使用场景可能是一个挑战。本文将详细探讨 Key 的概念及其应用场景,并通过实例帮助你更好地掌握这一重要工具。 ... [详细]
  • 不确定性|放入_华为机试题 HJ9提取不重复的整数
    不确定性|放入_华为机试题 HJ9提取不重复的整数 ... [详细]
  • 在软件开发过程中,MD5加密是一种常见的数据保护手段。本文将详细介绍如何在C#中使用两种不同的方式来实现MD5加密:字符串加密和流加密。 ... [详细]
  • 深入解析Java枚举及其高级特性
    本文详细介绍了Java枚举的概念、语法、使用规则和应用场景,并探讨了其在实际编程中的高级应用。所有相关内容已收录于GitHub仓库[JavaLearningmanual](https://github.com/Ziphtracks/JavaLearningmanual),欢迎Star并持续关注。 ... [详细]
  • 本文介绍了一种从与src同级的config目录中读取属性文件内容的方法。通过使用Java的Properties类和InputStream,可以轻松加载并获取指定键对应的值。 ... [详细]
author-avatar
知知亦不知_710
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有