0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

JavaでZIPからFileSystemを呼ぼうとして てこずった話

Last updated at Posted at 2020-04-09

Java7以降で「ZIP」や「JAR」形式のファイルを「FileSystem」として開けます。
(Java上ではZIPも「JAR」として扱われます。)

ところがあるときどうやってもうまくいかなかった。
    原因というか結論:「実行環境次第で使えない時がある(?)」

備忘録として記事にします。
(環境:Win10で古めのJava8)

2020/6/29:Java9以降のjlinkについて追記 

### 1. FileSystems.newFileSystem の使い方
まず、前提となる使用法を記しときます。
 ・FileSystems.newFileSystem()というメソッドを使います。
  ― 対象ファイルの指定は Path か URI が使えます。
  ― 既存ファイル、新規ファイルどちらでもいけます。

1.1 Path で指定する方法


    final Path zipfilePath = Paths.get("C:/a.zip");
	
    try{
        FileSystem fs = FileSystems.newFileSystem(zipfilePath, null);
    }catch(Exception e){
        e.printStackTrace();
    }
	

ネットで探すと、
    ↓ このように第2引数にクラスローダを渡すサンプルが多い。
    FileSystem fs = FileSystems.newFileSystem(zipfilePath, ClassLoader.getSystemClassLoader() );
この「ClassLoader.getSystemClassLoader()」がわけわからないからあきらめてURI指定に走る使用者が多い様子。
けれどソースを覗いた感じでは「null」で問題ない。

1.2 URI で指定する方法

    final Path zipfilePath = Paths.get("C:/a.zip");
    Map<String, Object> env = new HashMap<>();
    //env.put("create", "true"); ////    Enable creation for no-exists path.
    //env.put("encoding", "UTF-8");    
    try{
        FileSystem fs = FileSystems.newFileSystem( URI.create("jar:" + zipfilePath.toUri().toString() ), env );  //  スキーム「jar」を指定する
    }catch(Exception e){
        e.printStackTrace();
    }

URI による指定の場合は第2引数に Map インスタンスを渡します。ターゲットが既存ファイルの場合は空のMapでよい。
それでもって URI のほうでスキーム「jar」を指定しとく。

### 2. 実行したら失敗した(環境依存)

Path指定のときのエラーメッセージ
java.nio.file.ProviderNotFoundException: Provider not found
URI指定のときのエラーメッセージ
java.nio.file.ProviderNotFoundException: Provider "jar" not found

特に URI で指定していると(URIの使い方なんて自信ないし)
どこでミスってるかわけわからない。。。
諦めがちです。

2.1 なんで失敗したのか?

原因がわかって後から見直すと、エラーメッセージを素直に読んだその通りなのでした。
「jar (すなわち zip) を扱えるプロバイダ」が無い。

ここで、「プロバイダ」として実際になにが存在しているのか確認してみます。

ファイルシステムプロバイダを表示する
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.spi.FileSystemProvider;

class ZipAsFileSystem{

	public static final void main(String[] args) {

		for (FileSystemProvider provider: FileSystemProvider.installedProviders()) {
			System.out.println("provider = "+provider);
			System.out.println("scheme = "+provider.getScheme());
		}
	}
}

まず、エラーがでない環境の実行結果を示します。

実行結果 jdk1.8.0_121
    provider = sun.nio.fs.WindowsFileSystemProvider@28d93b30
        scheme = file

    provider = com.sun.nio.zipfs.ZipFileSystemProvider@1b6d3586
        scheme = jar

↑ これが正常。

つぎにエラーが出る環境の実行結果:

実行結果 jre1.8.0_121
    provider = sun.nio.fs.WindowsFileSystemProvider@45ee12a7
        scheme = file

↑ こちらは「ZipFileSystemProvider」が見つかってない。
だからZIPをハンドルできない。
どうりですね。


上の2つの環境は どちらも同じバージョンのOracleのjavaです。
違いは
・JDK同梱のランタイム(正常動作)
・JREとして配布されているランタイム(失敗)

どうして JRE 側に「ZipFileSystemProvider」ないのかはわからない。
このバージョン特有なのか、Oracle特有の事情なのか、あるいはこの記事の筆者が何か余計なことをしたか不明です。

### 3. 結論
とにかく
・「ZipFileSystemProvider」が実行環境中に存在していないときがある。
・そのせいでZIPをFileSystemできないときがある。
ということでした。

2020/6/29 追記

### 4. 対処法

Java8

ZipFileSystemProviderは「zipfs.jar」に含まれます。
Java8の場合はランタイムのlib/ext/zipfs.jar
があるかどうか確認、あるいは入れ替えなどで対策になるかも(未確認)

Java9以降

(注:実際に試した環境はLibericaJDK11です。)
Java9以降のランタイムで
「Provider "jar" not found」
が出る場合は
‪C:\Program Files\BellSoft\LibericaJDK-11\jmods\jdk.zipfs.jmod
のように「*.zipfs.jmod」があるか確認するとよい(はず。)

Jdeps および Jlink 使用時

jdepsとjlinkでランタイム(JRE)を抽出作成する際のはなし。

ZipFileSystemProviderを使用するJARをターゲットとして
jdepsで依存モジュールをリストした際、
「*.zipfs.jmod」がリストに載りませんでした。

そのままjlinkでランタイムを作ってターゲットJARを動かした使ったところ「Provider "jar" not found」エラー。

jlink 実行時引数のモジュールリストに「jdk.zipfs」と追加することで解決。

0
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?