百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

如何在Java中读取超过内存大小的文件

toyiye 2024-08-24 00:40 4 浏览 0 评论

读取文件内容,然后进行处理,在Java中我们通常利用 Files 类中的方法,将可以文件内容加载到内存,并流顺利地进行处理。但是,在一些场景下,我们需要处理的文件可能比我们机器所拥有的内存要大。此时,我们则需要采用另一种策略:部分读取它,并具有其他结构来仅编译所需的数据。

接下来,我们就来说说这一场景:当遇到大文件,无法一次载入内存时候要如何处理。

#模拟场景

假设,当前我们需要开发一个程序来分析来自服务器的日志文件,并生成一份报告,列出前 10 个最常用的应用程序。

每天,都会生成一个新的日志文件,其中包含时间戳、主机信息、持续时间、服务调用等信息,以及可能与我们的特定方案无关的其他数据。

2024-02-25T00:00:00.000+GMT host7 492 products 0.0.3 PUT 73.182.150.152 eff0fac5-b997-40a3-87d8-02ff2f397b44
2024-02-25T00:00:00.016+GMT host6 123 logout 2.0.3 GET 34.235.76.94 8b97acae-dd36-4e83-b423-12905a4ab38d
2024-02-25T00:00:00.033+GMT host6 50 payments/:id 0.4.6 PUT 148.241.146.59 ac3c9064-4782-46d9-a0b6-69e4d55a5b38
2024-02-25T00:00:00.050+GMT host2 547 orders 1.5.0 PUT 6.232.116.248 2285a81e-c511-41b9-b0ea-a475a0a45805
2024-02-25T00:00:00.067+GMT host4 400 suggestions 0.8.6 DELETE 149.138.227.154 8031b639-700e-4a7c-b257-fcbed0d029ce
2024-02-25T00:00:00.084+GMT host2 644 login 6.90 GET 208.158.145.204 3906a28c-56e4-4e5f-b548-591eab737aa7
2024-02-25T00:00:00.101+GMT host5 339 suggestions 0.8.9 PUT 173.109.21.97 c7dfec8a-5ca8-4d0d-b903-aaf65629fdd0
2024-02-25T00:00:00.118+GMT host9 87 products 2.6.3 POST 220.252.90.140 e5ceef67-2f0f-4c2d-a6d2-c698598aaef2
2024-02-25T00:00:00.134+GMT host0 845 products 9.4.6 GET 136.79.178.188 f28578c1-c37c-47a3-a473-4e65371e0245
2024-02-25T00:00:00.151+GMT host4 675 login 0.89 DELETE 32.159.65.239 d27ff353-e501-43e6-bdce-680d79a07c36

我们的代码将收到日志文件列表,我们的目标是编制一份报告,列出最常用的 10 个服务。但是,要包含在报告中,服务必须在提供的每个日志文件中至少有一个条目。简而言之,一项服务必须每天使用才有资格包含在报告中。

#基础实现

解决这个问题的最初方法是考虑业务需求并创建以下代码:

public void processFiles(final List<File> fileList) {
  final Map<LocalDate, List<LogLine>> fileContent = getFileContent(fileList);
  final List<String> serviceList = getServiceList(fileContent);
  final List<Statistics> statisticsList = getStatistics(fileContent, serviceList);
  final List<Statistics> topCalls = getTop10(statisticsList);

  print(topCalls);
}

该方法接收文件列表作为参数,核心流程如下:

  • 创建一个包含每个文件条目的映射,其中Key是 LocalDate,Value是文件行列表。
  • 使用所有文件中的唯一服务名称创建字符串列表。
  • 生成所有服务的统计信息列表,将文件中的数据组织到结构化地图中。
  • 筛选统计信息,获取排名前 10 的服务调用。
  • 打印结果。

可以注意到,这种方法将太多数据加载到内存中,不可避免地会导致 OutOfMemoryError

