从类路径目录获取资源列表

我正在寻找一种方法来从给定的类路径目录中获得所有资源名称的列表,类似于方法List<String> getResourceNames (String directoryName)

例如,给定一个类路径目录x/y/z,其中包含文件a.htmlb.htmlc.html和子目录dgetResourceNames("x/y/z")应该返回一个包含以下字符串的List<String>:

它应该同时适用于文件系统和jar中的资源。

我知道我可以用__abc0、__abc1和__abc2编写一个简短的代码片段,但我不想白费力气。我的问题是,给定现有的公开可用的库,实现getResourceNames的最快方法是什么?Spring和Apache Commons栈都是可行的。

322464 次浏览

代码
强烈的> < /源:forums.devx.com/showthread.php ? t = 153784

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;


/**
* list resources available from the classpath @ *
*/
public class ResourceList{


/**
* for all elements of java.class.path get a Collection of resources Pattern
* pattern = Pattern.compile(".*"); gets all resources
*
* @param pattern
*            the pattern to match
* @return the resources in the order they are found
*/
public static Collection<String> getResources(
final Pattern pattern){
final ArrayList<String> retval = new ArrayList<String>();
final String classPath = System.getProperty("java.class.path", ".");
final String[] classPathElements = classPath.split(System.getProperty("path.separator"));
for(final String element : classPathElements){
retval.addAll(getResources(element, pattern));
}
return retval;
}


private static Collection<String> getResources(
final String element,
final Pattern pattern){
final ArrayList<String> retval = new ArrayList<String>();
final File file = new File(element);
if(file.isDirectory()){
retval.addAll(getResourcesFromDirectory(file, pattern));
} else{
retval.addAll(getResourcesFromJarFile(file, pattern));
}
return retval;
}


private static Collection<String> getResourcesFromJarFile(
final File file,
final Pattern pattern){
final ArrayList<String> retval = new ArrayList<String>();
ZipFile zf;
try{
zf = new ZipFile(file);
} catch(final ZipException e){
throw new Error(e);
} catch(final IOException e){
throw new Error(e);
}
final Enumeration e = zf.entries();
while(e.hasMoreElements()){
final ZipEntry ze = (ZipEntry) e.nextElement();
final String fileName = ze.getName();
final boolean accept = pattern.matcher(fileName).matches();
if(accept){
retval.add(fileName);
}
}
try{
zf.close();
} catch(final IOException e1){
throw new Error(e1);
}
return retval;
}


private static Collection<String> getResourcesFromDirectory(
final File directory,
final Pattern pattern){
final ArrayList<String> retval = new ArrayList<String>();
final File[] fileList = directory.listFiles();
for(final File file : fileList){
if(file.isDirectory()){
retval.addAll(getResourcesFromDirectory(file, pattern));
} else{
try{
final String fileName = file.getCanonicalPath();
final boolean accept = pattern.matcher(fileName).matches();
if(accept){
retval.add(fileName);
}
} catch(final IOException e){
throw new Error(e);
}
}
}
return retval;
}


/**
* list the resources that match args[0]
*
* @param args
*            args[0] is the pattern to match, or list all resources if
*            there are no args
*/
public static void main(final String[] args){
Pattern pattern;
if(args.length < 1){
pattern = Pattern.compile(".*");
} else{
pattern = Pattern.compile(args[0]);
}
final Collection<String> list = ResourceList.getResources(pattern);
for(final String name : list){
System.out.println(name);
}
}
}

如果你正在使用Spring,请查看< >强PathMatchingResourcePatternResolver < / >强

自定义扫描

实现您自己的扫描器。例如:

(评论中提到了此解决方案的局限性)

private List<String> getResourceFiles(String path) throws IOException {
List<String> filenames = new ArrayList<>();


try (
InputStream in = getResourceAsStream(path);
BufferedReader br = new BufferedReader(new InputStreamReader(in))) {
String resource;


while ((resource = br.readLine()) != null) {
filenames.add(resource);
}
}


return filenames;
}


private InputStream getResourceAsStream(String resource) {
final InputStream in
= getContextClassLoader().getResourceAsStream(resource);


return in == null ? getClass().getResourceAsStream(resource) : in;
}


private ClassLoader getContextClassLoader() {
return Thread.currentThread().getContextClassLoader();
}

Spring框架

使用Spring Framework中的PathMatchingResourcePatternResolver

Ronmamo反射

对于巨大的CLASSPATH值,其他技术在运行时可能会很慢。一个更快的解决方案是使用ronmamo的反射API,它在编译时预编译搜索。

