
[英]Load JSON file: java.lang.OutOfMemoryError: Java heap space
[英]JVM throws java.lang.OutOfMemoryError: heap space (File processing)
我编写了一个文件复制处理器,它获取每个文件的 MD5 哈希,将其添加到哈希图中,然后将所有具有相同哈希的文件添加到名为 dupeList 的哈希图中。 但是在运行大目录进行扫描时,例如 C:\\Program Files\\ 它会抛出以下错误
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.nio.file.Files.read(Unknown Source)
at java.nio.file.Files.readAllBytes(Unknown Source)
at com.embah.FileDupe.Utils.FileUtils.getMD5Hash(FileUtils.java:14)
at com.embah.FileDupe.FileDupe.getDuplicateFiles(FileDupe.java:43)
at com.embah.FileDupe.FileDupe.getDuplicateFiles(FileDupe.java:68)
at ImgHandler.main(ImgHandler.java:14)
我确定它是因为它处理了这么多文件,但我不确定有更好的方法来处理它。 我试图让这项工作正常进行,这样我就可以筛选所有孩子的婴儿照片并删除重复项,然后再将它们放在我的外部硬盘驱动器上进行长期存储。 感谢大家的帮助!
我的代码
public class FileUtils {
public static String getMD5Hash(String path){
try {
byte[] bytes = Files.readAllBytes(Paths.get(path)); //LINE STACK THROWS ERROR
byte[] hash = MessageDigest.getInstance("MD5").digest(bytes);
bytes = null;
String hexHash = DatatypeConverter.printHexBinary(hash);
hash = null;
return hexHash;
} catch(Exception e){
System.out.println("Having problem with file: " + path);
return null;
}
}
public class FileDupe {
public static Map<String, List<String>> getDuplicateFiles(String dirs){
Map<String, List<String>> allEntrys = new HashMap<>(); //<hash, file loc>
Map<String, List<String>> dupeEntrys = new HashMap<>();
File fileDir = new File(dirs);
if(fileDir.isDirectory()){
ArrayList<File> nestedFiles = getNestedFiles(fileDir.listFiles());
File[] fileList = new File[nestedFiles.size()];
fileList = nestedFiles.toArray(fileList);
for(File file:fileList){
String path = file.getAbsolutePath();
String hash = "";
if((hash = FileUtils.getMD5Hash(path)) == null)
continue;
if(!allEntrys.containsValue(path))
put(allEntrys, hash, path);
}
fileList = null;
}
allEntrys.forEach((hash, locs) -> {
if(locs.size() > 1){
dupeEntrys.put(hash, locs);
}
});
allEntrys = null;
return dupeEntrys;
}
public static Map<String, List<String>> getDuplicateFiles(String... dirs){
ArrayList<Map<String, List<String>>> maps = new ArrayList<Map<String, List<String>>>();
Map<String, List<String>> dupeMap = new HashMap<>();
for(String dir : dirs){ //Get all dupe files
maps.add(getDuplicateFiles(dir));
}
for(Map<String, List<String>> map : maps){ //iterate thru each map, and add all items not in the dupemap to it
dupeMap.putAll(map);
}
return dupeMap;
}
protected static ArrayList<File> getNestedFiles(File[] fileDir){
ArrayList<File> files = new ArrayList<File>();
return getNestedFiles(fileDir, files);
}
protected static ArrayList<File> getNestedFiles(File[] fileDir, ArrayList<File> allFiles){
for(File file:fileDir){
if(file.isDirectory()){
getNestedFiles(file.listFiles(), allFiles);
} else {
allFiles.add(file);
}
}
return allFiles;
}
protected static <KEY, VALUE> void put(Map<KEY, List<VALUE>> map, KEY key, VALUE value) {
map.compute(key, (s, strings) -> strings == null ? new ArrayList<>() : strings).add(value);
}
public class ImgHandler {
private static Scanner s = new Scanner(System.in);
public static void main(String[] args){
System.out.print("Please enter locations to scan for dupelicates\nSeperate Location via semi-colon(;)\nLocations: ");
String[] locList = s.nextLine().split(";");
Map<String, List<String>> dupes = FileDupe.getDuplicateFiles(locList);
System.out.println(dupes.size() + " dupes detected!");
dupes.forEach((hash, locs) -> {
System.out.println("Hash: " + hash);
locs.forEach((loc) -> System.out.println("\tLocation: " + loc));
});
}
整个文件读入一个字节数组不仅需要足够的堆空间,它也仅限于文件大小增加至Integer.MAX_VALUE
原则 (对于HotSpot JVM中实际限制是更小的几个字节)。
最好的解决方案是根本不将数据加载到堆内存中:
public static String getMD5Hash(String path) {
MessageDigest md;
try { md = MessageDigest.getInstance("MD5"); }
catch(NoSuchAlgorithmException ex) {
System.out.println("FileUtils.getMD5Hash(): "+ex);
return null;// TODO better error handling
}
try(FileChannel fch = FileChannel.open(Paths.get(path), StandardOpenOption.READ)) {
for(long pos = 0, rem = fch.size(), chunk; rem>pos; pos+=chunk) {
chunk = Math.min(Integer.MAX_VALUE, rem-pos);
md.update(fch.map(FileChannel.MapMode.READ_ONLY, pos, chunk));
}
} catch(IOException e){
System.out.println("Having problem with file: " + path);
return null;// TODO better error handling
}
return String.format("%032X", new BigInteger(1, md.digest()));
}
如果底层的MessageDigest
实现是纯Java实现,它将把数据从直接缓冲区传输到堆,但这不在您的责任范围内(这将在消耗的堆内存和性能之间进行合理的权衡)。
上面的方法将处理超过2GiB大小的文件而不会出现问题。
无论FileUtils
采用哪种实现,都试图读取整个文件以计算哈希值。 这不是必需的:可以通过读取较小的块中的内容来进行计算。 实际上,要求这样做是一种糟糕的设计,而不是简单地读取所需的块(64字节?)。 因此,也许您需要使用更好的库。
您有很多解决方案:
不要一次读取所有字节,请尝试使用BufferedInputStream
,并且每次都读取很多字节。 但不是所有文件。
try (BufferedInputStream fileInputStream = new BufferedInputStream( Files.newInputStream(Paths.get("your_file_here"), StandardOpenOption.READ))) { byte[] buf = new byte[2048]; int len = 0; while((len = fileInputStream.read(buf)) == 2048) { // Add this to your calculation doSomethingWithBytes(buf); } doSomethingWithBytes(buf, len); // Do only with the bytes // read from the file } catch(IOException ex) { ex.printStackTrace(); }
使用C / C ++进行此操作,(这是不安全的,因为您将自己处理内存)
考虑使用番石榴:
private final static HashFunction HASH_FUNCTION = Hashing.goodFastHash(32);
//somewhere later
final HashCode hash = Files.asByteSource(file).hash(HASH_FUNCTION);
番石榴将为您缓冲文件的读取。
我在我的 Windows 机器上遇到了这个 Java 堆空间错误,我花了数周时间在线搜索解决方案,我尝试将我的 -Xmx 值提高得更高但没有成功。 我什至尝试使用参数运行我的 Spring Boot 应用程序,以在运行时使用如下命令增加堆大小
mvn spring-boot:run -Dspring-boot.run.jvmArguments="-Xms2048m -Xmx4096m"
但仍然没有成功。 直到我发现我正在运行内存大小有限的 jdk 32 位,我不得不卸载 32 位并安装 64 位,这为我解决了我的问题。 我希望这可以帮助遇到与我类似的问题的人。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.