Java中的大量常量

时间:2010-05-04 07:04:28

标签: java android

我需要在Java应用程序中包含大约1 MB的数据,以便在其余的源代码中快速轻松地访问。我的主要背景不是Java,所以我最初的想法是将数据直接转换为Java源代码,定义1MByte的常量数组,类(而不是C ++ struct)等,如下所示:

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

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

注意:数据由2个表组成,每个表有大约50000个数据记录,可以通过各种方式进行搜索。这可能需要稍后的一些索引,以这种方式保存更多的记录,可能是100万条记录。我希望应用程序能够非常快速地启动,而无需遍历这些记录。

11 个答案:

答案 0 :(得分:22)

我个人不会将其放入源代码中。

相反,请在jar文件中以适当的原始格式包含数据(我假设您将打包应用程序或库)并使用Class.getResourceAsStreamClassLoader.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(但不要忘记浏览一些BufferedInputStreamBufferedReader

根据您希望如何访问数据,最好将记录存储在哈希映射(HashMap<Integer, DataRecord>)中(以ij为键)

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


对于内存映射方法,请查看java中的java.nio.channels - 包。特别是方法

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

可以找到完整的代码示例here


Dan Bornstein(DalvikVM的首席开发人员)在this talk(大约0:30:00)解释了您的问题。但是我怀疑这个解决方案适用于兆字节数据。

答案 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的长度限制为实际的最大大小,则以下内容可能有效:

  • 将每个条目写入二进制文件,条目大小相同,因此每个条目都浪费一些字节(int a,int b,int stringsize,string,padding)
  • 要读取条目,请将文件作为随机访问文件打开,将索引与条目的长度相乘以获取偏移量并搜索位置。
  • 将字节放入bytebuffer并读取值,String必须使用String(byte [],int start,int length,Charset)ctor进行转换。

如果无法限制块的长度,则将字符串转储到附加文件中,并仅将偏移量存储在表中。这需要额外的文件访问权限,并且难以修改数据 有关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);
   }
}

缺少伪码:

  • 写作,因为你需要它用于持续数据
  • 条目总数/ sizeof文件,只需要在文件开头有一个额外的整数,每个访问操作需要额外的4个字节偏移量。

答案 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)

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