java—高效的数据结构,可存储数百万条记录

inb24sb2  于 2021-06-04  发布在  Hadoop
关注(0)|答案(2)|浏览(632)

我有一个输入文件,其中包含数百万条记录,每条记录又包含数千列,其中每一列用分隔符分隔。
记录和列的数量因文件而异。
我有一个要求,我必须解析这些记录并将它们存储在java对象中,以便它可以进一步传递给drools框架进行列级验证。
这就是我的输入数据和模式文件的样子。
输入文件:

John|Doe|35|10 Floyd St|132|Los Angeles|CA|USA ... and so on 
...
...
Millions records like this

架构文件:

firstName|String|false|20|NA
lastName|String|false|20|NA
age|Integer|false|3|NA
addressLine1|String|false|20|NA
addressLine2|String|false|20|NA
city|String|false|5|NA
state|String|false|10|NA
country|String|false|10|NA

我试图借助一个Map来实现这个解决方案,并创建了一个包含这个Map的java类。

class GenericRecord {
   Map<String,FieldSpecification> properties; //used HashMap as an implementation
}

class FieldSpecification {
    public String fieldName;
    public String dataType;
    public int length;
    public String value;
    public String format;
}

对于输入文件中的reach行,我正在创建一个 Record 对象并使用map存储其列的值。除此之外,我还将有关列的元数据存储在 FieldSpecification 对象,如数据类型、长度、格式等。
对于我的输入文件中的几千行来说,它工作得很好,但是一旦行数开始增加,它就开始因为内存问题而中断(正如预期的那样)。它正在创建数百万个对象的Map,其中有数千个键。
我知道这不是解决这类问题的有效方法。
因此,我关心的是基于内存的解决方案是否适用于我的场景,或者我更喜欢基于磁盘的解决方案,比如嵌入式数据库或基于磁盘的Map。
请告知是否有任何其他开源Map实现,我可以使用。
注意:对于文件解析和数据验证,我使用的是hadoop,它运行在一个40节点的集群上。
以下是我的Map器的流程和实现:
作为complete行接收该值,然后将该行传递给java框架,java框架将其转换为相应的generiobject(如上所述),然后将该对象传递给drools框架进行进一步验证。
Map器实现:

public void map(LongWritable key , Text value , Context context) throws IOException, InterruptedException {

        //Convert the text value to string i.e line by line comes here
        String record = value.toString();

        // Develop a drools service that will take record as an input 
        // and will validate it on the basis of XL sheet provided
        workingMemory = knowledgeBase.newStatefulKnowledgeSession();
        DroolsObject recordObject = DroolsServiceImpl.validateByRecord(record, fileMetaData, workingMemory);

        //Check to validate if the processed record
        if(recordObject.isValid) {
            context.getCounter(AppCounter.VALID_RECORD).increment(1);
            mapperOutputKey.set("A");
            mapperOutputValue.set(recordObject.toString());
            context.write(mapperOutputKey,mapperOutputValue);
        }

        else {
            context.getCounter(AppCounter.INVALID_RECORD).increment(1);
            mapperOutputKey.set("R");
            mapperOutputValue.set(recordObject.toStringWithErrors());
            context.write(mapperOutputKey,mapperOutputValue);
        }
}
lc8prwob

lc8prwob1#

我建议把数据保存在一个( byte[][] )表,并通过行的编号引用行。然后,您可以使用一个按需读取相应字段的光标:

class FieldSpecification {
    private final int row;
    private final byte[][] mem;

    public String fieldName();
    public String dataType();
    public int length();
    public String value();
    public String format();
}

垃圾收集器应该很容易地处理这些对象。你只需要关心它们的生命周期。
当字节数组不适合你的内存时,好吧,那你就完了。
然后可以通过将名称Map到行号来实现Map。

p8ekf7hl

p8ekf7hl2#

因为您必须将文件中的每个字节的数据都保存在内存中(可能除了分隔符),所以首先要查看文件的大小并将其与内存大小进行比较。如果你的文件比内存大,那就把它保存在内存中的整个想法划掉。
如果内存比文件大,你就有机会了,尽管你需要仔细研究这个文件将来会如何增长,程序将在什么平台上运行,等等。
因此,假设它适合,您可以更有效地使用您的数据结构。保存内存的一个简单方法是废弃Map,只需将每条记录保存为一个字符串(在文件中编码)。一个字符串数组应该有最小的开销,不过您需要确保在填充原始数组时不会不断调整其大小。
当数据结构变大时保持简单可以节省大量内存开销。
另外,如果数据很容易放入内存,则可能需要对jvm进行一些调整,以便为其分配足够的内存(使用-xmx更改堆大小),从而使jvm足够大。我希望您使用的是64位平台上的64位jvm。

相关问题