代码之家  ›  专栏  ›  技术社区  ›  Lars D

Java中的常量常量

  •  15
  • Lars D  · 技术社区  · 16 年前

    我需要在Java应用程序中包含大约1兆字节的数据,以便在源代码的其余部分中非常快速和容易地访问。我的主要背景不是Java,所以我最初的想法是将数据直接转换成Java源代码,定义1MbEnter的常数数组、类(而不是C++结构)等,类似于此:

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

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

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

    11 回复  |  直到 13 年前
        1
  •  22
  •   Jon Skeet    16 年前

    我个人 不会 把它放在源格式中。

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

    您很可能希望一个类封装加载、缓存和提供这些数据——但我认为将其转换为源代码并没有多大好处。

        2
  •  7
  •   aioobe    16 年前

    由于Java字节码文件的限制,类文件不能大于64K字节。(它们不适用于此类数据。)

    我将在启动程序时加载数据,使用如下代码行:

    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。)

    如果将数据转换为二进制格式,当然可以提高效率。在这种情况下,您可以利用 DataInputStream (但别忘了经历一些 BufferedInputStream BufferedReader )

    根据您希望如何访问数据,最好将记录存储在哈希图中。( HashMap<Integer, DataRecord> )(有 i j 作为钥匙)。

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


    对于内存映射方法 ,看看 java.nio.channels 用Java封装。尤其是方法

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

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


    Dan Bornstein(Dalvikvm的主要开发人员)在 this talk (看看0:30:00左右)。然而,我怀疑这个解决方案是否适用于与兆字节一样多的数据。

        3
  •  3
  •   Lars Andren    16 年前

    一个想法是您使用枚举器,但我不确定这是否适合您的实现,它还取决于您计划如何使用数据。

    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 初始化器设置类的私有字段。

        4
  •  3
  •   Michael Borgwardt    16 年前

    将数据输入源实际上不是最快的解决方案,也不是一个长期的解决方案。加载Java类是相当复杂和缓慢的(至少在字节码验证的平台上,对Android不确定)。

    最快的方法是定义自己的二进制索引格式。你可以把它当作 byte[] (可能使用内存映射)甚至 RandomAccessFile 在你开始使用它之前,不要以任何方式解释它。这样做的代价是访问它的代码的复杂性。对于固定大小的记录,通过二进制搜索访问的已排序记录列表仍然非常简单,但其他任何内容都会变得难看。

    尽管在这样做之前,您确定这不是过早的优化吗?最简单(可能仍然相当快)的解决方案是jsut序列化一个映射、列表或数组——您是否尝试过这个方法,并确定它实际上太慢了?

        5
  •  1
  •   McDowell rahul gupta    16 年前

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

    请注意,对类及其结构的大小有严格的限制[Ref JVM Spec .

        6
  •  1
  •   fish    16 年前

    这是你在Java中定义它的方式,如果我理解你在做什么:

    public final Object[][] myList = { 
              { 23012, 22, "Hamburger"} , 
              { 28375, 123, "Kieler"}
            };
    
        7
  •  1
  •   josefx    16 年前

    看起来您计划编写自己的轻量级数据库。
    如果可以将字符串的长度限制为实际的最大大小,则可以使用以下方法:

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

    如果不能限制块的长度,请将字符串转储到另一个文件中,并只在表中存储偏移量。这需要额外的文件访问权限,并使修改数据变得困难。
    有关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字节偏移量。
        8
  •  0
  •   p.marino    16 年前

    还可以声明一个静态类(或一组静态类),将指定的值公开为 方法 . 毕竟,您希望代码能够找到给定名称的值,而不希望该值发生更改。

    所以:location=mylibofconstants.returnHamburgerlocation().zipcode

    你可以用lazyinitialization将这些东西存储在哈希表中,如果你认为即时计算是浪费时间的话。

        9
  •  0
  •   Sebastien Lorber    16 年前

    你需要的不是缓存吗? 作为类,它被加载到内存中,实际上不局限于定义的大小,应该和使用常量一样快… 实际上,它甚至可以使用某种索引来搜索数据(例如对象hashcode…) 例如,您可以创建所有数据数组(例如23012、22,“Hamburger”),然后创建3个哈希图: 图1.Put(23012,汉堡); 图2.Put(22,汉堡); MAP3.PUT(“汉堡”,汉堡); 这样你就可以根据你的参数在地图上快速搜索… (但只有当你的钥匙在地图上是唯一的…这只是一个能激励你的例子)

    在工作中,我们有一个非常大的webapp(80个weblogic实例),它几乎就是我们要做的:到处缓存。从数据库中的CountryList创建缓存…

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

        10
  •  0
  •   Pete Kirkham    16 年前

    Java序列化听起来像是需要解析的东西…不好的。难道不存在某种标准格式来存储流中的数据,而不需要解析就可以使用标准API进行读取/查找吗?

    如果您要在代码中创建数据,那么它将在第一次使用时被加载。这不太可能比从一个单独的文件中加载效率高出很多,而且在分析类文件中的数据的同时,JVM必须验证和编译字节码以创建100万次的每个对象,而不是仅仅从循环中加载一次。

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

    您可能需要检查 HDF5 库在您的平台上运行;但是对于这样一个简单而小型的数据集来说,这可能是多余的。

        11
  •  0
  •   Taras Shevchuk    13 年前

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