JAR文件在命令行中可以正常运行,但直接双击就无法运行
我有一个桌面应用,需要从文本文件读取数据,并显示JPEG图片。这个在NetBeans中一切正常,打包成的JAR从命令行移动到非NetBeans目录后可以正确打开并运行。然而,当我双击启动应用时,界面会打开,但本应显示数据的字段却是空白的。
我看到其他帖子建议使用IOStreams而不是FileReaders和 FileWriters,因此我也改成这种方式重写了一遍。结果还是一样——在NetBeans中一切正常,在命令行通过 java -jar <myfile.jar> 运行也没问题,但双击 <myfile.jar> 图标时,GUI会显示出来,却没有读取到应有的数据,也没有显示任何图片。
我也看了大量关于类路径的文章,但如果真是这个问题,我的应用从命令行就应该也不能运行,对吧?
这是我正在尝试实现的一个简化版本——读取文本文件并在一个JPanel中显示其内容(我知道在这个简化版本中我引入的导入语句比实际需要的要多):
package com.steve;
import javax.swing.*;
import java.awt.*;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.charset.StandardCharsets;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.BufferedReader;
import java.io.IOException;
public class Testing extends JFrame{
public Testing(){
super("My Test App");
EntryPanel panel = new EntryPanel();
this.getContentPane().add(panel);
this.setSize(800, 600);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setVisible(true);
}
public class EntryPanel extends JPanel{
String strTextToDisplay;
public EntryPanel(){
super();
try{
this.strTextToDisplay = getTextFromFile();
}
catch(IOException ioe){
System.out.println(ioe.getMessage());
}
}
private String getTextFromFile() throws IOException{
String fileName = "testfile.txt";
var filePath = Paths.get(fileName);
String myStr = "Here it is:\n";
try(BufferedReader br = Files.newBufferedReader(filePath, StandardCharsets.UTF_8)){
String line;
while((line = br.readLine()) != null){
myStr = myStr.concat(line).concat("\n");
}
}
System.out.println(myStr);
return myStr;
}
@Override
public void paintComponent(Graphics g){
g.setColor(new Color(0, 0, 255));
g.fillRect(0,0, this.getWidth(), this.getHeight());
g.setColor(new Color(255, 100, 100));
g.setFont(new Font("Serif", Font.BOLD, 15));
g.drawString(strTextToDisplay, 20, 20);
}
}
public static void main(String[] args){
System.out.println("In main");
Testing myTest = new Testing();
}
}
我的 manifest.txt 文件如下:
Manifest-Version: 1.0
Class-Path: com.resources/testfile.txt
Main-Class: com.steve.Testing
Created-By: 21.0.10 (Ubuntu)
解决方案
以下演示如何从JAR文件中加载一个“资源”。
(代码后的说明。)
package com.steve;
import java.io.BufferedReader;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
public class Testing {
public static void main(String[] args) {
Testing instance = new Testing();
Class<?> instanceClass = instance.getClass();
URL url = instanceClass.getResource("/com/resources/testfile.txt");
if (url == null) {
System.err.println("ERROR: File not found.");
}
else {
try {
URI uri = url.toURI(); // throws java.net.URISyntaxRxception
try (BufferedReader br = Files.newBufferedReader(Paths.get(uri))) {
String myStr = "Here it is:" + System.lineSeparator();
String line = br.readLine();
while (line != null) {
myStr = myStr.concat(line);
myStr = myStr.concat(System.lineSeparator());
line = br.readLine();
}
System.out.println(myStr);
}
}
catch (IOException | URISyntaxException x) {
x.printStackTrace();
}
}
}
}
JAR中的文件路径相对于你调用方法 getResource 的 Class 来说是相对路径的。因此如果文件 testfile.txt 与Java类 Testing 位于同一个包中,那么传给方法 getResource 的参数就只是 testfile.txt。但由于它位于不同的包中,你需要使用“完整”路径,其中路径分隔符必须是斜杠,即 /。在上面的代码中,前导的 / 表示一个“完整”路径。
请注意,在JAR的 MANIFEST中不需要 [Class-Path] 属性。
备选方案
请查看旧的回答,但这里还有更多问题。
基本要点
“文件”位于jar内部,jar是一种zip文件。
这意味着 File(用于磁盘文件)不可用,但通用的 Path 也可以使用。同样也可以考虑使用 URL/URI。
在底层,二进制字节读取可以通过一个 InputStream 来完成。
以文本读取可以通过一个 Reader 来实现,使用InputStream的某种 Charset 编码。
获取资源
在你的 com.steve.Testing 类中,资源 com.resources.testfile.txt 不能通过相对路径进行访问,但如下:
URL url = getClass().getResource("/com/resources/testfile.txt");
Path path = Paths.get(url.toURI());
这使用了 URI,它比 URL 更新且更通用。Path 是一个通用的实现,它维护着它所使用的文件系统,在这里内部是一个Zip文件系统。
private String getTextFromFile() throws IOException {
URL url = getClass().getResource("/com/resources/testfile.txt");
Path path = Paths.get(url.toURI());
String content = Files.readString(path);
System.out.println(content);
return content;
}
正如你所见,Files 类具有非常好的功能,例如可以执行以下操作:
Path path2 = Paths.get("...");
Files.copy(path, path2);
通过这种方式,你的只读资源文件即可被复制以便编辑。
使用 String 和 concat/+= 可能会被编译器巧妙地优化,但对于较大的文件而言仍然不是一个好主意。
不过这段代码假设文件使用UTF-8编码。对Ubuntu来说没问题。
你也可以选择其他编码,但UTF-8更优,因为可以提供 Charset。
Charset charset = StandardCharsets.ISO_889_1;
String content = Files.readString(pat, charset);
System.out.println 同样会把String(内部始终使用Unicode,UTF-8/UTF-16)转换为当前运行平台的编码。例如在Windows机器上,可能会出现希腊文字符的编码问题。