#改进实现

就如文章开头说的,我们需要采用另一种策略:逐行处理文件的模式。

private void processFiles(final List<File> fileList) {
  final Map<String, Counter> compiledMap = new HashMap<>();

  for (int i = 0; i < fileList.size(); i++) {
    processFile(fileList, compiledMap, i);
  }

  final List<Counter> topCalls =
      compiledMap.values().stream()
          .filter(Counter::allDaysSet)
          .sorted(Comparator.comparing(Counter::getNumberOfCalls).reversed())
          .limit(10)
          .toList();

  print(topCalls);
}
  • 首先,它声明一个Map(compiledMap),其中一个String作为键,代表服务名称,以及一个Counter对象(稍后解释),它将存储统计信息。
  • 接下来,它逐一处理这些文件并相应地更新compileMap。
  • 然后,它利用流功能来: 仅过滤具有全天数据的计数器;按调用次数排序;最后,检索前 10 名。

在看整个处理的核心processFile方法之前,我们先来分析一下Counter类,它在这个过程中也起到了至关重要的作用:

public class Counter {
  @Getter private String serviceName;
  @Getter private long numberOfCalls;
  private final BitSet daysWithCalls;

  public Counter(final String serviceName, final int numberOfDays) {
    this.serviceName = serviceName;
    this.numberOfCalls = 0L;
    daysWithCalls = new BitSet(numberOfDays);
  }

  public void add() {
    numberOfCalls++;
  }

  public void setDay(final int dayNumber) {
    daysWithCalls.set(dayNumber);
  }

  public boolean allDaysSet() {
    return daysWithCalls.stream()
        .mapToObj(index -> daysWithCalls.get(index))
        .reduce(Boolean.TRUE, Boolean::logicalAnd);
  }
}
  • 它包含三个属性:serviceName、numberOfCalls 和 daysWithCalls
  • numberOfCalls 属性通过 add 方法递增,该方法为 serviceName 的每个处理行调用。
  • daysWithCalls 属性是一个 Java BitSet,一种用于存储布尔属性的内存高效结构。它使用要处理的天数进行初始化,每个位代表一天,初始化为 false。
  • setDay 方法将 BitSet 中与给定日期位置相对应的位设置为 true。

allDaysSet 方法负责检查 BitSet 中的所有日期是否都设置为 true。它通过将 BitSet 转换为布尔流,然后使用逻辑 AND 运算符减少它来实现此目的。

private void processFile(final List<File> fileList, 
                         final Map<String, Counter> compiledMap, 
                         final int dayNumber) {
  try (Stream<String> lineStream = Files.lines(fileList.get(dayNumber).toPath())) {
    lineStream
        .map(this::toLogLine)
        .forEach(
            logLine -> {
              Counter counter = compiledMap.get(logLine.serviceName());
              if (counter == null) {
                counter = new Counter(logLine.serviceName(), fileList.size());
                compiledMap.put(logLine.serviceName(), counter);
              }
              counter.add();
              counter.setDay(dayNumber);
            });

  } catch (final IOException e) {
    throw new RuntimeException(e);
  }
}
  • 该过程使用Files类的lines方法逐行读取文件,并将其转换为流。这里的关键特征是lines方法是惰性的,这意味着它不会立即读取整个文件;相反,它会在流被消耗时读取文件。
  • toLogLine 方法将每个字符串文件行转换为具有用于访问日志行信息的属性的对象。
  • 处理文件行的主要过程比预期的要简单。它从与serviceName关联的compileMap中检索(或创建)Counter,然后调用Counter的add和setDay方法。

正如我们所看到的,在 Java 中处理大文件而不将整个文件加载到内存中并不是什么复杂的事情。 Files类提供了逐行处理文件的方法,我们还可以在文件处理过程中利用哈希来存储数据,这有助于节省内存。

相关推荐

# Python 3 # Python 3字典Dictionary(1)

