2010-05-04 43 views
15

我需要在Java應用程序中包含大約1 MB的數據,以便在其他源代碼中快速輕鬆地進行訪問。我的主要背景是不是Java,所以我最初的想法是將數據直接轉換爲Java源代碼,定義常量數組的1M字節,類(而不是C++結構)等,像這樣:Java中的大量常量

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

然而,似乎Java不支持這樣的結構。它是否正確?如果是的話,這個問題的最佳解決方案是什麼?

注意:數據由2個表組成,每個數據大約有50000條記錄,這些記錄將以各種方式進行搜索。這可能會稍後需要一些索引,這樣可以保存更多的記錄,大概有100萬條記錄。我希望應用程序啓動速度非常快,而不會迭代這些記錄。

回答

22

我個人不會把它放在源代碼形式。

相反,將數據以適當的原始格式包含在jar文件中(我假設您將打包應用程序或庫)並使用Class.getResourceAsStreamClassLoader.getResourceAsStream加載它。

你可能很希望一個類封裝加載,緩存和提供這些數據 - 但我沒有看到將它轉換成源代碼很多好處。

+0

這是什麼Java的標準數據格式? – 2010-05-04 07:22:51

+2

@Lars:對於K/V對,* key = value *在'.properties'文件中(對於'Properties'類檢查javadoc),僅列出天空的限制,儘管我建議你使用簡單的您的需求。如果你願意,也許是XML,但通常這不是必須的。 – Esko 2010-05-04 07:25:35

+0

@Lars D:這真的取決於你的數據需要什麼。屬性文件適用於鍵/值對,但如果您有很多數字數據,則效率可能不是非常高。 (對於XML也是如此。)有許多序列化庫可能會讓您的生活更輕鬆,或者只是使用您自己的自定義數據格式。 – 2010-05-04 07:27:45

3

一個想法是你使用枚舉數,但我不確定這是否適合你的實現,也取決於你打算如何使用數據。

Stuff someThing = Stuff.HAMBURGER; 
int hamburgerA = Stuff.HAMBURGER.getA() // = 23012 

另一個想法是使用static初始化設置類的私有字段:

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; 
} 

}

這些就好像訪問。

+0

@Lars,除去enum構造函數調用的參數 – aioobe 2010-05-04 07:13:44

+0

@aioobe,非常感謝!除非你想把這些數字作爲字符串存儲(然後私有構造函數需要相應地改變),否則「沒有」需要。 – 2010-05-04 07:16:02

+0

@Lars D(其他Lars),像這樣你永遠不需要顯式地調用構造函數,你只需要像上面的例子那樣定義每個元素。 – 2010-05-04 07:18:46

0

您也可以聲明一個靜態類(或一組靜態類),將需要的值暴露爲方法。畢竟,您希望您的代碼能夠找到給定名稱的值,並且不希望該值發生更改。

這樣:位置= MyLibOfConstants.returnHamburgerLocation()郵編

而且你可以在哈希表中與惰性初始模式這個東西保存,如果你件事計算它的飛行將是浪費時間。

7

由於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; 
    } 
} 

注:掃描儀因爲它有一個簡單的界面,所以不要試圖使用它,堅持使用某種形式的BufferedReader和Split或StringTokenizer。)

