2010-05-04 39 views
15

我需要在Java应用程序中包含大约1 MB的数据,以便在其他源代码中快速轻松地进行访问。我的主要背景是不是Java,所以我最初的想法是将数据直接转换为Java源代码,定义常量数组的1M字节,类(而不是C++结构)等,像这样:Java中的大量常量

public final/immutable/const MyClass MyList[] = { 
    { 23012, 22, "Hamburger"} , 
    { 28375, 123, "Kieler"} 
}; 

然而,似乎Java不支持这样的结构。它是否正确?如果是的话,这个问题的最佳解决方案是什么?

注意:数据由2个表组成,每个数据大约有50000条记录,这些记录将以各种方式进行搜索。这可能会稍后需要一些索引,这样可以保存更多的记录,大概有100万条记录。我希望应用程序启动速度非常快,而不会迭代这些记录。

回答

22

我个人不会把它放在源代码形式。

相反,将数据以适当的原始格式包含在jar文件中(我假设您将打包应用程序或库)并使用Class.getResourceAsStreamClassLoader.getResourceAsStream加载它。

你可能很希望一个类封装加载,缓存和提供这些数据 - 但我没有看到将它转换成源代码很多好处。

+0

这是什么Java的标准数据格式? – 2010-05-04 07:22:51

+2

@Lars:对于K/V对,* key = value *在'.properties'文件中(对于'Properties'类检查javadoc),仅列出天空的限制,尽管我建议你使用简单的您的需求。如果你愿意,也许是XML,但通常这不是必须的。 – Esko 2010-05-04 07:25:35

+0

@Lars D:这真的取决于你的数据需要什么。属性文件适用于键/值对,但如果您有很多数字数据,则效率可能不是非常高。 (对于XML也是如此。)有许多序列化库可能会让您的生活更轻松,或者只是使用您自己的自定义数据格式。 – 2010-05-04 07:27:45

3

一个想法是你使用枚举数,但我不确定这是否适合你的实现,也取决于你打算如何使用数据。

Stuff someThing = Stuff.HAMBURGER; 
int hamburgerA = Stuff.HAMBURGER.getA() // = 23012 

另一个想法是使用static初始化设置类的私有字段:

public enum Stuff { 

HAMBURGER (23012, 22), 
KIELER (28375, 123); 

private int a; 
private int b; 

//private instantiation, does not need to be called explicitly. 
private Stuff(int a, int b) { 
    this.a = a; 
    this.b = b; 
    } 

public int getAvalue() { 
    return this.a; 
} 

public int getBvalue() { 
    return this.b; 
} 

}

这些就好像访问。

+0

@Lars,除去enum构造函数调用的参数 – aioobe 2010-05-04 07:13:44

+0

@aioobe,非常感谢!除非你想把这些数字作为字符串存储(然后私有构造函数需要相应地改变),否则“没有”需要。 – 2010-05-04 07:16:02

+0

@Lars D(其他Lars),像这样你永远不需要显式地调用构造函数,你只需要像上面的例子那样定义每个元素。 – 2010-05-04 07:18:46

0

您也可以声明一个静态类(或一组静态类),将需要的值暴露为方法。毕竟,您希望您的代码能够找到给定名称的值,并且不希望该值发生更改。

这样:位置= MyLibOfConstants.returnHamburgerLocation()邮编

而且你可以在哈希表中与惰性初始模式这个东西保存,如果你件事计算它的飞行将是浪费时间。

7

由于java字节码文件的限制,类文件不能大于64k iirc。 (他们只是不适合这种类型的数据。)

我一旦开始了程序加载数据,使用类似下面的代码行:

import java.io.*; 
import java.util.*; 

public class Test { 
    public static void main(String... args) throws IOException { 
     List<DataRecord> records = new ArrayList<DataRecord>(); 
     BufferedReader br = new BufferedReader(new FileReader("data.txt")); 
     String s; 
     while ((s = br.readLine()) != null) { 
      String[] arr = s.split(" "); 
      int i = Integer.parseInt(arr[0]); 
      int j = Integer.parseInt(arr[1]); 
      records.add(new DataRecord(i, j, arr[0])); 
     } 
    } 
} 


