如何以静态方式从jar中加载文件



我有一个"默认";要使用的图像文件;适当的";图像文件不存在。我正在使用Maven创建jar,并将该文件与我已经拥有的其他图像一起放在resources/images下。

该文件是在一个有几个默认值的类中定义的:

public class Defaults {
public static final File BLANK_IMAGE = new File(relative_path_to_image);
//Other defaults
}

这在IDE中有效,但如果我尝试创建jar,则不行——如果我这样做并从命令提示符运行它,当找不到特定文件时,它会弹出我为之编写的消息。

我已经研究了几种潜在的解决方案,但它们都依赖于非静态方法,因为File是静态的,所以不能在这里使用这些方法(而且,当它的所有实例都相同时,实例化它是没有意义的——这只是浪费时间和资源)。

我只是把File转换成String,并在每个需要getResourceAsStream()Object内部处理它吗?

通过首先将文件转换为BufferedImage,该文件被放置在几个JLabel实例上(这些实例意味着具有尚未创建的图像)。我提到这一点是为了防止有更好的方法来达到同样的结果,从而绕过我遇到的问题。

new File表示文件。jar文件中的条目不是一个。如果你在代码中的任何地方提到File,游戏就会立即失败。

(此外,当某个东西的所有实例都相同时,实例化它是没有意义的——这只是浪费时间和资源)。

这种心态是非常有害的。这会导致编写错误的代码。所有方面都很糟糕:

  • 测试难度明显加大
  • 执行速度较慢。(大量的快速垃圾实际上比少数缓存对象更快。阅读CPU缓存行为和世代垃圾收集,发现"在许多地方使用的长寿命对象"比你想象的要糟糕得多,而"创建的对象之后几乎立即成为垃圾的对象"实际上几乎不需要任何成本——这与你的思维方式完全相反。)r仍在努力)
  • 不惯用(java不再是这样写的;它使与其他java程序员在代码方面的交互变得比需要的更困难)
  • 不灵活(当需求发生变化,功能请求弹出时,用自己的心态修改代码比需要的更难)

我是不是一直在把文件转换成字符串,并用getResourceAsStream()在每个需要它的对象中处理它?

我想你严重误解了File是什么。文件只是一个简单的包装;它有一个String类型的字段,表示路径。此:

private static final File MY_IMG_FILE = new File("/foo/bar");
public JLabel codeThatIsInvokedALot() {
return new JLabel(new ImageIcon(MY_IMG_FILE));
}

效率与一样低

public JLabel codeThatIsInvokedALot() {
return new JLabel(new ImageIcon(MyClass.class.getResource("/foo/bar"));
}

从某种意义上说,你为一些对象的分配支付1分,为打开jar文件(或磁盘上的文件)并从中读取代表图像的实际字节支付100000000000分(这并不夸张,这是一个大致准确的比例)。如果你非常非常幸运,你的操作系统会进行一些磁盘缓存,并将其变成1比100万的差距。

如果您想要"效率",则需要加载整个图像一次"只创建一次文件对象";由于性能原因完全没有意义。File对象不缓存该文件的内容,只缓存它的位置,谈论它的"缓存"是不值得谈论的(你正在缓存……大约80位。查找的成本是创建整个文件的500倍,同样不是一个检验)。

那么它是如何做到的呢?一点也不难:

public class MyImages {
private final ImageIcon BLANK_IMAGE;
static {
BLANK_IMAGE = resourceToImage("/images/blankImage.png");
}
public static ImageIcon getBlankImage() { return BLANK_IMAGE; }
private static ImageIcon resourceToImage(String rsc) {
return new ImageIcon(MyImages.class.getResource(rsc));
}
}

这将:

  • JVM第一次执行任何提到MyImages的代码时,它将加载MyImages类。据推测,这种情况不会完全"启动",而是很快就会自然发生
  • 任何代码提到MyImages时,都已经加载;类最多只加载一次
  • 加载MyImages是一项繁重的工作-它将首先运行静态块(在第一个线程完成加载之前,任何其他接触MyImages的线程都将被阻止),这实际上将打开jar文件并读取png文件,并将其解压缩为原始图像映射
  • MyImages类在堆中的内存占用量相对较大,因为必须托管所有未打包的映像
  • return new JLabel(MyImages.getBlankIcon())将(除非这是JVM执行中的任何代码第一次接触MyImages)执行得非常快,而且不会接触到磁盘
  • 我敢肯定,创建15个具有相同图标的jlabel不会花费大量内存——它们都在ImageIcon的一个实例中使用相同的底层未打包像素数据
  • 使用正确的方法:SomeContextClass.class.getResource。这严格优于通过类加载器,或使用getClass()而不是SomeContextClass.class:这些常见的替代方案在特殊情况下失败(引导加载器、代理等-它们得到了null类加载器),在不太特殊的情况下失败,在模块化系统中,子类打破了getClass()风格,MyClass.class.getResource简单、简洁、惯用,并且具有比所有备选方案更少的故障模式

从jar中加载文件有很多方法,但最常见的方法是使用ClassLoader.getResourceAsStream()方法。