Java 读取大量txt文件时超出了GC开销限制

Java 读取大量txt文件时超出了GC开销限制,java,garbage-collection,bufferedreader,Java,Garbage Collection,Bufferedreader,我有一个20GB文件夹,其中包含358txt文件,共有733019372行,所有txt文件格式如下 77 clueweb12-0211wb-83-00000 88 clueweb12-0211wb-83-00001 82 clueweb12-0211wb-83-00002 82 clueweb12-0211wb-83-00003 64 clueweb12-0211wb-83-00004 80 clueweb12-0211wb-83-00005 83 clueweb12-0211wb-83-000

我有一个20GB文件夹,其中包含358txt文件,共有733019372行,所有txt文件格式如下

77 clueweb12-0211wb-83-00000
88 clueweb12-0211wb-83-00001
82 clueweb12-0211wb-83-00002
82 clueweb12-0211wb-83-00003
64 clueweb12-0211wb-83-00004
80 clueweb12-0211wb-83-00005
83 clueweb12-0211wb-83-00006
75 clueweb12-0211wb-83-00007
我的目的是当程序遍历所有txt文件时,逐行递归读取文件,每行分成两部分
(例如88和clueweb12-0211wb-83-0003)
,并将这些部分放入
LinkedHashMap
。之后,将用户提供的docID
(clueweb12-0211wb-83-00006)
作为参数,并将分数属于此docID
(83)
。如果遇到不存在的docID,则应将-1作为分数返回。例如:

clueweb12-0003wb-22-11553,foo,clueweb12-0109wb-78-15059,bar,clueweb12-0302wb-50-22339
应打印出:
84、-1,19、-1,79

我将用户的文件路径作为参数

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;


import static java.nio.file.FileVisitResult.CONTINUE;



public class App extends SimpleFileVisitor<Path>{

    public LinkedHashMap<String, List<String>> list = new LinkedHashMap<>(); // Put there scores and docIds

    @Override
    public FileVisitResult visitFile(Path path, BasicFileAttributes attr) throws IOException {

     File file =  new File(path.toString());
     BufferedReader br = new BufferedReader(new FileReader(file));
     String line;

    while((line = br.readLine()) != null){


        if(list.containsKey(line.split(" ")[0])){
            list.get(line.split(" ")[0]).add(line.split(" ")[1]);
        }
        else{
            list.put(line.split(" ")[0],new ArrayList(Arrays.asList(line.split(" ")[1])));
        }

    }
        return CONTINUE;
    }


    public static void main(String args[]) throws IOException {




        if (args.length < 2) {
            System.err.println("Usage: java App spamDir docIDs ...");
            return;
        }
        Path spamDir = Paths.get(args[0]);
        String[] docIDs = args[1].split(",");

        App ap = new App();
        Files.walkFileTree(spamDir, ap);
        ArrayList scores = new ArrayList(); // keep scores in that list

        //Search the Lists in LinkedHashMap
        for(int j=0; j<docIDs.length; j++){
            Set set = ap.list.entrySet();
            Iterator i = set.iterator();
            int counter = 0;
            while(i.hasNext()){

                // if LinkedHashMap has the docID add it to scores List
                Map.Entry me = (Map.Entry) i.next();
                ArrayList searchList = (ArrayList) me.getValue();
                if(searchList.contains(docIDs[j])){
                    scores.add(me.getKey());
                    counter++;
                    break;


                }
                else {

                    continue;
                }

            }
            // if LinkedHashMap has not the docId add -1 to scores List
            if(counter == 0){
                scores.add("-1");
            }

        }

        String joined = String.join("," , scores);
        System.out.println(joined);

    }
}
我试图使用
XmX2048M
来增加堆大小,但这并没有解决我的问题。我该怎么办


此外,如果我在不同的路径上运行程序(包括2个相同格式的txt文件),它会正常工作。

这听起来像是一个面试作业。我敢打赌,这里的任务是进行思想上的飞跃,将解析文件时必须保存在内存中的数据与可以存储在索引中的数据分开

不管你有多少内存,如果你继续这样做,你最终都会耗尽它。在您的案例中,有一些有用的提示可用于解决此问题:

  • 不要把一切都记在记忆里。如果可以的话,建立一个索引到一个只保存必要数据的单独文件
  • 将文件作为流处理:这意味着您可以逐行、逐文件地解析
    InputStream
    ,这样您就不必将它们保存在内存中
在你的情况下,这是:

public LinkedHashMap<String, List<String>> list

地图文档;
取决于您是否要计算分数出现的次数。外部的
映射
将文档ID作为键保存,而内部映射自己查找
分数->计数
。这是计数排序算法的一个微妙变化:您只需要跟踪文档id和每个文档id的分数,因为分数的大小是有限的(它们可以有多少位数?),所以最终会消耗
O(1)
内存。其余的数据可以扔掉


请注意您只需存储感兴趣的文档ID的键即可。您可以扔掉其余的。

以下内容采用了新的方法,并纠正了一些错误

public Map<String, List<String>> map = new HashMap<>();

@Override
public FileVisitResult visitFile(Path path, BasicFileAttributes attr)
        throws IOException {

    Files.lines(path).forEach(line -> {
        String[] keyValue = line.split(" ", 2);
        map.compute(keyValue[0],
             (key, oldList) -> {
                  List<String> list = oldList == null
                      ? new ArrayList<>()
                      : oldList;
                  list.add(keyValue[1]);
                  return list;
             }); 
    });
    return CONTINUE;
}
与普通ASCII的字符串相比,您可以节省一半的字节(一个
char
是两个字节)


可能一个数据库,比如嵌入式java Derby或H2会更好。

您的机器上有多少内存?如果文件夹在磁盘上的容量为20GB,那么它就不太可能全部放在内存中。。。(在内存中表示几乎总是比在磁盘上表示昂贵得多)分析它。前面的评论可能是对的,但硬数据永远不会有坏处。我尝试在VirtualBox Ubuntu上运行它,它有4096 MB内存。4096 MB是4 GB。您是如何计划在4gb内存中安装20gb的数据的?不管怎样,不管命名如何,您都试图将20gb的内容挤进4gb。如果你的工作机器有50GB的内存,那就把虚拟箱扔掉或者给它更多的内存。这是学校的家庭作业,我的任务是把docid作为命令行参数,打印出它们的分数。如果358个txt文件中没有一个包含docId,那么返回-1作为分数。开始时,我想使用第二个Map,但是因为有733019372行docId,所以Map变得越来越大。由于分数限制在0到99,我想我应该使用类似
Map
Yes的东西,lambda很难阅读,因为语法是如此不确定。我希望极简主义能给人留下深刻印象。当然,说出一个人仍然不知道的事情是职业化的标志。作为开发人员,不理解某些东西是正常现象。;)
Map<String, Map<Integer, Integer>> docIdsWithScoresAndCounts;
Map<String, List<Integer>> docIdsWithScores;
public Map<String, List<String>> map = new HashMap<>();

@Override
public FileVisitResult visitFile(Path path, BasicFileAttributes attr)
        throws IOException {

    Files.lines(path).forEach(line -> {
        String[] keyValue = line.split(" ", 2);
        map.compute(keyValue[0],
             (key, oldList) -> {
                  List<String> list = oldList == null
                      ? new ArrayList<>()
                      : oldList;
                  list.add(keyValue[1]);
                  return list;
             }); 
    });
    return CONTINUE;
}
byte[] bytes = keyValue[1].getBytes(Charset.defaultCharset());
String s = new String(bytes, Charset.defaultCharset());