Java 技巧 49:如何從 JAR 和 zip 檔案檔案中提取 Java 資源
您是剛開始接觸 JAR 檔案嗎?這個新類可幫您解決問題!
作者:John D. Mitchell 和 Arthur Choi
摘要
將一類 Java 資源打包在一個 Java ARchive (JAR) 檔案中是縮短下載時間、增強安全性和增強可管理性的極好方法。這篇技巧說明如何很容易地從 JAR 檔案中提取資源以供您自己使用。
多數 Java 程式員都非常清楚使用 JAR 檔案將組成 Java 解決方案的各種資源(即 .class 檔案、聲音和映像)打包的優點。(如果您不熟悉 JAR 檔案,請參閱後文的參考資源部分。)剛開始使用 JAR 檔案的人常問的一個問題是:“如何從 JAR 檔案中提取映像呢?”本文將回答這個問題,並會提供一個類,這個類使從 JAR 檔案中提取任何資源變得非常簡單!
載入 GIF 映像
假定我們有一個 JAR 檔案,其中包含我們的應用程式要使用的一組 .gif 映像。下面就是使用 JarResources 訪問 JAR 檔案中的影像檔的方法:
JarResources jar = new JarResources ("Images.jar");
Image logo =
Toolkit.getDefaultToolkit().createImage (jar.getResource ("logo.gif");
這段代碼說明我們可以建立一個 JarResources 對象,並將其初始化為包含我們要使用的資源的 JAR 檔案 -- Images.jar。隨後我們使用 JarResources 的 getResource() 方法將來自 logo.gif 檔案的未經處理資料提供給 AWT Toolkit 的 createImage() 方法。
命名說明
JarResource 是一個非常簡單的樣本,它說明了如何使用 Java 1.1 所提供的各種功能來處理 JAR 和 zip 檔案檔案。
關於命名的簡要說明。Java 中的歸檔支援實際上是以流行的 zip 歸檔格式為起點的(請參閱 "Java Tip 21: Use archive files to speed up applet loading")。因此,在最初實現處理檔案檔案的 Java 支援時,所有類檔案以及諸如此類的東西並未放在 java.util.zip 包中;這些類通常以 "Zip" 開頭。但在轉向 Java 1.1 時,功能已發生了變化,檔案檔案的名稱也更具有 Java 特徵。因此,現在我們稱之為 JAR 檔案的檔案基本上是 zip 檔案。
工作方式
JarResources 類的重要資料域用來跟蹤和儲存指定 JAR 檔案的內容:
public final class JarResources {
public boolean debugOn=false;
private Hashtable htSizes=new Hashtable();
private Hashtable htJarContents=new Hashtable();
private String jarFileName;
這樣,該類的執行個體化設定 JAR 檔案的名稱,然後轉到 init() 方法完成全部實際工作。
public JarResources(String jarFileName) {
this.jarFileName=jarFileName;
init();
}
現在,init() 方法只將指定 JAR 檔案的整個內容載入到一個 hashtable(通過資源名訪問)中。
這是一個相當有用的方法,下面我們對它作進一步的分析。ZipFile 類為我們提供了對 JAR/zip 檔案頭資訊的基本存取方法。這類似於檔案系統中的目錄資訊。下面我們列出 ZipFile 中的所有條目,並用檔案中每個資源的大小添充 htSizes hashtable:
private void init() {
try {
ZipFile zf=new ZipFile(jarFileName);
Enumeration e=zf.entries();
while (e.hasMoreElements()) {
ZipEntry ze=(ZipEntry)e.nextElement();
if (debugOn) {
System.out.println(dumpZipEntry(ze));
}
htSizes.put(ze.getName(),new Integer((int)ze.getSize()));
}
zf.close();
接下來,我們使用 ZipInputStream 類訪問檔案。ZipInputStream 類完成了全部魔術,允許我們單獨讀取檔案中的每個資源。我們從檔案中讀取組成每個資源的精確位元組數,並將其儲存在 htJarContents hashtable 中,您可以通過資源名訪問這些資料:
FileInputStream fis=new FileInputStream(jarFileName);
BufferedInputStream bis=new BufferedInputStream(fis);
ZipInputStream zis=new ZipInputStream(bis);
ZipEntry ze=null;
while ((ze=zis.getNextEntry())!=null) {
if (ze.isDirectory()) {
continue;
}
if (debugOn) {
System.out.println(
"ze.getName()="+ze.getName()+","+"getSize()="+ze.getSize()
);
}
int size=(int)ze.getSize();
// -1 表示大小未知。
if (size==-1) {
size=((Integer)htSizes.get(ze.getName())).intValue();
}
byte[] b=new byte[(int)size];
int rb=0;
int chunk=0;
while (((int)size - rb) > 0) {
chunk=zis.read(b,rb,(int)size - rb);
if (chunk==-1) {
break;
}
rb+=chunk;
}
// 添加到內部資源 hashtable 中
htJarContents.put(ze.getName(),b);
if (debugOn) {
System.out.println(
ze.getName()+" rb="+rb+
",size="+size+
",csize="+ze.getCompressedSize()
);
}
}
} catch (NullPointerException e) {
System.out.println("done.");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
請注意,用來標識每個資源的名稱是檔案中資源的限定路徑名,例如,不是包中的類名 -- 即 java.util.zip 包中的 ZipEntry 類將被命名為 "java/util/zip/ZipEntry",而不是 "java.util.zip.ZipEntry"。
代碼的最後一個重要部分是簡單的測試驅動程式。該測試驅動程式是一個簡單的應用程式,它接收 JAR/zip 檔案名和資源名。它試圖發現檔案中的資源檔,然後將成功或失敗的訊息報告出來:
public static void main(String[] args) throws IOException {
if (args.length!=2) {
System.err.println(
"usage: java JarResources "
);
System.exit(1);
}
JarResources jr=new JarResources(args[0]);
byte[] buff=jr.getResource(args[1]);
if (buff==null) {
System.out.println("Could not find "+args[1]+".");
} else {
System.out.println("Found "+args[1]+ " (length="+buff.length+").");
}
}
} // JarResources 類結束。
您已瞭解了這個類。一個便於使用的類,它隱藏了使用打包在 JAR 檔案中的資源的全部棘手問題。
練習
現在您對從檔案檔案中提取資源已有了一定的認識,下面是可用來修改和擴充 JarResources 類的一些說明:
- 不在構造期間一次性載入全部內容,而要消極式載入。對於大型 JAR 檔案,構造期間可能沒有足夠的記憶體載入全部檔案。
- 不只是提供類似
getResource() 這樣的一般讀方法,我們還可提供資源特定的讀方法 -- 例如,用來返回 Java Image 對象的 getImage() 方法,用來返回 Java Class 對象的 getClass() 方法(在自訂的類載入程式的協助下),等等。如果 JAR 檔案足夠小,則我們可以根據它們的副檔名(.gif、.class 等等)預先構建全部資源。
- 某些方法應該提供關於給定 JAR 檔案本身(基本上是
ZipFile 的封裝)的資訊,包括:Jar/zip 的條目數;返回全部資源名的 Enumerator;返回特定條目長度(和其他屬性)的讀方法;允許編製索引的讀方法,這僅僅是舉幾個例子。
- 可對
JarResources 進行擴充,以供 applet 使用。通過利用 applet 參數和 URLConnection 類,就可以從網路上下載 JAR 內容,而不是將檔案作為本地檔案開啟。此外,我們還可將該類擴充為一個自訂的 Java 內容處理常式。
小結
如果您曾經渴望知道如何從 JAR 檔案中提取映像,那麼您現在已學到了一種方法。有了本技巧提供的這個新類,您就不僅可以用 JAR 檔案處理映像,而且可以將提取魔術用於 JAR 檔案中的任何資源。
作者簡介 Arthur Choi 目前是 IBM 的一位顧問程式員。他曾先後在幾個公司任職,包括 SamSung Network Laboratory 和 MITRE。他參與過的項目包括客戶機/伺服器系統、分布式對象計算和網路管理。他在各種作業系統環境下用過多種語言。他於 1981 年開始用 FORTRAN IV 和 COBOL 進行編程。後來他轉向 C 和 C++,最近兩年他一直在用 Java 工作。他最感興趣的 Java 應用是廣域網路中的資料倉儲和網際網路上的平行處理和分散式處理(使用基於代理的編程)。Reach Arthur 的電子郵件地址為 arthur.choi@javaworld.com。John Mitchell,先後做過僱員、諮詢人員,現在是自己公司的老闆,過去十年他所投資的領域包括前沿電腦軟體的開發、對其他開發人員提供建議和培訓。他的諮詢範圍包括 Java 技術、編譯器、解譯器、基於 Web 應用程式和網際網路商務等。John 是 Making Sense of Java: A Guide for Managers and the Rest of Us 一書的作者之一,並在編程雜誌上發表了許多文章。除了為 JavaWorld 撰寫 Java Tips 專欄之外,他還主持著 comp.lang.tcl.announce 和 comp.binaries.geos 新聞群組。Reach John 的電子郵件地址為 john.mitchell@javaworld.com。 |
參考資源
- 這是類檔案
JarResources.java
JarResources.java
- JARs
http://www.javasoft.com/products/jdk/1.1/docs/guide/jar/index.html
- 有關 Java 的歸檔支援的詳細資料,請參閱 "Java Tip 21: Use archive files to speed up applet loading"
http://www.javaworld.com/javatips/jw-javatip21.html