Большое количество констант в Java

Мне нужно включить около 1 мегабайта данных в приложение Java, для очень быстрого и легкого доступа в остальной части исходного кода. Моим основным фоном является не Java, поэтому моей первоначальной идеей было преобразовать данные непосредственно в исходный код Java, определяя 1MByte постоянных массивов, классов (вместо С++ struct) и т.д., Что-то вроде этого:

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

Однако, похоже, что Java не поддерживает такие конструкции. Это верно? Если да, то каково наилучшее решение этой проблемы?

ПРИМЕЧАНИЕ. Данные состоят из 2 таблиц, каждая из которых содержит около 50000 записей данных, которые нужно искать по-разному. Это может потребовать некоторых индексов позже, со значительно большим количеством записей, возможно, 1 миллион записей, сохраненных таким образом. Я ожидаю, что приложение запустится очень быстро, без повторения этих записей.

Ответы

Ответ 1

Я бы лично не поместил его в исходную форму.

Вместо этого включите данные в некоторый соответствующий необработанный формат в вашем файле jar (я предполагаю, что вы будете упаковывать приложение или библиотеку вверх) и используйте Class.getResourceAsStream или ClassLoader.getResourceAsStream, чтобы загрузить его.

Вы вполне можете захотеть, чтобы класс инкапсулировал загрузку, кеширование и предоставление этих данных, но я не вижу большой выгоды от преобразования его в исходный код.

Ответ 2

Из-за ограничений файлов байт-кода 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.)

Эффективность, конечно же, может быть улучшена, если вы преобразуете данные в двоичный формат. В этом случае вы можете использовать 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

Здесь вы можете найти полные примеры кода здесь.


Дэн Борнштейн (ведущий разработчик DalvikVM) объясняет решение вашей проблемы в этот разговор (Посмотрите около 0:30:00), Однако я сомневаюсь, что решение применяется к таким же данным, как и мегабайт.

Ответ 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 initializer для установки частных полей класса.

Ответ 4

Ввод данных в источник мог бы быть не самым быстрым решением, а не длинным выстрелом. Загрузка Java-класса довольно сложна и медленна (по крайней мере, на платформе, которая проверяет байт-код, не уверен в Android).

Самый быстрый способ сделать это - определить собственный формат двоичных индексов. Затем вы могли бы прочитать это как byte[] (возможно, используя сопоставление памяти) или даже RandomAccessFile, не интерпретируя его каким-либо образом, пока не начнете его получать. Стоимость этого будет сложностью кода, который обращается к нему. С записями фиксированного размера отсортированный список записей, доступ к которым осуществляется через двоичный поиск, все равно будет довольно простым, но все остальное будет уродливым.

Хотя до этого вы уверены, что это не преждевременная оптимизация? Самое простое (и, вероятно, еще довольно быстрое) решение - это сериализовать карту, список или массив jsut - попробовали ли вы это и определили, что он, по сути, слишком медленный?

Ответ 5

конвертировать данные непосредственно в исходный код Java, определяя 1MByte постоянных массивов, классы

Помните, что существуют строгие ограничения на размер классов и их структур [ref JVM Spec.

Ответ 6

Вот как вы определяете его в Java, если я понял, что вам нужно:

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

Ответ 7

Похоже, вы планируете написать свою легкую базу данных.
Если вы можете ограничить длину String реалистичным максимальным размером, то может работать следующее:

  • записывать каждую запись в двоичный файл, записи имеют одинаковый размер, поэтому вы тратите несколько байтов на каждую запись (int a, int b, int stringsize, string, padding)
  • Чтобы прочитать запись, откройте файл как файл произвольного доступа, умножьте индекс на длину записи, чтобы получить смещение, и найдите позицию.
  • Поместите байты в байтовый буфер и прочитайте значения, 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);
   }
}

Отсутствует псевдокод:

  • так как вам нужно это для постоянных данных
  • общее количество записей/размер файла, требуется только целое число в начале файла и дополнительное 4-байтное смещение для каждой операции доступа.

Ответ 8

Вы также можете объявить статический класс (или набор статических классов), отображающий искомые значения как методы. В конце концов, вы хотите, чтобы ваш код мог найти значение для данного имени и не хотел, чтобы значение менялось.

Итак: location = MyLibOfConstants.returnHamburgerLocation(). zipcode

И вы можете хранить этот материал в хэш-таблице с lazyinitialization, если вы верите, что расчет его на лету будет пустой тратой времени.

Ответ 9

Не кеш, что вам нужно? Поскольку классы загружаются в память, не ограничиваясь определенным размером, они должны быть такими же быстрыми, как использование констант... На самом деле он может даже искать данные с помощью каких-то индексов (например, с hashcode объекта...) Вы можете, например, создать все свои массивы данных (ex {23012, 22, "Hamburger" }), а затем создать 3 хэш-карты: map1.put(23012, hamburgerItem); map2.put(22, hamburgerItem); map3.put( "гамбургер", hamburgerItem); Таким образом, вы можете искать очень быстро на одной из карт в соответствии с параметром, который у вас есть... (но это работает, только если ваши ключи уникальны на карте... это всего лишь пример, который может вас вдохновить)

На работе у нас очень большой webapp (80 экземпляров weblogic), и это почти то, что мы делаем: кеширование везде. Из списка стран в базе данных создайте кеш...

Существует много разных типов кешей, вы должны проверить ссылку и выбрать то, что вам нужно... http://en.wikipedia.org/wiki/Cache_algorithms

Ответ 10

Сериализация Java звучит как нечто, что нужно разобрать... не хорошо. Разве нет какого-то стандартного формата для хранения данных в потоке, который можно читать или искать с помощью стандартного API без его разбора?

Если вы должны были создать данные в коде, тогда все они будут загружены при первом использовании. Это вряд ли будет намного более эффективным, чем загрузка из отдельного файла, - а также анализ данных в файле класса, JVM должен проверить и скомпилировать байт-коды для создания каждого объекта в миллион раз, а не только один раз, если вы загрузите его из цикла.

Если вы хотите получить произвольный доступ и не можете использовать файл с отображением памяти, тогда существует RandomAccessFile, который может работать. Вам нужно либо загрузить индекс при запуске, либо вам нужно сделать записи фиксированной длиной.

Возможно, вы захотите проверить, запускаются ли на вашей платформе библиотеки HDF5; это может быть излишним для такого простого и небольшого набора данных.

Ответ 11

Я бы рекомендовал использовать активы для хранения таких данных.