在数据结构中有效地表示数据

时间:2012-01-25 11:52:54

标签: java optimization data-structures

我有以下格式:

SOLEXA3_1:3:5:1473:616/1    gi|7367913151|ref|NC_007367.1|  100.00  23  0   0   27  49  3404561 3404539 1e-5    46.1
SOLEXA3_1:3:5:1473:616/1    gi|73921565|ref|NC_007367.1|    100.00  23  0   0   27  49  3404561 3404539 1e-5    46.1
SOLEXA3_1:3:5:1474:616/1    gi|32140171|ref|NC_007367.1|    100.00  23  0   0   27  49  3404561 3404539 1e-2    46.1
SOLEXA3_1:3:5:1474:616/1    gi|7354921565|ref|NC_007367.1|  100.00  23  0   0   27  49  3404561 3404539 1e-5    46.1
SOLEXA3_1:3:5:1475:616/1    gi|73921565|ref|NC_007367.1|    100.00  23  0   0   27  49  3404561 3404539 1e-5    46.1
SOLEXA3_1:3:5:1475:616/1    gi|73921565|ref|NC_007367.1|    100.00  23  0   0   27  49  3404561 3404539 1e-5    46.1

基本上它是一个制表符分隔的文件,我将有多个输入点击(第一个字段:SOLEXA3_1:3:5:1474:616/1作为示例)和特定输入的多个点击: 上述示例输入的321401717354921565)。我想要做的是构建某种特定读取的所有命中的内存表示以及与每次命中相关的质量 - 它是倒数第二个字段 - 前面提到的1e-51e-2命中。所以我做的是以下几点:

我有Map<String, ArrayList<TObjectDoubleMap<String>>>。其中每个String基本上都是输入ID,而ArrayList包含来自Trove库的映射,该映射包含一对String,double - 该字符串是命中的Id和分数。我的输入文件大约是1800万行,并且有一堆-Xmx12g我正在堆内存不足。我有什么想法可以优化内存使用情况?请记住,实际得分会有所不同,所以我不认为分享它们是可行的。

3 个答案:

答案 0 :(得分:1)

我会用:

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

其中Key只是两个字段的串联,并且您将质量和分数写入ByteArrayOutputStream。

结果数据结构如下所示:

Key:    "SOLEXA3_1:3:5:1474:616/1_32140171"
Value:  |5|46.1|2|46.1|  //where this is actually just a byte[]

然后在阅读质量和分数时,你只需使用readByte()和readDouble(),直到你到达流的末尾。

当然,这样做会使查询内容变得有点棘手,但是你会节省大量的内存分配。

例如:

for ( String[] fields : rows ) {
    Map<String, ByteArrayOutputStream> map = new HashMap<String, ByteArrayOutputStream>();
    String key = fields[0] + "_" + fields[1];
    byte quality = Byte.parseByte(fields[10].substring(3));
    double score = Double.parseDouble(fields[11]);

    if ( !map.containsKey(key) ) {
        map.put(key, new ByteArrayOutputStream());
    }
    DataOutputStream dos = new DataOutputStream(map.get(key));
    dos.writeByte(quality);
    dos.writeDouble(score);
}


//reading
for ( String key : map.keySet() ) {
    ByteArrayOutputStream baos = map.get(key);
    int numHits = baos.size()/9; //1 byte for quality, 8 for score
    DataInputStream din = new DataInputStream(new ByteArrayInputStream(baos.toByteArray()));
    System.out.print( key + " - " + numHits);
    while ( din.available() > 0 ) {
        byte quality = din.readByte();
        double score = din.readDouble();
        System.out.print(" (" + quality + ", " + score + ")");
    }
    System.out.print("\n");
}

使用此方法,我可以在&lt; 1GB内存中读取和存储~20mil记录。 (在MacBook Pro上大约10秒钟)。

答案 1 :(得分:1)

我认为你使用列表地图的方法基本上是好的,但可以设计得更紧凑。

首先,确保您对读取的名称进行规范化。也就是说,在内存中应该只有一个字符串实例,其中包含字符“SOLEXA3_1:3:5:1473:616/1”;在使用之前使用映射将名称缩减为规范实例。

其次,命中标识符是否总是整数?如果是这样的话,请将它们存储起来(因为有些显然太大而不适合整体)。

第三,我认为你可以将命中和分数存储在一个非常紧凑的结构中,只要你准备做一些工作,通过手动将两者都打包成长(!)。然后,您可以为每个输入存储已排序的long数组。

以下是我如何规范读取名称:

Map<String, String> names = new HashMap<String, String>();

public String getCanonicalInstanceOfName(String name) {
    String canonicalName = names.get(name);
    if (canonicalName != null) {
        name = canonicalName;
    }
    else {
        names.put(name, name);
    }
    return name;
}

我至少可以想到一种更聪明的方法,但现在这样做。

以下是我如何处理得分:

public long packHitIDAndScore(String id, float score) {
    long numericID = Long.parseLong(id);
    int scoreAsHundredthsOfAPercent = (int)(score * 100.0);
    long packedIDAndScore = (numericID << 14) + scoreAsHundredthsOfAPercent;
    return packedIDAndScore;
}

14这是因为14个二进制位大到足以保存值高达16384,这足以包含0到10000的范围。请注意,您只能为ID获得50位存储空间,因此值得检查没有ID大于1125899906842623。

由于您使用的是Trove,因此可以将打包的long存储在TLongArrayList中。使用binarySearch对列表进行排序,以便为每个长连接列表找到合适的位置,并插入以将其放在那里。要在列表中查找值,请再次使用binarySearch。

答案 2 :(得分:0)

有几个选项可供选择,但我会使用嵌入式数据库(但只有“传统”数据库不可用) - 例如H2。除非您对结果数据进行非常繁重的计算,否则这将是一个安全的选择。

快速列出其他选项:

  • ETL系统(授予它需要数据库)
  • NIO,如果由于文件读取方式而发生内存不足
  • “更轻松”的数据结构(尝试?,FastUtil

你甚至可以综合使用它们。