java.nio.file.Path สำหรับรีซอร์ส classpath


143

มี API เพื่อรับ classpath ทรัพยากร (เช่นสิ่งที่ฉันได้รับจากClass.getResource(String)) เป็นjava.nio.file.Path? เป็นการดีที่ฉันต้องการใช้PathAPI ใหม่ที่แปลกใหม่กับแหล่งข้อมูล classpath


3
ดีการเส้นทางที่ยาว (เล่นสำนวนเจตนา) คุณมีPaths.get(URI)แล้ว'URL.toURI () , and last getResource () URL`ซึ่งส่งกลับ คุณอาจจะเชื่อมโยงเหล่านั้นเข้าด้วยกัน ยังไม่ได้ลอง
NilsH

คำตอบ:


175

อันนี้ใช้ได้สำหรับฉัน:

return Paths.get(ClassLoader.getSystemResource(resourceName).toURI());

7
@VGR หากทรัพยากรในไฟล์. jar สามารถลองใช้ `Resource resource = new ClassPathResource (" usage.txt "); ผู้อ่าน BufferedReader = ใหม่ BufferedReader (ใหม่ InputStreamReader (resource.getInputStream ()))) `` โปรดดูstackoverflow.com/questions/25869428/ …
zhuguowei

8
@zhuguowei นั่นเป็นวิธีการเฉพาะของฤดูใบไม้ผลิ มันไม่ทำงานเลยเมื่อไม่มีการใช้สปริง
Ryan J. McDonough

2
หากแอปพลิเคชันของคุณไม่ขึ้นอยู่กับ classloader ของระบบก็ควรเป็นThread.currentThread().getContextClassLoader().getResource(resourceName).toURI()
ThrawnCA

27

คาดเดาว่าสิ่งที่คุณต้องการจะทำคือเรียกใช้ Files.lines (... ) บนทรัพยากรที่มาจาก classpath - อาจมาจากภายใน jar

เนื่องจาก Oracle ทำให้ความเชื่อของเมื่อ Path คือ Path โดยไม่ให้ getResource ส่งคืนพา ธ ที่ใช้งานได้หากมันอยู่ในไฟล์ jar สิ่งที่คุณต้องทำคือ:

Stream<String> stream = new BufferedReader(new InputStreamReader(ClassLoader.getSystemResourceAsStream("/filename.txt"))).lines();

1
ไม่ว่าจะเป็น "/" ก่อนหน้านี้ในกรณีของคุณฉันไม่รู้ แต่ในกรณีของฉันclass.getResourceต้องใช้เครื่องหมายทับ แต่getSystemResourceAsStreamไม่สามารถค้นหาไฟล์เมื่อนำหน้าด้วยเครื่องหมายทับ
อดัม

11

วิธีแก้ปัญหาทั่วไปส่วนใหญ่มีดังนี้:

interface IOConsumer<T> {
    void accept(T t) throws IOException;
}
public static void processRessource(URI uri, IOConsumer<Path> action) throws IOException {
    try {
        Path p=Paths.get(uri);
        action.accept(p);
    }
    catch(FileSystemNotFoundException ex) {
        try(FileSystem fs = FileSystems.newFileSystem(
                uri, Collections.<String,Object>emptyMap())) {
            Path p = fs.provider().getPath(uri);
            action.accept(p);
        }
    }
}

อุปสรรคหลักคือการจัดการกับสองความเป็นไปได้ทั้งที่มีระบบไฟล์ที่มีอยู่ที่เราควรใช้ แต่ไม่ปิด (เช่นกับfileURIs หรือที่เก็บโมดูลของ Java 9) หรือต้องเปิดและปิดระบบไฟล์อย่างปลอดภัยด้วยตนเอง (เช่น ไฟล์ zip / jar)

ดังนั้นโซลูชันดังกล่าวข้างต้นสรุปการกระทำจริงในการinterfaceจัดการทั้งสองกรณีปิดอย่างปลอดภัยในภายหลังในกรณีที่สองและทำงานจาก Java 7 ถึง Java 10 ตรวจสอบว่ามีระบบไฟล์เปิดอยู่แล้วก่อนที่จะเปิดใหม่ดังนั้น ยังสามารถใช้งานได้ในกรณีที่ส่วนประกอบอื่นของแอปพลิเคชันของคุณได้เปิดระบบไฟล์สำหรับไฟล์ zip / jar เดียวกันแล้ว

มันสามารถใช้ในทุกเวอร์ชั่น Java ชื่อข้างต้นเช่นการแสดงเนื้อหาของแพคเกจ ( java.langในตัวอย่าง) เป็นPaths เช่นนี้:

processRessource(Object.class.getResource("Object.class").toURI(), new IOConsumer<Path>() {
    public void accept(Path path) throws IOException {
        try(DirectoryStream<Path> ds = Files.newDirectoryStream(path.getParent())) {
            for(Path p: ds)
                System.out.println(p);
        }
    }
});

ด้วย Java 8 หรือใหม่กว่าคุณสามารถใช้การแสดงออกแลมบ์ดาหรือการอ้างอิงวิธีการเพื่อแสดงถึงการกระทำที่แท้จริงเช่น

processRessource(Object.class.getResource("Object.class").toURI(), path -> {
    try(Stream<Path> stream = Files.list(path.getParent())) {
        stream.forEach(System.out::println);
    }
});

ทำเช่นเดียวกัน


รุ่นสุดท้ายของระบบโมดูลของ Java 9 ได้ทำลายตัวอย่างโค้ดข้างต้น JRE รุ่นไม่ลงรอยกันส่งกลับเส้นทาง/java.base/java/lang/Object.classสำหรับในขณะที่มันควรจะเป็นObject.class.getResource("Object.class") /modules/java.base/java/lang/Object.classสิ่งนี้สามารถแก้ไขได้โดยการเพิ่มส่วนที่หายไป/modules/เมื่อรายงานพา ธ พาเรนต์ว่าไม่มีอยู่:

processRessource(Object.class.getResource("Object.class").toURI(), path -> {
    Path p = path.getParent();
    if(!Files.exists(p))
        p = p.resolve("/modules").resolve(p.getRoot().relativize(p));
    try(Stream<Path> stream = Files.list(p)) {
        stream.forEach(System.out::println);
    }
});

จากนั้นจะทำงานอีกครั้งกับทุกรุ่นและวิธีการเก็บข้อมูล


1
วิธีนี้ใช้งานได้ดี! ฉันสามารถยืนยันได้ว่าสิ่งนี้ใช้ได้กับทรัพยากรทั้งหมด (ไฟล์ไดเรกทอรี) ทั้งในไดเรกทอรีคลาสพา ธ และขวดคลาสพา ธ นี่คือวิธีคัดลอกทรัพยากรจำนวนมากที่ควรทำใน Java 7+
Mitchell Skaggs

10

ปรากฎว่าคุณสามารถทำได้ด้วยความช่วยเหลือของผู้ให้บริการระบบไฟล์ซิปในตัว อย่างไรก็ตามการส่งผ่าน URI ทรัพยากรโดยตรงเพื่อPaths.getไม่ทำงาน แต่ก่อนอื่นต้องสร้างระบบไฟล์ zip สำหรับ jar URI โดยไม่มีชื่อรายการจากนั้นอ้างถึงรายการในระบบไฟล์นั้น:

static Path resourceToPath(URL resource)
throws IOException,
       URISyntaxException {

    Objects.requireNonNull(resource, "Resource URL cannot be null");
    URI uri = resource.toURI();

    String scheme = uri.getScheme();
    if (scheme.equals("file")) {
        return Paths.get(uri);
    }

    if (!scheme.equals("jar")) {
        throw new IllegalArgumentException("Cannot convert to Path: " + uri);
    }

    String s = uri.toString();
    int separator = s.indexOf("!/");
    String entryName = s.substring(separator + 2);
    URI fileURI = URI.create(s.substring(0, separator));

    FileSystem fs = FileSystems.newFileSystem(fileURI,
        Collections.<String, Object>emptyMap());
    return fs.getPath(entryName);
}

ปรับปรุง:

มีการชี้ให้เห็นอย่างถูกต้องว่ารหัสด้านบนมีทรัพยากรรั่วไหลเนื่องจากรหัสเปิดวัตถุ FileSystem ใหม่ แต่ไม่เคยปิด วิธีที่ดีที่สุดคือการส่งผ่านวัตถุผู้ปฏิบัติงานที่เหมือนผู้บริโภคเช่นเดียวกับที่ Holger ตอบ เปิด ZipFS FileSystem นานพอที่ผู้ปฏิบัติงานจะทำทุกอย่างที่จำเป็นต้องทำกับ Path (ตราบใดที่ผู้ปฏิบัติงานไม่พยายามเก็บวัตถุ Path ไว้ใช้ในภายหลัง) จากนั้นปิด FileSystem


11
ระวัง fs ที่เพิ่งสร้างใหม่ การเรียกที่สองโดยใช้ jar เดียวกันจะทำให้เกิดข้อยกเว้นเกี่ยวกับระบบไฟล์ที่มีอยู่แล้ว มันจะดีกว่าที่จะลอง (FileSystem fs = ... ) {return fs.getPath (entryName);} หรือถ้าคุณต้องการให้แคชนี้ทำการจัดการขั้นสูง ในรูปแบบปัจจุบันมีความเสี่ยง
raisercostin

3
นอกเหนือจากปัญหาของระบบไฟล์ใหม่ที่อาจไม่ปิดสมมติฐานเกี่ยวกับความสัมพันธ์ระหว่างแบบแผนและความจำเป็นในการเปิดระบบไฟล์ใหม่และการทำให้งงงวยกับเนื้อหา URI นั้น จำกัด ประโยชน์ของการแก้ปัญหา ฉันได้ตั้งคำตอบใหม่ซึ่งแสดงวิธีการทั่วไปที่ทำให้การดำเนินการง่ายขึ้นและจัดการรูปแบบใหม่ ๆ เช่นที่เก็บข้อมูลคลาส Java 9 ใหม่ในเวลาเดียวกัน นอกจากนี้ยังทำงานเมื่อมีคนอื่นอยู่ในโปรแกรมประยุกต์ได้เปิดแล้วระบบแฟ้ม (หรือวิธีการที่เรียกว่าสองครั้งสำหรับขวดเดียวกัน) ...
โฮล

ขึ้นอยู่กับการใช้งานของโซลูชันนี้ไม่ใช่ปิดnewFileSystemสามารถนำไปสู่หลายทรัพยากรที่แขวนรอบเปิดตลอดกาล แม้ว่าภาคผนวก @raisercostin หลีกเลี่ยงข้อผิดพลาดเมื่อพยายามที่จะสร้างระบบไฟล์ที่สร้างไว้แล้วถ้าคุณพยายามที่จะใช้กลับมาคุณจะได้รับPath ClosedFileSystemException@ Holger ตอบสนองได้ดีสำหรับฉัน
José Andias

FileSystemฉันจะไม่ปิด หากคุณโหลดทรัพยากรจาก Jar และจากนั้นสร้างสิ่งที่จำเป็นFileSystem- FileSystemจะช่วยให้คุณสามารถโหลดทรัพยากรอื่น ๆ จาก Jar เดียวกันได้ นอกจากนี้เมื่อคุณสร้างใหม่FileSystemคุณก็สามารถลองโหลดทรัพยากรอีกครั้งโดยใช้และการดำเนินงานโดยอัตโนมัติจะใช้ใหม่Paths.get(Path) FileSystem
NS du Toit

คือคุณไม่ต้องใช้#getPath(String)วิธีการกับFileSystemวัตถุ
NS du Toit

5

ฉันเขียนวิธีใช้ตัวช่วยเล็ก ๆ เพื่ออ่านPathsจากแหล่งข้อมูลในห้องเรียนของคุณ มันค่อนข้างสะดวกในการใช้เพราะต้องการเพียงการอ้างอิงคลาสที่คุณเก็บทรัพยากรของคุณรวมถึงชื่อของทรัพยากรเอง

public static Path getResourcePath(Class<?> resourceClass, String resourceName) throws URISyntaxException {
    URL url = resourceClass.getResource(resourceName);
    return Paths.get(url.toURI());
}  

1

คุณไม่สามารถสร้าง URI จากแหล่งข้อมูลภายในไฟล์ jar คุณสามารถเขียนมันลงในไฟล์ชั่วคราวแล้วใช้มัน (java8):

Path path = File.createTempFile("some", "address").toPath();
Files.copy(ClassLoader.getSystemResourceAsStream("/path/to/resource"), path, StandardCopyOption.REPLACE_EXISTING);

1

อ่านไฟล์จากโฟลเดอร์ทรัพยากรโดยใช้ NIO ใน java8

public static String read(String fileName) {

        Path path;
        StringBuilder data = new StringBuilder();
        Stream<String> lines = null;
        try {
            path = Paths.get(Thread.currentThread().getContextClassLoader().getResource(fileName).toURI());
            lines = Files.lines(path);
        } catch (URISyntaxException | IOException e) {
            logger.error("Error in reading propertied file " + e);
            throw new RuntimeException(e);
        }

        lines.forEach(line -> data.append(line));
        lines.close();
        return data.toString();
    }

0

คุณจำเป็นต้องกำหนดระบบแฟ้มไปยังทรัพยากรอ่านจากไฟล์ขวดดังกล่าวในhttps://docs.oracle.com/javase/8/docs/technotes/guides/io/fsp/zipfilesystemprovider.html ฉันประสบความสำเร็จในการอ่านทรัพยากรจากไฟล์ jar ด้วยรหัสด้านล่าง:

Map<String, Object> env = new HashMap<>();
try (FileSystem fs = FileSystems.newFileSystem(uri, env)) {

        Path path = fs.getPath("/path/myResource");

        try (Stream<String> lines = Files.lines(path)) {
            ....
        }
    }
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.