Python3字典字典是另一种可变容器模型,且可存储任意类型对象。字典的每个键值(key=>value)对用冒号(:)分割,每个对之间用逗号(,)分割,整个字典包括在花括号({})中,格式如...

Python第八课:数据类型中的字典及其函数与方法

Python3字典字典是另一种可变容器模型,且可存储任意类型对象。字典的每个键值...

Python中字典详解(python 中字典)

字典是Python中使用键进行索引的重要数据结构。它们是无序的项序列(键值对),这意味着顺序不被保留。键是不可变的。与列表一样,字典的值可以保存异构数据,即整数、浮点、字符串、NaN、布尔值、列表、数...

Python3.9又更新了:dict内置新功能,正式版十月见面

机器之心报道参与:一鸣、JaminPython3.8的热乎劲还没过去,Python就又双叒叕要更新了。近日,3.9版本的第四个alpha版已经开源。从文档中,我们可以看到官方透露的对dic...

Python3 基本数据类型详解(python三种基本数据类型)

文章来源:加米谷大数据Python中的变量不需要声明。每个变量在使用前都必须赋值,变量赋值以后该变量才会被创建。在Python中,变量就是变量,它没有类型,我们所说的"类型"是变...

一文掌握Python的字典(python字典用法大全)

字典是Python中最强大、最灵活的内置数据结构之一。它们允许存储键值对,从而实现高效的数据检索、操作和组织。本文深入探讨了字典,涵盖了它们的创建、操作和高级用法,以帮助中级Python开发...

超级完整|Python字典详解(python字典的方法或操作)

一、字典概述01字典的格式Python字典是一种可变容器模型,且可存储任意类型对象,如字符串、数字、元组等其他容器模型。字典的每个键值key=>value对用冒号:分割,每个对之间用逗号,...

Python3.9版本新特性:字典合并操作的详细解读

处于测试阶段的Python3.9版本中有一个新特性:我们在使用Python字典时,将能够编写出更可读、更紧凑的代码啦!Python版本你现在使用哪种版本的Python?3.7分?3.5分?还是2.7...

python 自学,字典3(一些例子)(python字典有哪些基本操作)

例子11;如何批量复制字典里的内容2;如何批量修改字典的内容3;如何批量修改字典里某些指定的内容...

Python3.9中的字典合并和更新,几乎影响了所有Python程序员

全文共2837字,预计学习时长9分钟Python3.9正在积极开发,并计划于今年10月发布。2月26日,开发团队发布了alpha4版本。该版本引入了新的合并(|)和更新(|=)运算符,这个新特性几乎...

Python3大字典:《Python3自学速查手册.pdf》限时下载中

最近有人会想了,2022了,想学Python晚不晚,学习python有前途吗?IT行业行业薪资高,发展前景好,是很多求职群里严重的香饽饽,而要进入这个高薪行业,也不是那么轻而易举的,拿信工专业的大学生...

python学习——字典(python字典基本操作)

字典Python的字典数据类型是基于hash散列算法实现的,采用键值对(key:value)的形式,根据key的值计算value的地址,具有非常快的查取和插入速度。但它是无序的,包含的元素个数不限,值...

324页清华教授撰写【Python 3 菜鸟查询手册】火了,小白入门字典

如何入门学习python...

Python3.9中的字典合并和更新,了解一下

全文共2837字,预计学习时长9分钟Python3.9正在积极开发,并计划于今年10月发布。2月26日,开发团队发布了alpha4版本。该版本引入了新的合并(|)和更新(|=)运算符,这个新特性几乎...

python3基础之字典(python中字典的基本操作)

字典和列表一样,也是python内置的一种数据结构。字典的结构如下图:列表用中括号[]把元素包起来,而字典是用大括号{}把元素包起来,只不过字典的每一个元素都包含键和值两部分。键和值是一一对应的...

取消回复欢迎 发表评论:

请填写验证码