[java] JAR 파일 내의 파일을 나열하는 방법은 무엇입니까?

디렉토리에서 모든 파일을 읽는 코드가 있습니다.

    File textFolder = new File("text_directory");

    File [] texFiles = textFolder.listFiles( new FileFilter() {
           public boolean accept( File file ) {
               return file.getName().endsWith(".txt");
           }
    });

잘 작동합니다. “text_directory”디렉토리에서 “.txt”로 끝나는 모든 파일로 배열을 채 웁니다.

JAR 파일 에서 비슷한 방식으로 디렉토리의 내용을 어떻게 읽을 수 있습니까?

그래서 제가 정말로하고 싶은 것은 내 JAR 파일 내의 모든 이미지를 나열하는 것입니다. 그래서 다음과 같이로드 할 수 있습니다.

ImageIO.read(this.getClass().getResource("CompanyLogo.png"));

( “CompanyLogo”가 “하드 코딩”되어 있기 때문에 작동하지만 JAR 파일 내의 이미지 수는 가변 길이가 10 ~ 200 개일 수 있습니다.)

편집하다

그래서 내 주요 문제는 다음과 같습니다 . 내 메인 클래스 가있는 JAR 파일이름 을 아는 방법 ?

나는 그것을 사용하여 읽을 수 있었다 java.util.Zip.

내 구조는 다음과 같습니다.

그들은 다음과 같습니다.

my.jar!/Main.class
my.jar!/Aux.class
my.jar!/Other.class
my.jar!/images/image01.png
my.jar!/images/image02a.png
my.jar!/images/imwge034.png
my.jar!/images/imagAe01q.png
my.jar!/META-INF/manifest 

