在图像中加载后无法删除外部 JAR 文件
Cannot delete external JAR file after loading in Image
我目前正忙于构建一个简单的 javafx 应用程序。在此应用程序中,您可以在应用程序中添加和删除 .jar 文件,我正在从中收集一些信息。我从这些 .jar 文件中使用的其中一件事是存储为图像的纹理。
每当我使用 jar 文件中的路径加载图像时,我都无法在以后删除此 .jar 文件。这是非常合乎逻辑的,因为 .jar 文件现在正被此图像使用,但出于某种原因,即使删除对此图像的所有引用并调用垃圾收集器,我也无法从应用程序中删除 .jar 文件。
我目前尝试删除文件的方式如下:
File file = new File("mods/file.jar");
file.delete();
在应用程序的某个时刻,我按如下方式初始化图像:
Image block = new Image(pathToJar);
我尝试了以下方法来解决我的问题:
- 设置
block = null;
并调用System.gc();
- 调用
Image.cancel()
并调用System.gc();
- 我也尝试过结合
System.runFinalization();
- 我尝试在舞台关闭时删除 .jar 文件,希望卸载所有内容,但没有成功。
目前,我不太清楚为什么我无法真正删除 .jar 文件。谁能向我解释为什么会这样以及如何解决?非常感谢!
编辑:申请的目的不够明确。我正在制作一个颜色选择器,它从 .jar 文件(Minecraft mods)中获取纹理的平均颜色。这些.jar 文件将以灵活的方式从应用程序中添加和删除。一旦您不想再考虑某个 .jar 文件的纹理,您只需将其从应用程序中删除并完成处理即可。为了便于使用,我制作了 .jar 文件的副本,以便我可以相对地访问它们。完成 .jar 文件后,我想删除此副本,因为它只会占用不必要的存储空间。
我已将问题(到目前为止)缩小到图像的初始化。根据要求,这里是 MRE:
public class example extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
// Read image, needed at some point in my code
Image block = new Image("jar:File:./mods/botania.jar!/assets/botania/textures/blocks/alfheim_portal.png");
// Make it so that jar is not used anymore
block.cancel();
block = null;
System.gc();
// Try to delete the file after being done with it
File delete = new File("mods/botania.jar");
Files.delete(delete.toPath());
}
}
在初始化图像并删除对它的引用后,它应该由垃圾收集器清理(至少据我所知)。相反,现在它只给出以下错误:
Caused by: java.nio.file.FileSystemException: mods\botania.jar: The
process cannot access the file because it is being used by another
process.
我无法在我的系统上重现该问题(Java 14 on Mac OS X);但是 运行 它在 Windows 10 上具有相同的 JDK/JavaFX 版本会产生您描述的异常。
问题似乎是(请参阅 , and hat-tip to @matt 来挖掘)默认情况下,jar:
URL 的 URL 处理程序缓存对底层 JarFile
对象(并且不调用 close()
,即使从中获得的 InputStream
已关闭)。由于 Windows 在这些情况下保留文件句柄,因此底层 OS 无法删除 jar 文件。
我认为解决这个问题最自然的方法是使用 java.util.jar
API(或者只是简单的 java.util.zip
API),这会让你走得更远比生成 URL 并将 URL 传递给 Image
构造函数更多地控制关闭资源。
这是一个使用这种方法的独立示例,它适用于我的 Mac 和我的 Windows 10 虚拟机:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
import java.util.zip.ZipEntry;
import javax.imageio.ImageIO;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class App extends Application {
@Override
public void init() throws Exception {
// create an image in a jar file:
BufferedImage image = new BufferedImage(100, 100, ColorSpace.TYPE_RGB);
Graphics graphics = image.getGraphics();
graphics.setColor(new Color(0xd5, 0x5e, 0x00));
graphics.fillRect(0, 0, 100, 100);
graphics.setColor(new Color(0x35, 0x9b, 0x73));
graphics.fillOval(25, 25, 50, 50);
JarOutputStream out = new JarOutputStream(new FileOutputStream("test.jar"));
ZipEntry entry = new ZipEntry("test.png");
out.putNextEntry(entry);
ImageIO.write(image, "png", out);
out.close();
}
@Override
public void start(Stage stage) throws Exception {
assert Files.exists(Paths.get("test.jar")) : "Jar file not created";
JarFile jarFile = new JarFile("test.jar");
JarEntry imgEntry = jarFile.getJarEntry("test.png");
InputStream inputStream = jarFile.getInputStream(imgEntry);
Image img = new Image(inputStream);
jarFile.close();
// The following line (instead of the previous five lines) works on Mac OS X,
// but not on Windows
//
// Image img = new Image("jar:file:test.jar!/test.png");
Files.delete(Paths.get("test.jar"));
assert ! Files.exists(Paths.get("test.jar")) : "Jar file not deleted";
ImageView iview = new ImageView(img);
Scene scene = new Scene(new BorderPane(iview), 200, 200);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
我目前正忙于构建一个简单的 javafx 应用程序。在此应用程序中,您可以在应用程序中添加和删除 .jar 文件,我正在从中收集一些信息。我从这些 .jar 文件中使用的其中一件事是存储为图像的纹理。
每当我使用 jar 文件中的路径加载图像时,我都无法在以后删除此 .jar 文件。这是非常合乎逻辑的,因为 .jar 文件现在正被此图像使用,但出于某种原因,即使删除对此图像的所有引用并调用垃圾收集器,我也无法从应用程序中删除 .jar 文件。
我目前尝试删除文件的方式如下:
File file = new File("mods/file.jar");
file.delete();
在应用程序的某个时刻,我按如下方式初始化图像:
Image block = new Image(pathToJar);
我尝试了以下方法来解决我的问题:
- 设置
block = null;
并调用System.gc();
- 调用
Image.cancel()
并调用System.gc();
- 我也尝试过结合
System.runFinalization();
- 我尝试在舞台关闭时删除 .jar 文件,希望卸载所有内容,但没有成功。
目前,我不太清楚为什么我无法真正删除 .jar 文件。谁能向我解释为什么会这样以及如何解决?非常感谢!
编辑:申请的目的不够明确。我正在制作一个颜色选择器,它从 .jar 文件(Minecraft mods)中获取纹理的平均颜色。这些.jar 文件将以灵活的方式从应用程序中添加和删除。一旦您不想再考虑某个 .jar 文件的纹理,您只需将其从应用程序中删除并完成处理即可。为了便于使用,我制作了 .jar 文件的副本,以便我可以相对地访问它们。完成 .jar 文件后,我想删除此副本,因为它只会占用不必要的存储空间。
我已将问题(到目前为止)缩小到图像的初始化。根据要求,这里是 MRE:
public class example extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
// Read image, needed at some point in my code
Image block = new Image("jar:File:./mods/botania.jar!/assets/botania/textures/blocks/alfheim_portal.png");
// Make it so that jar is not used anymore
block.cancel();
block = null;
System.gc();
// Try to delete the file after being done with it
File delete = new File("mods/botania.jar");
Files.delete(delete.toPath());
}
}
在初始化图像并删除对它的引用后,它应该由垃圾收集器清理(至少据我所知)。相反,现在它只给出以下错误:
Caused by: java.nio.file.FileSystemException: mods\botania.jar: The process cannot access the file because it is being used by another process.
我无法在我的系统上重现该问题(Java 14 on Mac OS X);但是 运行 它在 Windows 10 上具有相同的 JDK/JavaFX 版本会产生您描述的异常。
问题似乎是(请参阅 , and hat-tip to @matt 来挖掘)默认情况下,jar:
URL 的 URL 处理程序缓存对底层 JarFile
对象(并且不调用 close()
,即使从中获得的 InputStream
已关闭)。由于 Windows 在这些情况下保留文件句柄,因此底层 OS 无法删除 jar 文件。
我认为解决这个问题最自然的方法是使用 java.util.jar
API(或者只是简单的 java.util.zip
API),这会让你走得更远比生成 URL 并将 URL 传递给 Image
构造函数更多地控制关闭资源。
这是一个使用这种方法的独立示例,它适用于我的 Mac 和我的 Windows 10 虚拟机:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
import java.util.zip.ZipEntry;
import javax.imageio.ImageIO;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class App extends Application {
@Override
public void init() throws Exception {
// create an image in a jar file:
BufferedImage image = new BufferedImage(100, 100, ColorSpace.TYPE_RGB);
Graphics graphics = image.getGraphics();
graphics.setColor(new Color(0xd5, 0x5e, 0x00));
graphics.fillRect(0, 0, 100, 100);
graphics.setColor(new Color(0x35, 0x9b, 0x73));
graphics.fillOval(25, 25, 50, 50);
JarOutputStream out = new JarOutputStream(new FileOutputStream("test.jar"));
ZipEntry entry = new ZipEntry("test.png");
out.putNextEntry(entry);
ImageIO.write(image, "png", out);
out.close();
}
@Override
public void start(Stage stage) throws Exception {
assert Files.exists(Paths.get("test.jar")) : "Jar file not created";
JarFile jarFile = new JarFile("test.jar");
JarEntry imgEntry = jarFile.getJarEntry("test.png");
InputStream inputStream = jarFile.getInputStream(imgEntry);
Image img = new Image(inputStream);
jarFile.close();
// The following line (instead of the previous five lines) works on Mac OS X,
// but not on Windows
//
// Image img = new Image("jar:file:test.jar!/test.png");
Files.delete(Paths.get("test.jar"));
assert ! Files.exists(Paths.get("test.jar")) : "Jar file not deleted";
ImageView iview = new ImageView(img);
Scene scene = new Scene(new BorderPane(iview), 200, 200);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}