class DataRecord { 
    public final int i, j; 
    public final String s; 
    public DataRecord(int i, int j, String s) { 
     this.i = i; 
     this.j = j; 
     this.s = s; 
    } 
} 

注:扫描仪因为它有一个简单的界面,所以不要试图使用它,坚持使用某种形式的BufferedReader和Split或StringTokenizer。)

如果将数据转换为数据,当然可以提高效率二进制格式。在这种情况下,你可以使用DataInputStream的(但不要忘记去通过一些BufferedInputStreamBufferedReader

根据您希望如何访问数据时,你可能会更好存储在哈希记录-map(HashMap<Integer, DataRecord>)(具有ij作为关键)。

如果您希望在JVM加载类文件本身的同时加载数据(粗略地!),您可以执行读取/初始化操作,而不是在方法中,但在static { ... }中进行了封装。


对于内存映射方法,看看在Java中java.nio.channels -package。特别是该方法

public abstract MappedByteBuffer map(FileChannel.MapMode mode, long position,long size) throws IOException

完整代码的例子可以发现here


丹·伯恩斯坦(DalvikVM的主要开发人员)解释this talk解决您的问题(看看周围0:30:00)。不过,我怀疑这个解决方案适用于多达一兆字节的数据。

+0

加载数据不是一个选项,它会太慢。内存映射会更有意义,但我认为我的目标平台上没有内存映射(Android) – 2010-05-04 07:25:04

+0

查看http://www.developer.com/java/other/article.php/ 1548681 /介绍到内存映射IO-in-Java.htm – aioobe 2010-05-04 07:27:09

+0

类文件*可以*大于64k - 它是单个方法(和初始化块)不可能的。 – 2010-05-04 07:49:26

1

将数据直接转换为Java源代码,定义常量阵列的1M字节,类

注意,有上的类和它们的结构[参考JVM Spec尺寸严格的约束。

0

不是你需要的缓存吗? 作为类被加载到内存中,并没有真正限制到一个定义的大小,应该像使用常量一样快...... 实际上,它甚至可以用某种索引搜索数据(例如使用对象哈希码...) 例如,您可以创建所有数据数组(例如{23012,22,“Hamburger”}),然后创建3个散列图: map1.put(23012,hamburgerItem); map2.put(22,hamburgerItem); map3.put(“Hamburger”,hamburgerItem); 这样你就可以根据你的参数在地图上快速搜索... (但是,只有当你的钥匙在地图上是唯一的,这才有效...这只是一个例子,可以激发你)

在工作中,我们有一个非常大的webapp(80个weblogic实例),它几乎是我们所做的:无处不在。从数据库中countrylist,创建缓存...

有许多不同类型的高速缓存,您应检查该链接并选择你所需要的... http://en.wikipedia.org/wiki/Cache_algorithms

+0

主要标准是数据存在于程序启动时,所以我不需要遍历它或解析它。我不确定缓存如何帮助您做到这一点? – 2010-05-04 07:48:34

+0

@Lars:你想要什么都没有意义。加载一个Java类涉及迭代字节代码并解析它。如果没有迭代并以某种形式解析数据,就不可能加载任何类型的数据。这只是一个这些步骤花费多少的问题。 – 2010-05-04 07:52:37

+0

@迈克尔:好点。 – 2010-05-04 08:37:36

1

这是你如何定义它Java中,如果我明白你所追求的:

public final Object[][] myList = { 
      { 23012, 22, "Hamburger"} , 
      { 28375, 123, "Kieler"} 
     }; 
+0

哥伦布蛋...我不知道这是怎么看字节码。 – 2010-05-04 08:40:07

+0

只需执行'javac TheAboveCode.java && javap -v TheAboveCode'记住Android使用完全不同的文件格式(.dex) – aioobe 2010-05-04 08:49:08

+0

该分配作为构造函数的一部分执行,其大小限制为64k字节,这是违反。其他方法也被限制为64k字节,所以至少需要16种方法才能实现。 – 2010-05-04 09:21:55

3

将数据放入源可实际上不是最快的解决方案,而不是由一个长镜头。加载一个Java类是相当复杂和缓慢的(至少在一个执行字节码验证的平台上,对Android没有把握)。

执行此操作的最快方法是定义您自己的二进制索引格式。然后,您可以将其作为byte[](可能使用内存映射)或甚至是RandomAccessFile读取,但不会以任何方式解释它,直到您开始访问它。这成本将是访问它的代码的复杂性。对于固定大小的记录,通过二进制搜索访问的记录的排序列表仍然非常简单,但其他任何内容都会变得很难看。

虽然在这之前,你确定这不是过早的优化吗?最简单的(也许还是相当快的)解决方案将是序列化一个Map,List或者数组 - 你试过这个并确定它实际上太慢了吗?

0

Java序列化听起来像是需要解析的东西......不好。是否有某种标准格式用于将数据存储到数据流中,可以使用标准API读取/查看数据而不解析数据?

如果您要在代码中创建数据,那么它将全部在第一次使用时加载。这不太可能比从一个单独的文件中加载效率更高 - 以及解析类文件中的数据,JVM必须验证并编译字节码才能创建每个对象一百万次,而不仅仅是一次从循环加载它。

如果你想要随机访问,并且不能使用内存映射文件,那么有一个RandomAccessFile可能工作。您需要在开始时加载索引,或者您需要使条目具有固定长度。

您可能想要检查HDF5库是否在您的平台上运行;尽管如此,对于这样一个简单和小的数据集可能是过度的。

1

看起来你打算编写自己的轻量级数据库。
如果你可以限制字符串的长度,以一个现实的最大尺寸以下可能的工作:

  • 写每进入一个二进制文件,该条目具有相同的大小,所以你浪费与每个条目的字节(int a,int b,int stringsize,string,padding)
  • 要读取一个条目以随机访问文件的形式打开文件,请将索引与条目长度相乘以获取偏移量并查找位置。
  • 将字节放入bytebuffer中并读取值,String必须用String(byte [],int start,int length,Charset)转换。

如果您不能限制块转储字符串的长度在一个附加文件中,并且只将偏移量存储在您的表中。这需要额外的文件访问权限,并且难以修改数据。
有关java中的随机文件访问的一些信息可以在这里找到http://java.sun.com/docs/books/tutorial/essential/io/rafs.html

为了更快地访问,您可以将一些读取的条目缓存在散列图中,并且在读取新的条目时始终从地图中删除最早的条目。
伪代码(不会编译):

class MyDataStore 
{ 
    FileChannel fc = null; 
    Map<Integer,Entry> mychace = new HashMap<Integer, Entry>(); 
    int chaceSize = 50000; 
    ArrayList<Integer> queue = new ArrayList(); 
    static final int entryLength = 100;//byte 
    void open(File f)throws Exception{fc = f.newByteChannel()} 
    void close()throws Exception{fc.close();fc = null;} 
    Entry getEntryAt(int index) 
    { 
     if(mychace.contains(index))return mychace.get(index); 

     long pos = index * entryLength; fc.seek(pos);ByteBuffer 
     b = new ByteBuffer(100); 
     fc.read(b); 
     Entry a = new Entry(b); 
     queue.add(index); 
     mychace.put(index,a); 
     if(queue.size()>chacesize)mychace.remove(queue.remove(0)); 
     return a; 
    } 

} 
class Entry{ 
    int a; int b; String s; 
    public Entry(Bytebuffer bb) 
    { 
    a = bb.getInt(); 
    b = bb.getInt(); 
    int size = bb.getInt(); 
    byte[] bin = new byte[size]; 
    bb.get(bin); 
    s = new String(bin); 
    } 
} 

从伪缺少:

  • 写作,因为你需要它的常量数据
  • 项/的sizeof文件的总数,只需要一个文件开始处的附加整数以及每个访问操作的附加4字节偏移量。
0

我会建议使用资产来存储这些数据。