-->

Eclipse Java File FileInputStream vs Input Stream

2019-01-20 05:19发布

问题:

This question already has an answer here:

  • How can I access a txt file in a jar with FileInputStream? 3 answers

I am going through some tutorials and I have a problem with loading font file into Eclipse Java Project. I tried many solutions suggested here on SO, and eventually find one (using FileInputStream) which does work for me, but not when the project is exported as runnable JAR. On the other hand using same directory structure in the other project where I load icons works, so I guess the problem is not in the path itself.

Here is the directory structure:

Here is the code:

package examples;

import java.awt.Font;
import java.awt.FontFormatException;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URISyntaxException;

import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;

public class Test01 extends JPanel {

    String text = "Free the bound periodicals";
    Font fon;
    FileInputStream fis;
    // InputStream fis;

    @Override
    public void paintComponent(Graphics comp) {
        Graphics2D comp2D = (Graphics2D) comp;


        // This (for another project) works both in Eclipse and in runnable  JAR
        // ImageIcon loadIcon = new ImageIcon(getClass().getResource("/examples/resources/load.gif"));

        // This (quite contrary to many SO suggestions) doesn't work in Eclipse for this project, why?
        // fis = this.getClass().getResourceAsStream("/examples/resources/vedrana.ttf");

        // This (quite contrary to many SO suggestions) doesn't work in Eclipse for this project, why?
        // fis = this.getClass().getClassLoader().getResourceAsStream("/examples/resources/verdana.ttf");

        // This works within Eclipse project but not when exported to runnable JAR, 
        // Moreover many suggest that InputStream should be favored over FileInputStream
        try {
            fis = new FileInputStream(new File(getClass().getResource("/examples/resources/verdana.ttf").toURI()));
        } catch (FileNotFoundException e1) {
            JOptionPane.showMessageDialog(this, "FileNotFoundException!");
        } catch (URISyntaxException e1) {
            JOptionPane.showMessageDialog(this, "URISyntaxException!");
        } catch (Exception e1) {
            JOptionPane.showMessageDialog(this, "NullPointerException!");
        }

        try {
            fon = Font.createFont(Font.TRUETYPE_FONT, fis);
        } catch (FontFormatException e) {
            // TODO Auto-generated catch block
            System.out.println("Error - FontFormatException " + e.getMessage());
        } catch (IOException e) {
            // TODO Auto-generated catch block
            System.out.println("Error - IOException " + e.getMessage());
        }

        fon = fon.deriveFont(Font.PLAIN, 72);

        FontMetrics metrics = getFontMetrics(fon);
        comp2D.setFont(fon);
        comp2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

        int x = (getSize().width - metrics.stringWidth(text)) / 2;
        int y = getSize().height / 2;

        comp2D.drawString(text, x, y);
    }

    public static void main(String[] args) {
        JFrame mainFrame = new JFrame("Main Menu");
        mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        mainFrame.setSize(1000, 250);
        mainFrame.setVisible(true);
        mainFrame.add(new Test01());
        // mainFrame.pack();
    }
}

So, what bothers me is:

- Why this is not working (seems it cant find font file) as it throws NullPointerException

fis = this.getClass().getResourceAsStream("/examples/resources/vedrana.ttf");

nor this is working

fis = this.getClass().getClassLoader().getResourceAsStream("/examples/resources/verdana.ttf");

- Why this works within Eclipse project, but not when exported to runnable JAR

fis = new FileInputStream(new File(getClass().getResource("/examples/resources/verdana.ttf").toURI()));

UPDATE As it turned out this will work:

fis = this.getClass().getClassLoader().getResourceAsStream("examples/resources/verdana.ttf");

The problem was in first slash - path should be "examples/resources/verdana.ttf" instead of "/examples/resources/verdana.ttf". It works both in Eclipse and in runnable JAR.

Now, what intrigues me is why is then that first slash necessary in this case

ImageIcon loadIcon = new ImageIcon(getClass().getResource("/examples/resources/load.gif"));

UPDATE 2: After being frustrated why this method doesn't work

InputStream fis = this.getClass().getResourceAsStream("/examples/resources/verdana.ttf");

I deleted whole class from Eclipse and now BOTH methods work within Eclipse and runanble JAR - just as it should be.

InputStream fis = this.getClass().getResourceAsStream("/examples/resources/verdana.ttf");

or

InputStream fis = this.getClass().getClassLoader().getResourceAsStream("examples/resources/verdana.ttf");

回答1:

To answer your last concern about the slash: when you use getClass() the search will begin from the calling class. If you don't use the first class the file will be looked for in examples/examples/resource (since the calling class is in examples) which doesn't exist. What the forward slash does is bring the search to the class path root.

This works within Eclipse project but not when exported to runnable JAR, Moreover many suggest that InputStream should be favored over FileInputStream

If you a resource is needed for you application and is packaged in the jar, it should be read from an URL, either via getClass().getResource() which returns an actual URL to a resource, or getClass().getResourceAsStream() which return the resource as a stream in the form of InputStream, obtained from the URL. You can also getClassLoader(), but here are the main differences

  • getClass() - As stated above, the search will begin from the location of the calling class. So whatever package the class is in, that's where the search begins. A structure like this

    ProjectRoot
              src
                 com
                    example
                         MyClass.class
                 resources
                        resource.ttf
    

    will cause the the search to begin from inside example dir of the package. So if you try and use this path resources/resource.ttf if will fail, because there is no resources dir in the examples dir. Using the / will bring the search to the root of the class path, which is the src (at least from IDE perspective - which will get jar'ed into a classes). So /resources/resource.ttf would work, as src/resources/resource.ttf exists.

  • getClassLoader() - begins it's search from the class path root, so the src. You don't need the extra / because resources/resource.ttf is like saying it exists as src/resources/resource.ttf.


On another note, when you use any form of a File search, the search will be in terms of the local file system. So as to your question why using File would work on your IDE, is because the IDE launches the program not from the jar, but from the IDE itself and uses the current working directory as the root path.

So the main thing to keep in mind is that when your file is an application resources, read it as such from the class path.

InputStream is = getClass().getResourcAsStream("/path/to/file");

Further explanation about the functionality of your IDE. Yours is eclipse. If you go to you project in yout file system, you will see a bin file, in terms of your IDE is the class path. When you compile your code, the resources get copied into that path. That's where the IDE will search when you try and read as a file, as the project is the working directory, and using a non absolute path, that's where the searching happens



回答2:

Although @peeskillet provided very concise answer, if anyone wants to do additional reading, also very good explanation could be found here:

Loading Java Files

I am mentioning this because it seems there is a lot of questions & confusion (including my own) about proper way to load a file in Java using different methods.

So in the end both methods work:

// This works both within Eclipse project and in runnable JAR
        InputStream fis = this.getClass().getResourceAsStream("/examples/resources/verdana.ttf");

or

// This works both within Eclipse project and in runnable JAR
            InputStream fis = this.getClass().getClassLoader().getResourceAsStream("examples/resources/verdana.ttf");