From 8911e2dafaf1044fb28b671bcf2d9b931cdcbc45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Kubitz?= Date: Mon, 11 Mar 2024 15:28:57 +0100 Subject: [PATCH] SafeFileOutputStream: remember the hash of content to avoid read on save when content hash did not change (~50ms slower) --- .../localstore/SafeFileInputStream.java | 8 ++-- .../localstore/SafeFileOutputStream.java | 42 +++++++++++++++---- 2 files changed, 37 insertions(+), 13 deletions(-) diff --git a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/SafeFileInputStream.java b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/SafeFileInputStream.java index 2b4e393f1a2..02a703be289 100644 --- a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/SafeFileInputStream.java +++ b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/SafeFileInputStream.java @@ -18,7 +18,7 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; -import java.nio.file.Files; +import java.nio.file.NoSuchFileException; /** * Given a target and a temporary locations, it tries to read the contents @@ -33,12 +33,12 @@ public class SafeFileInputStream { public static InputStream of(String targetPath, String tempPath) throws IOException { File target = new File(targetPath); try { - return new ByteArrayInputStream(Files.readAllBytes(target.toPath())); - } catch (FileNotFoundException e) { + return new ByteArrayInputStream(SafeFileOutputStream.read(target.toPath())); + } catch (FileNotFoundException | NoSuchFileException e) { if (tempPath == null) tempPath = target.getAbsolutePath() + EXTENSION; target = new File(tempPath); - return new ByteArrayInputStream(Files.readAllBytes(target.toPath())); + return new ByteArrayInputStream(SafeFileOutputStream.read(target.toPath())); } } } diff --git a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/SafeFileOutputStream.java b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/SafeFileOutputStream.java index 4c664092258..9d8dd446f6f 100644 --- a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/SafeFileOutputStream.java +++ b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/SafeFileOutputStream.java @@ -19,8 +19,13 @@ import java.io.IOException; import java.io.OutputStream; import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; /** * This class should be used when there's a file already in the @@ -34,6 +39,7 @@ public class SafeFileOutputStream extends OutputStream { private final String targetPath; private final ByteArrayOutputStream output; private static final String EXTENSION = ".bak"; //$NON-NLS-1$ + private static final Map fileHashes = Collections.synchronizedMap(new HashMap<>()); /** * Creates an output stream on a file at the given location @@ -59,37 +65,45 @@ public SafeFileOutputStream(String targetPath, String tempPath) throws IOExcepti @Override public void close() throws IOException { File target = new File(targetPath); + Path targetP = target.toPath(); File temp = getTempFile(); + byte[] newContent = output.toByteArray(); + Integer newHash = hash(newContent); + Integer oldHash = fileHashes.put(targetP, newHash); if (!target.exists()) { if (!temp.exists()) { - Files.write(target.toPath(), output.toByteArray()); + Files.write(targetP, newContent); return; } // If we do not have a file at target location, but we do have at temp location, // it probably means something wrong happened the last time we tried to write // it. // So, try to recover the backup file. And, if successful, write the new one. - Files.copy(temp.toPath(), target.toPath()); + Files.copy(temp.toPath(), targetP); } - byte[] oldContent = Files.readAllBytes(target.toPath()); - byte[] newContent = output.toByteArray(); - if (Arrays.equals(oldContent, newContent)) { - return; + + if (Objects.equals(oldHash, newHash)) { + // quick path: since hash did not change it is likely that content did not + // change: + byte[] oldContent = Files.readAllBytes(targetP); + if (Arrays.equals(oldContent, newContent)) { + return; + } } try { Files.write(temp.toPath(), newContent); - commit(temp, target); + commit(temp, targetP); } catch (IOException e) { temp.delete(); throw e; // rethrow } } - private void commit(File temp, File target) throws IOException { + private void commit(File temp, Path targetP) throws IOException { if (!temp.exists()) return; - Files.copy(temp.toPath(), target.toPath(), StandardCopyOption.REPLACE_EXISTING); + Files.copy(temp.toPath(), targetP, StandardCopyOption.REPLACE_EXISTING); temp.delete(); } @@ -111,4 +125,14 @@ public String getTempFilePath() { public void write(int b) throws IOException { output.write(b); } + + private static int hash(byte[] content) { + return Arrays.hashCode(content) + content.length; + } + + public static byte[] read(Path path) throws IOException { + byte[] content = Files.readAllBytes(path); + fileHashes.put(path, hash(content)); + return content; + } }