如果將數據轉換爲數據,當然可以提高效率二進制格式。在這種情況下,你可以使用DataInputStream的(但不要忘記去通過一些BufferedInputStreamBufferedReader

根據您希望如何訪問數據時,你可能會更好存儲在哈希記錄-map(HashMap<Integer, DataRecord>)(具有ij作爲關鍵)。

如果您希望在JVM加載類文件本身的同時加載數據(粗略地!),您可以執行讀取/初始化操作,而不是在方法中,但在static { ... }中進行了封裝。


對於內存映射方法,看看在Java中java.nio.channels -package。特別是該方法

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

完整代碼的例子可以發現here


丹·伯恩斯坦(DalvikVM的主要開發人員)解釋this talk解決您的問題(看看周圍0:30:00)。不過,我懷疑這個解決方案適用於多達一兆字節的數據。

+0

加載數據不是一個選項,它會太慢。內存映射會更有意義,但我認爲我的目標平臺上沒有內存映射(Android) – 2010-05-04 07:25:04

+0

查看http://www.developer.com/java/other/article.php/ 1548681 /介紹到內存映射IO-in-Java.htm – aioobe 2010-05-04 07:27:09

+0

類文件*可以*大於64k - 它是單個方法(和初始化塊)不可能的。 – 2010-05-04 07:49:26

1

將數據直接轉換爲Java源代碼,定義常量陣列的1M字節,類

注意,有上的類和它們的結構[參考JVM Spec尺寸嚴格的約束。

0

不是你需要的緩存嗎? 作爲類被加載到內存中,並沒有真正限制到一個定義的大小,應該像使用常量一樣快...... 實際上,它甚至可以用某種索引搜索數據(例如使用對象哈希碼...) 例如,您可以創建所有數據數組(例如{23012,22,「Hamburger」}),然後創建3個散列圖: map1.put(23012,hamburgerItem); map2.put(22,hamburgerItem); map3.put(「Hamburger」,hamburgerItem); 這樣你就可以根據你的參數在地圖上快速搜索... (但是,只有當你的鑰匙在地圖上是唯一的,這纔有效...這只是一個例子,可以激發你)

在工作中,我們有一個非常大的webapp(80個weblogic實例),它幾乎是我們所做的:無處不在。從數據庫中countrylist,創建緩存...

有許多不同類型的高速緩存,您應檢查該鏈接並選擇你所需要的... http://en.wikipedia.org/wiki/Cache_algorithms

+0

主要標準是數據存在於程序啓動時,所以我不需要遍歷它或解析它。我不確定緩存如何幫助您做到這一點? – 2010-05-04 07:48:34

+0

@Lars:你想要什麼都沒有意義。加載一個Java類涉及迭代字節代碼並解析它。如果沒有迭代並以某種形式解析數據,就不可能加載任何類型的數據。這只是一個這些步驟花費多少的問題。 – 2010-05-04 07:52:37

+0

@邁克爾:好點。 – 2010-05-04 08:37:36

1

這是你如何定義它Java中,如果我明白你所追求的:

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

哥倫布蛋...我不知道這是怎麼看字節碼。 – 2010-05-04 08:40:07

+0

只需執行'javac TheAboveCode.java && javap -v TheAboveCode'記住Android使用完全不同的文件格式(.dex) – aioobe 2010-05-04 08:49:08

+0

該分配作爲構造函數的一部分執行,其大小限制爲64k字節,這是違反。其他方法也被限制爲64k字節,所以至少需要16種方法才能實現。 – 2010-05-04 09:21:55

3

將數據放入源可實際上不是最快的解決方案,而不是由一個長鏡頭。加載一個Java類是相當複雜和緩慢的(至少在一個執行字節碼驗證的平臺上,對Android沒有把握)。

執行此操作的最快方法是定義您自己的二進制索引格式。然後,您可以將其作爲byte[](可能使用內存映射)或甚至是RandomAccessFile讀取,但不會以任何方式解釋它,直到您開始訪問它。這成本將是訪問它的代碼的複雜性。對於固定大小的記錄,通過二進制搜索訪問的記錄的排序列表仍然非常簡單,但其他任何內容都會變得很難看。

雖然在這之前,你確定這不是過早的優化嗎?最簡單的(也許還是相當快的)解決方案將是序列化一個Map,List或者數組 - 你試過這個並確定它實際上太慢了嗎?

0

Java序列化聽起來像是需要解析的東西......不好。是否有某種標準格式用於將數據存儲到數據流中,可以使用標準API讀取/查看數據而不解析數據?

如果您要在代碼中創建數據,那麼它將全部在第一次使用時加載。這不太可能比從一個單獨的文件中加載效率更高 - 以及解析類文件中的數據,JVM必須驗證並編譯字節碼才能創建每個對象一百萬次,而不僅僅是一次從循環加載它。

如果你想要隨機訪問,並且不能使用內存映射文件,那麼有一個RandomAccessFile可能工作。您需要在開始時加載索引,或者您需要使條目具有固定長度。

您可能想要檢查HDF5庫是否在您的平臺上運行;儘管如此,對於這樣一個簡單和小的數據集可能是過度的。

1

看起來你打算編寫自己的輕量級數據庫。
如果你可以限制字符串的長度,以一個現實的最大尺寸以下可能的工作:

  • 寫每進入一個二進制文件,該條目具有相同的大小,所以你浪費與每個條目的字節(int a,int b,int stringsize,string,padding)
  • 要讀取一個條目以隨機訪問文件的形式打開文件,請將索引與條目長度相乘以獲取偏移量並查找位置。
  • 將字節放入bytebuffer中並讀取值,String必須用String(byte [],int start,int length,Charset)轉換。

如果您不能限制塊轉儲字符串的長度在一個附加文件中,並且只將偏移量存儲在您的表中。這需要額外的文件訪問權限,並且難以修改數據。
有關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字節偏移量。
0

我會建議使用資產來存儲這些數據。