如果你使用apache commonsIO,你可以使用文件系统(可选扩展过滤器):

Collection<File> files = FileUtils.listFiles(new File("directory/"), null, false);

对于资源/类路径:

List<String> files = IOUtils.readLines(MyClass.class.getClassLoader().getResourceAsStream("directory/"), Charsets.UTF_8);

如果您不知道“directoy/”是在文件系统中还是在资源中,您可以添加一个

if (new File("directory/").isDirectory())

if (MyClass.class.getClassLoader().getResource("directory/") != null)

在调用和组合使用两者之前…

所以在PathMatchingResourcePatternResolver方面,这是代码中需要的:

@Autowired
ResourcePatternResolver resourceResolver;


public void getResources() {
resourceResolver.getResources("classpath:config/*.xml");
}

结合了罗伯的回答。

final String resourceDir = "resourceDirectory/";
List<String> files = IOUtils.readLines(Thread.currentThread().getClass().getClassLoader().getResourceAsStream(resourceDir), Charsets.UTF_8);


for (String f : files) {
String data = IOUtils.toString(Thread.currentThread().getClass().getClassLoader().getResourceAsStream(resourceDir + f));
// ... process data
}

使用反射

获取类路径上的所有内容:

Reflections reflections = new Reflections(null, new ResourcesScanner());
Set<String> resourceList = reflections.getResources(x -> true);

另一个例子-从some.package中获取所有扩展名为. csv的文件:

Reflections reflections = new Reflections("some.package", new ResourcesScanner());
Set<String> resourceList = reflections.getResources(Pattern.compile(".*\\.csv"));

基于上面@rob的信息,我创建了一个实现,我将其发布到公共领域:

private static List<String> getClasspathEntriesByPath(String path) throws IOException {
InputStream is = Main.class.getClassLoader().getResourceAsStream(path);


StringBuilder sb = new StringBuilder();
while (is.available()>0) {
byte[] buffer = new byte[1024];
sb.append(new String(buffer, Charset.defaultCharset()));
}


return Arrays
.asList(sb.toString().split("\n"))          // Convert StringBuilder to individual lines
.stream()                                   // Stream the list
.filter(line -> line.trim().length()>0)     // Filter out empty lines
.collect(Collectors.toList());              // Collect remaining lines into a List again
}

虽然我不会期望getResourcesAsStream在目录上像那样工作,但它确实做到了,而且工作得很好。

Spring frameworkPathMatchingResourcePatternResolver对于这些事情非常棒:

private Resource[] getXMLResources() throws IOException
{
ClassLoader classLoader = MethodHandles.lookup().getClass().getClassLoader();
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(classLoader);


return resolver.getResources("classpath:x/y/z/*.xml");
}

Maven的依赖:

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>LATEST</version>
</dependency>

这应该可以工作(如果spring不是一个选项):

public static List<String> getFilenamesForDirnameFromCP(String directoryName) throws URISyntaxException, UnsupportedEncodingException, IOException {
List<String> filenames = new ArrayList<>();


URL url = Thread.currentThread().getContextClassLoader().getResource(directoryName);
if (url != null) {
if (url.getProtocol().equals("file")) {
File file = Paths.get(url.toURI()).toFile();
if (file != null) {
File[] files = file.listFiles();
if (files != null) {
for (File filename : files) {
filenames.add(filename.toString());
}
}
}
} else if (url.getProtocol().equals("jar")) {
String dirname = directoryName + "/";
String path = url.getPath();
String jarPath = path.substring(5, path.indexOf("!"));
try (JarFile jar = new JarFile(URLDecoder.decode(jarPath, StandardCharsets.UTF_8.name()))) {
Enumeration<JarEntry> entries = jar.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
String name = entry.getName();
if (name.startsWith(dirname) && !dirname.equals(name)) {
URL resource = Thread.currentThread().getContextClassLoader().getResource(name);
filenames.add(resource.toString());
}
}
}
}
}
return filenames;
}

使用Spring很简单。无论是一个文件,文件夹,甚至是多个文件,都有可能通过注入来实现。

这个例子演示了插入位于x/y/z文件夹中的多个文件。

import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;


@Service
public class StackoverflowService {
@Value("classpath:x/y/z/*")
private Resource[] resources;


public List<String> getResourceNames() {
return Arrays.stream(resources)
.map(Resource::getFilename)
.collect(Collectors.toList());
}
}

它确实适用于文件系统中的资源以及jar中的资源。

