لدي هذا الرمز الذي يقرأ جميع الملفات من الدليل.
File textFolder = new File("text_directory");
File [] texFiles = textFolder.listFiles( new FileFilter() {
public boolean accept( File file ) {
return file.getName().endsWith(".txt");
}
});
إنه عمل رائع. تملأ الصفيف بكافة الملفات التي تنتهي بـ ".txt" من الدليل "text_directory".
كيف يمكنني قراءة محتويات الدليل بطريقة مماثلة داخل ملف 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 ، يمكنك إنشاء FileSystem
من ملف JAR (Zip) ، ثم استخدام آليات دليل وتصفية دليل NIO للبحث فيه. هذا من شأنه أن يسهل كتابة التعليمات البرمجية التي تتعامل مع JARs والدلائل "انفجرت".
التعليمات البرمجية التي تعمل لملفات 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());
}
}
}
إريكسون الإجابة عملت تماما:
إليك رمز العمل.
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]));
أرغب في التوسع في answer الجواب - لأنه حل غير آمن للغاية ، لعدة أسباب:
FileSystem
.FileSystem
موجود بالفعل.هذا حل أكثر أمانًا إلى حد ما:
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
على نفس الملفات ، وقد تتداخل معها (حتى في تطبيق واحد مترابط).
أيضًا ، لا يبحث عن null
s (على سبيل المثال ، في getClass().getResource()
.
تعد واجهة Java NIO المعينة نوعًا من الرهيبة ، لأنها تقدم موردًا عامًا/أحاديًا غير آمن للخيط ، وتوثيقه غامض للغاية (الكثير من المجهول بسبب تطبيقات موفر معينة). قد تختلف النتائج لمقدمي FileSystem
الآخرين (وليس JAR). ربما هناك سبب وجيه لكونه بهذه الطريقة ؛ لا أعرف ، لم أقم بالبحث في التطبيقات.
لذلك أعتقد أن مشكلتي الرئيسية هي كيفية معرفة اسم الجرة التي يعيش فيها صفي الرئيسي.
على افتراض أن مشروعك معبأ في Jar (ليس صحيحًا بالضرورة!) ، يمكنك استخدام ClassLoader.getResource () أو findResource () مع اسم الفئة (متبوعًا .class) للحصول على الجرة التي تحتوي على فئة معينة. سيتعين عليك تحليل اسم الجرة من عنوان URL الذي يتم إرجاعه (وليس بهذه الصعوبة) ، والذي سأتركه كتمرين للقارئ :-)
تأكد من اختبار الحالة التي لا يكون فيها الفصل جزءًا من الجرة.
إليك طريقة كتبتها عن "تشغيل جميع 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);
}
}
لقد نقلت إجابة acheron55 إلى Java 7 وأغلقت الكائن FileSystem
. يعمل هذا الرمز في IDE ، في ملفات جرة وفي جرة داخل حرب على Tomcat 7 ؛ لكن لاحظ أنه لا يعمل في جرة داخل حرب على 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;
}
});
}
}
}
فيما يلي مثال على استخدام Reflections library لمسح classpath بشكل متكرر من خلال نمط اسم regex المُضاف مع زوج من Guava الامتيازات لجلب محتويات الموارد:
Reflections reflections = new Reflections("com.example.package", new ResourcesScanner());
Set<String> paths = reflections.getResources(Pattern.compile(".*\\.template$"));
Map<String, String> templates = new LinkedHashMap<>();
for (String path : paths) {
log.info("Found " + path);
String templateName = Files.getNameWithoutExtension(path);
URL resource = getClass().getClassLoader().getResource(path);
String text = Resources.toString(resource, StandardCharsets.UTF_8);
templates.put(templateName, text);
}
هذا يعمل مع كل من الجرار والفصول انفجرت.
ملف jar هو مجرد ملف مضغوط به بيان منظم. يمكنك فتح ملف jar باستخدام أدوات Java Zip المعتادة ومسح محتويات الملف بهذه الطريقة ، وتضخيم التدفقات ، وما إلى ذلك ، ثم استخدم ذلك في مكالمة getResourceAsStream ، ويجب أن تكون جميعها مكتنزة.
تحرير/بعد التوضيح
استغرق الأمر دقيقة واحدة لتتذكر كل القطع والأجزاء ، وأنا متأكد من أن هناك طرقًا أنظف للقيام بذلك ، لكنني أردت أن أرى أنني لم أكن مجنونة. في مشروعي image.jpg هو ملف في جزء من ملف الجرة الرئيسي. أحصل على أداة تحميل الفئة للفئة الرئيسية (SomeClass هي نقطة الدخول) واستخدمها لاكتشاف مورد image.jpg. ثم بعض دفق السحر للحصول عليه في هذا الشيء ImageInputStream وكل شيء على ما يرام.
InputStream inputStream = SomeClass.class.getClassLoader().getResourceAsStream("image.jpg");
JPEGImageReaderSpi imageReaderSpi = new JPEGImageReaderSpi();
ImageReader ir = imageReaderSpi.createReaderInstance();
ImageInputStream iis = new MemoryCacheImageInputStream(inputStream);
ir.setInput(iis);
....
ir.read(0); //will hand us a buffered image
بالنظر إلى ملف JAR الفعلي ، يمكنك سرد المحتويات باستخدام JarFile.entries()
. ستحتاج إلى معرفة موقع ملف JAR على الرغم من ذلك - لا يمكنك فقط أن تطلب من محمل الفصل أن يسرد كل ما يمكنه الحصول عليه.
يجب أن تكون قادرًا على تحديد موقع ملف JAR استنادًا إلى عنوان URL الذي تم إرجاعه من ThisClassName.class.getResource("ThisClassName.class")
، لكن قد يكون الأمر بسيطًا بعض الشيء.
هناك نوعان من الأدوات المساعدة المفيدة للغاية كلاهما يسمى JarScan:
راجع أيضًا هذا السؤال:JarScan ، امسح جميع ملفات JAR في جميع المجلدات الفرعية لفئة معينة
مجرد طريقة مختلفة لإدراج/قراءة الملفات من عنوان URL للجرار وهي تقوم بذلك بشكل متكرر للجرار المتداخلة
https://Gist.github.com/trung/2cd90faab7f75b3bcbaa
URL urlResource = Thead.currentThread().getContextClassLoader().getResource("foo");
JarReader.read(urlResource, new InputStreamCallback() {
@Override
public void onFile(String name, InputStream is) throws IOException {
// got file name and content stream
}
});