Большое количество констант в Java - PullRequest
15 голосов
/ 04 мая 2010

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

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

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

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

Ответы [ 11 ]

22 голосов
/ 04 мая 2010

Я лично не переведу его в исходный код.

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

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

7 голосов
/ 04 мая 2010

Из-за ограничений файлов байт-кода java файлы классов не могут быть больше 64 000 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 голосов
/ 04 мая 2010

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

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

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

3 голосов
/ 04 мая 2010

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

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

Другая идея заключается в использовании статического инициализатора для установки личных полей класса.

1 голос
/ 04 мая 2010

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

  • записать каждую запись в двоичный файл, записи имеют одинаковый размер, поэтому вы тратите несколько байтов на каждую запись (int a, int b, int stringsize, string, padding)
  • Чтобы прочитать запись, откройте файл как файл с произвольным доступом, умножьте индекс на длину записи, чтобы получить смещение и найти позицию.
  • Поместите байты в байтовый буфер и прочитайте значения. Строка должна быть преобразована с помощью ctor String (byte [], int start, int length, Charset).

Если вы не можете ограничить длину блока, выведите строки в дополнительный файл и сохраните только смещения в своей таблице. Это требует дополнительного доступа к файлу и усложняет изменение данных.
Некоторая информация о произвольном доступе к файлам в 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 байта для каждой операции доступа.
1 голос
/ 04 мая 2010

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

public final Object[][] myList = { 
          { 23012, 22, "Hamburger"} , 
          { 28375, 123, "Kieler"}
        };
1 голос
/ 04 мая 2010

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

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

0 голосов
/ 06 сентября 2012

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

0 голосов
/ 04 мая 2010

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

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

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

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

0 голосов
/ 04 мая 2010

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...