views:

276

answers:

4

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.

A: 

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

leeand00
Compression isn't the issue. Ease of use for Webstart is. That's why i want to put it in a jar. Thanks though. :-)
JoshuaD
+4  A: 

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.

Aaron Digulla
That only returns a URL, which doesn't do me any good. The AngelCodeFont constructors don't support URLs, and in either case, if it did, it would still be creating two new InputStreamReaders everytime the constructor was called.
JoshuaD
Then use getResourceAsStream().
Aaron Digulla
The InputStreamReaders are cheap; what's killing you is the line with new JarFile().
Aaron Digulla
That's simply not true. Jar File took 00:00:00to load.Font Height 8 took 00:00:09 to load. (min:sec:milli)Font Height 11 took 00:00:03 to load. (min:sec:milli)Font Height 12 took 00:00:02 to load. ...Font Height 66 took 00:00:27 to load. (min:sec:milli)
JoshuaD
JoshuaD: I suggest that you run your code through a profiler. That will tell you which method takes all the time and then, you can optimize. If you have enough RAM, how about loading all items of the JAR into byte arrays?
Aaron Digulla
A: 

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.

Jared
Thanks Jared. I'm at work now, but I'll try that tonight. I'll let you know how it goes.
JoshuaD
A: 

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.

JoshuaD