我认为你可以利用[Zip文件系统提供商][1]来实现这一点。当使用FileSystems.newFileSystem时,看起来可以将ZIP中的对象视为“常规”文件。

在上面的链接文档中:

在传递给FileSystems.newFileSystem方法的java.util.Map对象中指定zip文件系统的配置选项。有关Zip文件系统特定于提供程序的配置属性的信息,请参阅[Zip文件系统属性][2]主题。

一旦你有了一个zip文件系统的实例,你就可以调用[java.nio.file.FileSystem][3]和[java.nio.file.Path][4]类的方法来执行诸如复制、移动和重命名文件以及修改文件属性等操作。

jdk.zipfs模块在[Java 11 states][5]中的文档:

zip文件系统提供程序将zip或JAR文件视为文件系统,并提供操作文件内容的能力。如果已经安装,zip文件系统提供程序可以由[FileSystems.newFileSystem][6]创建。

下面是我利用您的示例资源做的一个虚构的示例。注意,.zip.jar,但是你可以调整你的代码来代替使用类路径资源:

设置

cd /tmp
mkdir -p x/y/z
touch x/y/z/{a,b,c}.html
echo 'hello world' > x/y/z/d
zip -r example.zip x

Java

import java.io.IOException;
import java.net.URI;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.util.Collections;
import java.util.stream.Collectors;


public class MkobitZipRead {


public static void main(String[] args) throws IOException {
final URI uri = URI.create("jar:file:/tmp/example.zip");
try (
final FileSystem zipfs = FileSystems.newFileSystem(uri, Collections.emptyMap());
) {
Files.walk(zipfs.getPath("/")).forEach(path -> System.out.println("Files in zip:" + path));
System.out.println("-----");
final String manifest = Files.readAllLines(
zipfs.getPath("x", "y", "z").resolve("d")
).stream().collect(Collectors.joining(System.lineSeparator()));
System.out.println(manifest);
}
}


}

输出

Files in zip:/
Files in zip:/x/
Files in zip:/x/y/
Files in zip:/x/y/z/
Files in zip:/x/y/z/c.html
Files in zip:/x/y/z/b.html
Files in zip:/x/y/z/a.html
Files in zip:/x/y/z/d
-----
hello world

这两个答案都不适合我,即使我把我的资源放在资源文件夹里,并遵循上面的答案。真正让人觉得好笑的是:

@Value("file:*/**/resources/**/schema/*.json")
private Resource[] resources;

目前列出类路径中所有资源的最健壮的机制是在ClassGraph中使用此模式,因为它处理尽可能广泛的类路径规范机制,包括新的JPMS模块系统。(我是ClassGraph的作者。)

List<String> resourceNames;
try (ScanResult scanResult = new ClassGraph().acceptPaths("x/y/z").scan()) {
resourceNames = scanResult.getAllResources().getNames();
}

我的方法,没有Spring,在单元测试中使用:

URI uri = TestClass.class.getResource("/resources").toURI();
Path myPath = Paths.get(uri);
Stream<Path> walk = Files.walk(myPath, 1);
for (Iterator<Path> it = walk.iterator(); it.hasNext(); ) {
Path filename = it.next();
System.out.println(filename);
}

扩展上面的卢克·哈钦森答案,使用他的ClassGraph库,我能够轻松地获得一个资源文件夹中所有文件的列表,几乎不需要任何努力。

假设在资源文件夹中,有一个名为MyImages的文件夹。下面是获取该文件夹中所有文件的URL列表的简单方法:

import io.github.classgraph.ClassGraph;
import io.github.classgraph.ResourceList;
import io.github.classgraph.ScanResult;


public static LinkedList<URL> getURLList (String folder) {
LinkedList<URL> urlList    = new LinkedList<>();
ScanResult      scanResult = new ClassGraph().enableAllInfo().scan();
ResourceList    resources  = scanResult.getAllResources();
for (URL url : resources.getURLs()) {
if (url.toString().contains(folder)) {
urlList.addLast(url);
}
}
return urlList;
}

然后你只需这样做:

LinkedList<URL> myURLFileList = getURLList("MyImages");

这些url可以被加载到流中,或者使用Apache的FileUtils将文件复制到其他地方,就像这样:

String outPath = "/My/Output/Path";
for(URL url : myURLFileList) {
FileUtils.copyURLToFile(url, new File(outPath, url.getFile()));
}

我认为ClassGraph是一个非常灵巧的库,可以使这样的任务非常简单和容易理解。