Extracting files from a Jar more efficently

2019-04-13 01:40发布

问题:

I'm extending a utility class that bundles a set of images and .xml description files. Currently I keep all the files in a directory and load them from there. The directory looks like this:

8.png
8.xml
9.png
9.xml
10.png
10.xml
...
...
50.png
50.xml
...



Here's my current constructor. It is lightning fast and does what I need it to do. (I've stripped out some of the error checking to make it easier to read):

public DivineFont(String directory ) {

    File dir = new File(directory);

    //children is an array that looks like this: '10.fnt', '11.fnt', etc.
    String[] children = dir.list(fntFileFilter);

    fonts = new Hashtable<Integer, AngelCodeFont>(100);

    AngelCodeFont buffer;
    int number;
            String fntFile;
            String imgFile;

    for(int k = 0; k < children.length; k++ ) {
        number = Integer.parseInt( children[k].split("\\.")[0] );
        fntFile = directory + File.separator + number + ".xml";
        imgFile = directory + File.separator + number + ".png";
        buffer = new AngelCodeFont(fntFile, imgFile);

        fonts.put(number, buffer);
    }
}

For the sake of webstart and cleanliness, I've been trying to load these resources from a Jar instead. I've got it working, but the load time went from instantaneous to a few seconds, and that's not acceptable. Here's the code I tried (again, error checking stripped):

(This isn't the best way to do what I want to do, it's a mock-up to see if the idea worked. It didn't. The two for-loops is in no way the source of the problem; it's the process of creating all those InputStreams that slows it down)

public DivineFont(String jarFileName ) {

    JarFile jarfile = new JarFile(jarFileName);
    Enumeration<JarEntry> em = jarfile.entries();
    ArrayList<Integer> fontHeights = new ArrayList<Integer>(100);
    for (Enumeration em1 = jarfile.entries(); em1.hasMoreElements(); ) {
        String fileName = em1.nextElement().toString();
        if( fileName.endsWith(".fnt") ) {
            fontHeights.add( Integer.parseInt(fileName.split("\\.")[0] ) );
        }
    }

    fonts = new Hashtable<Integer, AngelCodeFont>(100);

    AngelCodeFont buffer;
    int number;

    for(int k = 0; k < fontHeights.size(); k++ ) {
        number = fontHeights.get(k);
        InputStream fntFileStream = jarfile.getInputStream(jarfile.getEntry(number + ".xml"));
        InputStream pngFileStream = jarfile.getInputStream(jarfile.getEntry(number + ".png"));
        buffer = new AngelCodeFont(String.valueOf(number), fntFileStream, pngFileStream );

        fonts.put(number, buffer);


    }
}


Anyone know of a better way to work with .jar files besides the way I've tried here? Here's the AngelCodeFont API. If it was absolutely necessary I could submit a patch for that, but I'd rather not have to. It seems to me that there's probably a way to do what I want to do, I'm just not familiar with it.

I'm not terribly against quickly dumping the jar to a temporary directory and then reading the files from there, but if there's a way to do it reading directly from the jar quickly, I'd much rather do that.

Also: Compression isn't an issue at all. The only reason I'm using a jar is for the packing issue.

回答1:

Opening a JAR is a very expensive operation. So you may want to open the JAR once and keep the JarFile instance in a static field somewhere. In your case, you will also want to read all entries and keep them in a hashmap for instant access of the resource you need.

Another solution is to put the JAR on the classpath and use

DivineFont.class.getContextClassLoader().getResource*("/8.png");

Mind the "/"! If you omit it, Java will search in the same directory (package) in which it finds the file DivineFont.class.

Getting things from the classpath has been optimized to death in Java.



回答2:

Well, I found the answer. The answer to my original question was "Wrong question." The Jar file wasn't the issue, it was the library I was using to load the images.

When I was loading from the file system, the image was being named "38.png", etc. When I was loading from the Jar, I was simply naming it "38".

The Image loader class inside the library uses the file extension of the name to identify which image loader to use. If there is no file extension, it uses a slower, basic image loader. When I changed this line:

buffer = new AngelCodeFont(String.valueOf(number), fntFileStream, pngFileStream );

to this line:

buffer = new AngelCodeFont( number + ".png", fntFileStream, pngFileStream );

We have ourselves a winner.

Thanks for the help anyway guys. It took me a few hours to figure this out, but if not for your posts, I probably would have continued to assume the blame was Java's rather than the Library I'm using.



回答3:

Jar file manipulation can be very expensive, and the Java class library already implements this kind of resource loading (probably as efficiently as possible.)

Plus, once you're in webstart, your jar file names will get mangled, so you'll likely end up having to explore every jar file on the classpath to load your resources (I've done it! Ugly!)

Instead, use Class.getResourceAsStream(String resourceName). I haven't profiled it, but I haven't noticed it being noticeably slower than direct file access.



回答4:

This library may not compress as much as you need, but it'll be faster...