我需要在Java应用程序中包含大约1 MB的数据,以便在其余的源代码中快速轻松地访问。我的主要背景不是Java,所以我最初的想法是将数据直接转换为Java源代码,定义1MByte的常量数组,类(而不是C ++ struct)等,如下所示:
public final/immutable/const MyClass MyList[] = {
{ 23012, 22, "Hamburger"} ,
{ 28375, 123, "Kieler"}
};
但是,似乎Java不支持这样的结构。它是否正确?如果是,那么这个问题的最佳解决方案是什么?
注意:数据由2个表组成,每个表有大约50000个数据记录,可以通过各种方式进行搜索。这可能需要稍后的一些索引,以这种方式保存更多的记录,可能是100万条记录。我希望应用程序能够非常快速地启动,而无需遍历这些记录。
答案 0 :(得分:22)
我个人不会将其放入源代码中。
相反,请在jar文件中以适当的原始格式包含数据(我假设您将打包应用程序或库)并使用Class.getResourceAsStream
或ClassLoader.getResourceAsStream
加载它。
你可能希望一个类封装加载,缓存和提供这些数据 - 但我认为将它转换为源代码并没有太大的好处。
答案 1 :(得分: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;
}
}
( NB:扫描仪非常慢,所以不要仅因为它有一个简单的界面而使用它。坚持使用某种形式的BufferedReader和split,或者StringTokenizer。)< / p>
如果将数据转换为二进制格式,当然可以提高效率。在这种情况下,您可以使用DataInputStream
(但不要忘记浏览一些BufferedInputStream
或BufferedReader
)
根据您希望如何访问数据,最好将记录存储在哈希映射(HashMap<Integer, DataRecord>
)中(以i
或j
为键)
如果您希望在JVM加载类文件本身的同时加载数据(大致!),您可以执行读取/初始化,而不是在方法中,而是在static { ... }
中进行封装。
对于内存映射方法,请查看java中的java.nio.channels
- 包。特别是方法
可以找到完整的代码示例here。
答案 2 :(得分:3)
一个想法是你使用枚举器,但我不确定这是否适合你的实现,它还取决于你计划如何使用数据。
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;
}
}
然后可以访问这些:
Stuff someThing = Stuff.HAMBURGER;
int hamburgerA = Stuff.HAMBURGER.getA() // = 23012
另一个想法是使用static初始值设定项来设置类的私有字段。
答案 3 :(得分:3)
将数据放入源代码实际上可能不是最快的解决方案,而不是远程解决方案。加载Java类非常复杂和缓慢(至少在进行字节码验证的平台上,不确定Android)。
最快的方法是定义自己的二进制索引格式。然后,您可以将其读作byte[]
(可能使用内存映射),甚至是RandomAccessFile
,无需以任何方式解释它,直到您开始访问它为止。这样做的代价是访问它的代码的复杂性。对于固定大小的记录,通过二进制搜索访问的记录的排序列表仍然非常简单,但其他任何内容都会变得难看。
虽然在此之前,您确定这不是过早的优化吗?最容易(也可能仍然很快)的解决方案是jsut序列化Map,List或数组 - 你试过这个并确定它实际上太慢了吗?
答案 4 :(得分:1)
将数据直接转换为Java源代码,定义1MByte的常量数组,类
请注意,对类及其结构的大小有严格的限制[ref JVM Spec。
答案 5 :(得分:1)
如果我理解你的目标,那就是用Java定义它的方式:
public final Object[][] myList = {
{ 23012, 22, "Hamburger"} ,
{ 28375, 123, "Kieler"}
};
答案 6 :(得分:1)
看起来您打算编写自己的轻量级数据库 如果您可以将String的长度限制为实际的最大大小,则以下内容可能有效:
如果无法限制块的长度,则将字符串转储到附加文件中,并仅将偏移量存储在表中。这需要额外的文件访问权限,并且难以修改数据 有关java中随机文件访问的一些信息可以在http://java.sun.com/docs/books/tutorial/essential/io/rafs.html找到。
为了更快地访问,您可以在Hashmap中缓存一些读取条目,并在读取新条目时始终从地图中删除最旧的条目。
伪代码(不会编译):
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);
}
}
缺少伪码:
答案 7 :(得分:0)
您还可以声明一个静态类(或一组静态类),将所需的值公开为 methods 。毕竟,您希望您的代码能够找到给定名称的值,并且不希望该值发生更改。
所以:location = MyLibOfConstants.returnHamburgerLocation()。zipcode
你可以将这些东西存储在带有lazyinitialization的哈希表中,如果你在飞行中计算它会浪费时间。
答案 8 :(得分:0)
不是你需要的缓存吗? 作为类,它被加载到内存中,并不仅限于定义的大小,应该与使用常量一样快...... 实际上它甚至可以使用某种索引搜索数据(例如使用对象哈希码...) 例如,您可以创建所有数据数组(ex {23012,22,“Hamburger”}),然后创建3个hashmap: map1.put(23012,hamburgerItem); map2.put(22,hamburgerItem); map3.put( “汉堡包”,hamburgerItem); 通过这种方式,您可以根据您拥有的参数在地图中快速搜索... (但这只适用于你的钥匙在地图中是独一无二的......这只是一个可以激励你的例子)
在工作中,我们有一个非常大的webapp(80个weblogic实例),它几乎就是我们所做的:到处缓存。从数据库中的国家/地区列表中,创建缓存...
有许多不同类型的缓存,您应该检查链接并选择您需要的... http://en.wikipedia.org/wiki/Cache_algorithms
答案 9 :(得分:0)
Java序列化听起来像需要解析的东西......不好。是否存在某种用于在流中存储数据的标准格式,可以使用标准API读取/查找而无需解析它?
如果您要在代码中创建数据,那么它将在首次使用时加载。这不太可能比从单独的文件加载更有效 - 以及解析类文件中的数据,JVM必须验证并编译字节码以创建每个对象一百万次,而不是只有一次如果你从循环中加载它。
如果您想要随机访问并且无法使用内存映射文件,则可以使用RandomAccessFile
。您需要在启动时加载索引,或者需要使条目具有固定长度。
您可能想要检查HDF5库是否在您的平台上运行;但是对于这样一个简单的小数据集来说可能有点过分。
答案 10 :(得分:0)
我建议使用资产来存储这些数据。