Ускорение чтения файлов


У меня есть файл 1.7 G со следующим форматом:

String Long String Long String Long String Long ... etc

По сути, String - это ключ, аLong - это значение в хэш-карте, которое я заинтересован инициализировать перед запуском чего-либо еще в моем приложении.

Мой текущий код:

  RandomAccessFile raf=new RandomAccessFile("/home/map.dat","r");
                raf.seek(0);
                while(raf.getFilePointer()!=raf.length()){
                        String name=raf.readUTF();
                        long offset=raf.readLong();
                        map.put(name,offset);
                }

Это занимает около 12 минут, и я уверен, что есть лучшие способы сделать это, поэтому я был бы признателен за любую помощь или указатель.

Спасибо


Обновление как в EJP предложение?

EJP спасибо за ваше предложение, и я надеюсь, что это то, что вы имели в виду. Поправьте меня, если это неправильно

DataInputStream dis=null;
    try{
     dis=new DataInputStream(new BufferedInputStream(new FileInputStream("/home/map.dat")));
     while(true){
       String name=dis.readUTF();
       long offset=dis.readLong();
       map.put(name, offset);
     }
    }catch (EOFException eofe){
      try{
        dis.close();
      }catch (IOException ioe){
        ioe.printStackTrace();
      }
    }
2 2

2 ответа:

Я бы сконструировал файл так, чтобы его можно было использовать на месте. то есть без загрузки таким образом. Поскольку у вас есть записи переменной длины, вы можете построить массив расположения каждой записи, а затем разместить ключ в порядке, чтобы вы могли выполнять двоичный поиск данных. (Или вы можете использовать пользовательскую хэш-таблицу) затем вы можете обернуть это с помощью метода, который скрывает тот факт, что данные фактически хранятся в файле, а не превращаются в объекты данных.

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


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

import vanilla.java.chronicle.Chronicle;
import vanilla.java.chronicle.Excerpt;
import vanilla.java.chronicle.impl.IndexedChronicle;
import vanilla.java.chronicle.tools.ChronicleTest;

import java.io.IOException;
import java.util.*;

public class Main {
    static final String TMP = System.getProperty("java.io.tmpdir");

    public static void main(String... args) throws IOException {
        String baseName = TMP + "/test";
        String[] keys = generateAndSave(baseName, 100 * 1000 * 1000);

        long start = System.nanoTime();
        SavedSortedMap map = new SavedSortedMap(baseName);
        for (int i = 0; i < keys.length / 100; i++) {
            long l = map.lookup(keys[i]);
//            System.out.println(keys[i] + ": " + l);
        }
        map.close();
        long time = System.nanoTime() - start;

        System.out.printf("Load of %,d records and lookup of %,d keys took %.3f seconds%n",
                keys.length, keys.length / 100, time / 1e9);
    }

    static SortedMap<String, Long> generateMap(int keys) {
        SortedMap<String, Long> ret = new TreeMap<>();
        while (ret.size() < keys) {
            long n = ret.size();
            String key = Long.toString(n);
            while (key.length() < 9)
                key = '0' + key;
            ret.put(key, n);
        }
        return ret;
    }

    static void saveData(SortedMap<String, Long> map, String baseName) throws IOException {
        Chronicle chronicle = new IndexedChronicle(baseName);
        Excerpt excerpt = chronicle.createExcerpt();
        for (Map.Entry<String, Long> entry : map.entrySet()) {
            excerpt.startExcerpt(2 + entry.getKey().length() + 8);
            excerpt.writeUTF(entry.getKey());
            excerpt.writeLong(entry.getValue());
            excerpt.finish();
        }
        chronicle.close();
    }

    static class SavedSortedMap {
        final Chronicle chronicle;
        final Excerpt excerpt;
        final String midKey;
        final long size;

        SavedSortedMap(String baseName) throws IOException {
            chronicle = new IndexedChronicle(baseName);
            excerpt = chronicle.createExcerpt();
            size = chronicle.size();
            excerpt.index(size / 2);
            midKey = excerpt.readUTF();
        }

        // find exact match or take the value after.
        public long lookup(CharSequence key) {
            if (compareTo(key, midKey) < 0)
                return lookup0(0, size / 2, key);
            return lookup0(size / 2, size, key);
        }

        private final StringBuilder tmp = new StringBuilder();

        private long lookup0(long from, long to, CharSequence key) {
            long mid = (from + to) >>> 1;
            excerpt.index(mid);
            tmp.setLength(0);
            excerpt.readUTF(tmp);
            if (to - from <= 1)
                return excerpt.readLong();
            int cmp = compareTo(key, tmp);
            if (cmp < 0)
                return lookup0(from, mid, key);
            if (cmp > 0)
                return lookup0(mid, to, key);
            return excerpt.readLong();
        }

        public static int compareTo(CharSequence a, CharSequence b) {
            int lim = Math.min(a.length(), b.length());
            for (int k = 0; k < lim; k++) {
                char c1 = a.charAt(k);
                char c2 = b.charAt(k);
                if (c1 != c2)
                    return c1 - c2;
            }
            return a.length() - b.length();
        }

        public void close() {
            chronicle.close();
        }
    }

    private static String[] generateAndSave(String baseName, int keyCount) throws IOException {
        SortedMap<String, Long> map = generateMap(keyCount);
        saveData(map, baseName);
        ChronicleTest.deleteOnExit(baseName);

        String[] keys = map.keySet().toArray(new String[map.size()]);
        Collections.shuffle(Arrays.asList(keys));
        return keys;
    }
}

Генерирует 2 ГБ необработанных данных и выполняет миллион поисков. Он написан таким образом, что загрузка и поиск используют очень мало кучи. (

ls -l /tmp/test*
-rw-rw---- 1 peter peter 2013265920 Dec 11 13:23 /tmp/test.data
-rw-rw---- 1 peter peter  805306368 Dec 11 13:23 /tmp/test.index

/tmp/test created.
/tmp/test, size=100000000
Load of 100,000,000 records and lookup of 1,000,000 keys took 10.945 seconds

Использование поиска по хэш-таблице было бы быстрее на поиск, так как это O(1) вместо O(ln N), но более сложно реализовать.

  1. Используйте DataInputStream, обернутый вокруг BufferedInputStream, обернутый вокруг FileInputStream.

  2. Вместо по крайней мере четырех системных вызовов на итерацию, проверки длины и текущего размера и выполнения неизвестно скольких операций чтения, чтобы получить строку и long, просто вызовите readUTF() и readLong (), пока не получите исключение EOFException.