지금은 다음을 사용하여 “images / image01.png”인스턴스를로드 할 수 있습니다.

    ImageIO.read(this.getClass().getResource("images/image01.png));

하지만 파일 이름을 알고 있기 때문에 나머지 파일을 동적으로로드해야합니다.



답변

CodeSource src = MyClass.class.getProtectionDomain().getCodeSource();
if (src != null) {
  URL jar = src.getLocation();
  ZipInputStream zip = new ZipInputStream(jar.openStream());
  while(true) {
    ZipEntry e = zip.getNextEntry();
    if (e == null)
      break;
    String name = e.getName();
    if (name.startsWith("path/to/your/dir/")) {
      /* Do something with this entry. */
      ...
    }
  }
}
else {
  /* Fail... */
}

Java 7에서는 FileSystemJAR (zip) 파일에서을 생성 한 다음 NIO의 디렉토리 검색 및 필터링 메커니즘을 사용하여 검색 할 수 있습니다. 이렇게하면 JAR 및 “폭발 된”디렉토리를 처리하는 코드를 더 쉽게 작성할 수 있습니다.


답변

IDE 및 .jar 파일 모두에서 작동하는 코드 :

import java.io.*;
import java.net.*;
import java.nio.file.*;
import java.util.*;
import java.util.stream.*;

public class ResourceWalker {
    public static void main(String[] args) throws URISyntaxException, IOException {
        URI uri = ResourceWalker.class.getResource("/resources").toURI();
        Path myPath;
        if (uri.getScheme().equals("jar")) {
            FileSystem fileSystem = FileSystems.newFileSystem(uri, Collections.<String, Object>emptyMap());
            myPath = fileSystem.getPath("/resources");
        } else {
            myPath = Paths.get(uri);
        }
        Stream<Path> walk = Files.walk(myPath, 1);
        for (Iterator<Path> it = walk.iterator(); it.hasNext();){
            System.out.println(it.next());
        }
    }
}


답변

erickson의 대답 은 완벽하게 작동했습니다.

다음은 작동 코드입니다.

CodeSource src = MyClass.class.getProtectionDomain().getCodeSource();
List<String> list = new ArrayList<String>();

if( src != null ) {
    URL jar = src.getLocation();
    ZipInputStream zip = new ZipInputStream( jar.openStream());
    ZipEntry ze = null;

    while( ( ze = zip.getNextEntry() ) != null ) {
        String entryName = ze.getName();
        if( entryName.startsWith("images") &&  entryName.endsWith(".png") ) {
            list.add( entryName  );
        }
    }

 }
 webimages = list.toArray( new String[ list.size() ] );

그리고 다음과 같이 내로드 방법을 수정했습니다.

File[] webimages = ...
BufferedImage image = ImageIO.read(this.getClass().getResource(webimages[nextIndex].getName() ));

이에:

String  [] webimages = ...

BufferedImage image = ImageIO.read(this.getClass().getResource(webimages[nextIndex]));


답변

여러 가지 이유로 매우 안전하지 않은 솔루션이기 때문에 acheron55의 답변 을 확장하고 싶습니다 .

  1. FileSystem개체를 닫지 않습니다 .
  2. FileSystem개체가 이미 존재 하는지 확인하지 않습니다 .
  3. 스레드로부터 안전하지 않습니다.

이것은 다소 안전한 해결책입니다.

private static ConcurrentMap<String, Object> locks = new ConcurrentHashMap<>();

public void walk(String path) throws Exception {

    URI uri = getClass().getResource(path).toURI();
    if ("jar".equals(uri.getScheme()) {
        safeWalkJar(path, uri);
    } else {
        Files.walk(Paths.get(path));
    }
}

private void safeWalkJar(String path, URI uri) throws Exception {

    synchronized (getLock(uri)) {
        // this'll close the FileSystem object at the end
        try (FileSystem fs = getFileSystem(uri)) {
            Files.walk(fs.getPath(path));
        }
    }
}

private Object getLock(URI uri) {

    String fileName = parseFileName(uri);
    locks.computeIfAbsent(fileName, s -> new Object());
    return locks.get(fileName);
}

private String parseFileName(URI uri) {

    String schemeSpecificPart = uri.getSchemeSpecificPart();
    return schemeSpecificPart.substring(0, schemeSpecificPart.indexOf("!"));
}

private FileSystem getFileSystem(URI uri) throws IOException {

    try {
        return FileSystems.getFileSystem(uri);
    } catch (FileSystemNotFoundException e) {
        return FileSystems.newFileSystem(uri, Collections.<String, String>emptyMap());
    }
}   

파일 이름을 동기화 할 필요가 없습니다. 매번 동일한 객체에서 동기화 할 수 있습니다 (또는 메서드를 만들 수 있습니다 synchronized). 순전히 최적화입니다.

FileSystem동일한 파일에 대해 인터페이스 를 사용하는 코드에 다른 부분이있을 수 있고 (단일 스레드 응용 프로그램에서도) 간섭을 일으킬 수 있기 때문에 이것이 여전히 문제가되는 해결책이라고 말하고 싶습니다 .
또한 nulls를 확인하지 않습니다 (예 : on getClass().getResource().

이 특정 Java NIO 인터페이스는 스레드로부터 안전하지 않은 전역 / 싱글 톤 리소스를 도입하고 문서가 매우 모호합니다 (제공 업체별 구현으로 인해 많은 알 수 없음). 결과는 다른 FileSystem공급자 (JAR 아님)에 따라 다를 수 있습니다 . 아마 그럴만 한 이유가있을 것입니다. 모르겠습니다. 구현을 조사하지 않았습니다.


답변

그래서 내 주된 문제는 내 메인 클래스가 사는 항아리의 이름을 아는 방법입니다.

프로젝트가 Jar에 압축되어 있다고 가정하면 (반드시 사실은 아닙니다!), ClassLoader.getResource () 또는 findResource ()를 클래스 이름 (.class가 뒤 따름)과 함께 사용하여 지정된 클래스가 포함 된 jar를 가져올 수 있습니다. 반환되는 URL에서 jar 이름을 구문 분석해야합니다 (그다지 어렵지 않음). 독자를위한 연습으로 남겨 둘 것입니다. 🙂

클래스가 항아리의 일부가 아닌 경우를 테스트해야합니다.


답변

Java 7에 대한 acheron55의 답변 을 포팅 하고 FileSystem객체를 닫았습니다 . 이 코드는 IDE, jar 파일 및 Tomcat 7과의 전쟁 내의 jar에서 작동합니다. 그러나 JBoss 7과의 전쟁에서 항아리에서 작동 하지 않습니다 ( 이 게시물FileSystemNotFoundException: Provider "vfs" not installed 도 참조하십시오 ). 또한 원본 코드와 마찬가지로 errr이 제안한 것처럼 스레드로부터 안전하지 않습니다 . 이러한 이유로 저는이 솔루션을 포기했습니다. 그러나 이러한 문제를 받아 들일 수 있다면 기성 코드는 다음과 같습니다.

import java.io.IOException;
import java.net.*;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Collections;

public class ResourceWalker {

    public static void main(String[] args) throws URISyntaxException, IOException {
        URI uri = ResourceWalker.class.getResource("/resources").toURI();
        System.out.println("Starting from: " + uri);
        try (FileSystem fileSystem = (uri.getScheme().equals("jar") ? FileSystems.newFileSystem(uri, Collections.<String, Object>emptyMap()) : null)) {
            Path myPath = Paths.get(uri);
            Files.walkFileTree(myPath, new SimpleFileVisitor<Path>() {
                @Override
                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                    System.out.println(file);
                    return FileVisitResult.CONTINUE;
                }
            });
        }
    }
}


답변

다음은 “패키지에서 모든 JUnits 실행”을 위해 작성한 방법입니다. 필요에 맞게 조정할 수 있어야합니다.

private static void findClassesInJar(List<String> classFiles, String path) throws IOException {
    final String[] parts = path.split("\\Q.jar\\\\E");
    if (parts.length == 2) {
        String jarFilename = parts[0] + ".jar";
        String relativePath = parts[1].replace(File.separatorChar, '/');
        JarFile jarFile = new JarFile(jarFilename);
        final Enumeration<JarEntry> entries = jarFile.entries();
        while (entries.hasMoreElements()) {
            final JarEntry entry = entries.nextElement();
            final String entryName = entry.getName();
            if (entryName.startsWith(relativePath)) {
                classFiles.add(entryName.replace('/', File.separatorChar));
            }
        }
    }
}

편집 : 아,이 경우이 스 니펫도 필요할 수 있습니다 (동일한 사용 사례 :))

private static File findClassesDir(Class<?> clazz) {
    try {
        String path = clazz.getProtectionDomain().getCodeSource().getLocation().getFile();
        final String codeSourcePath = URLDecoder.decode(path, "UTF-8");
        final String thisClassPath = new File(codeSourcePath, clazz.getPackage().getName().repalce('.', File.separatorChar));
    } catch (UnsupportedEncodingException e) {
        throw new AssertionError("impossible", e);
    }
}