2012-12-11 89 views
2

我有以下格式的文件1.7G:加快文件讀取

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

從本質上講,字符串是一個關鍵和是HashMap中的值我感興趣的初始化之前在應用程序中運行其他任何東西

我當前的代碼是:

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(); 
     } 
    } 
+1

你的分析結果說什麼?瓶頸究竟在哪裏? –

+1

1.7G鍵值對,爲什麼你不使用數據庫而不是文件? – jlordo

+0

你想用這些數據做什麼?我有一種強烈的感覺,認爲你可能使用了一種效率低下的方法。 –

回答

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 GB的原始數據並執行百萬次查找。它的寫入方式使加載和查找使用很少的堆。 (< < 1 MB)

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),但更復雜的實現將是每查找更快。

+0

+1這加上一個內存映射文件應該提供性能,初始化時間和內存消耗的完美結合。 –

+1

如果OP無法更改文件,則可以通過創建具有這種結構的索引文件來實現此方法。 –

+1

@MarkoTopolnik我很難形容它是'完美'。更多的內存,更多的I/O,以及更多的執行時間,比OP現在所做的更多。較少的啓動時間,是的。 – EJP

4
  1. 使用繞繞一個FileInputStream包裹的BufferedInputStream包裹一個DataInputStream。

  2. 而不是至少每個迭代四個系統調用,檢查長度和當前的大小和執行誰知道有多少讀取字符串和長,只需調用readUTF()和readLong(),直到你得到一個EOFException。

+0

感謝EJP的回答和評論。我已經試過了,需要大約5分鐘才能上傳數據。我使用了DataInputStream,但我並沒有等待EOFExcpetion,而是使用了可能會減慢讀取過程的sys調用。 – DotNet

+0

@DNet它的確如此。您每次迭代都添加一個系統調用。按照我的方式嘗試。我的方式是每8k數據只有一個系統調用。有很大的區別。 – EJP

+0

沒有系統調用「可用」的總時間4分22秒。再次感謝 – DotNet