From b8d561932f091475dccb31dcfd45b6c3cec2bc8f Mon Sep 17 00:00:00 2001 From: Alexey Kudravtsev Date: Fri, 28 Sep 2018 14:47:13 +0300 Subject: [PATCH] memory optimization: store byte array instead of String in FilePartNode --- .../openapi/vfs/impl/FilePointerPartNode.java | 24 +++++----- .../impl/VirtualFilePointerManagerImpl.java | 2 +- .../util/text/ByteArrayCharSequence.java | 44 ++++++++++++++----- .../com/intellij/util/text/ImmutableText.java | 31 +------------ 4 files changed, 47 insertions(+), 54 deletions(-) diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/impl/FilePointerPartNode.java b/platform/platform-impl/src/com/intellij/openapi/vfs/impl/FilePointerPartNode.java index 25ef3e699900..7571894b1f5d 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/impl/FilePointerPartNode.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/impl/FilePointerPartNode.java @@ -27,6 +27,7 @@ import com.intellij.openapi.vfs.pointers.VirtualFilePointer; import com.intellij.openapi.vfs.pointers.VirtualFilePointerManager; import com.intellij.util.ArrayUtil; import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.text.ByteArrayCharSequence; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -37,7 +38,7 @@ import java.util.List; // all file pointers we store in the tree with nodes corresponding to the file structure on disk class FilePointerPartNode { private static final FilePointerPartNode[] EMPTY_ARRAY = new FilePointerPartNode[0]; - @NotNull String part; // common prefix of all file pointers beneath + @NotNull CharSequence part; // common prefix of all file pointers beneath @NotNull FilePointerPartNode[] children; FilePointerPartNode parent; // file pointers for this exact path (e.g. concatenation of all "part" fields down from the root). @@ -52,8 +53,8 @@ class FilePointerPartNode { int pointersUnder; // number of alive pointers in this node plus all nodes beneath private static final VirtualFileManager ourFileManager = VirtualFileManager.getInstance(); - FilePointerPartNode(@NotNull String part, FilePointerPartNode parent, Pair fileAndUrl, int pointersToStore) { - this.part = part; + FilePointerPartNode(@NotNull CharSequence part, FilePointerPartNode parent, Pair fileAndUrl, int pointersToStore) { + this.part = ByteArrayCharSequence.convertToBytesIfAsciiString(part); this.parent = parent; children = EMPTY_ARRAY; myFileAndUrl = fileAndUrl; @@ -172,11 +173,11 @@ class FilePointerPartNode { } private void doCheckConsistency(boolean dotDotOccurred) { - int ddi = part.indexOf(".."); - if (ddi != -1) { + int dotDotIndex = StringUtil.indexOf(part, ".."); + if (dotDotIndex != -1) { // part must not contain "/.." nor "../" nor be just ".." // (except when the pointer was created from URL of non-existing file with ".." inside) - dotDotOccurred |= part.equals("..") || ddi != 0 && part.charAt(ddi-1) == '/' || ddi < part.length() - 2 && part.charAt(ddi+2) == '/'; + dotDotOccurred |= part.equals("..") || dotDotIndex != 0 && part.charAt(dotDotIndex-1) == '/' || dotDotIndex < part.length() - 2 && part.charAt(dotDotIndex+2) == '/'; } int childSum = 0; for (FilePointerPartNode child : children) { @@ -237,8 +238,8 @@ class FilePointerPartNode { if (index > start + 1 && index != path.length() && path.charAt(index - 1) == '/') index--; String pathRest = path.substring(index); FilePointerPartNode newNode = pathRest.isEmpty() ? this : new FilePointerPartNode(pathRest, this, fileAndUrl, pointersToStore); - String commonPredecessor = StringUtil.first(part, index - start, false); - FilePointerPartNode splittedAway = new FilePointerPartNode(part.substring(index - start), this, myFileAndUrl, pointersUnder); + CharSequence commonPredecessor = StringUtil.first(part, index - start, false); + FilePointerPartNode splittedAway = new FilePointerPartNode(part.subSequence(index - start, part.length()), this, myFileAndUrl, pointersUnder); splittedAway.children = children; for (FilePointerPartNode child : children) { child.parent = splittedAway; @@ -275,7 +276,7 @@ class FilePointerPartNode { return indexOfFirstDifferentChar(path, start, part, 0); } - private static boolean endsWith(@NotNull String string, @NotNull String end) { + private static boolean endsWith(@NotNull String string, @NotNull CharSequence end) { return indexOfFirstDifferentChar(string, string.length() - end.length(), end, 0) == string.length(); } @@ -335,7 +336,7 @@ class FilePointerPartNode { } // return an index in s1 of the first different char of strings s1[start1..) and s2[start2..) - private static int indexOfFirstDifferentChar(@NotNull CharSequence s1, int start1, @NotNull String s2, int start2) { + private static int indexOfFirstDifferentChar(@NotNull CharSequence s1, int start1, @NotNull CharSequence s2, int start2) { boolean ignoreCase = !SystemInfo.isFileSystemCaseSensitive; int len1 = s1.length(); int len2 = s2.length(); @@ -381,8 +382,9 @@ class FilePointerPartNode { return leaves == null ? null : leaves instanceof VirtualFilePointerImpl ? (VirtualFilePointerImpl)leaves : ((VirtualFilePointerImpl[])leaves)[0]; } + @NotNull private String getUrl() { - return parent == null ? part : parent.getUrl() + part; + return parent == null ? String.valueOf(part) : parent.getUrl() + part; } private int leavesNumber() { diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/impl/VirtualFilePointerManagerImpl.java b/platform/platform-impl/src/com/intellij/openapi/vfs/impl/VirtualFilePointerManagerImpl.java index aa3e427bc82b..152d7ce0918f 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/impl/VirtualFilePointerManagerImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/impl/VirtualFilePointerManagerImpl.java @@ -458,7 +458,7 @@ public class VirtualFilePointerManagerImpl extends VirtualFilePointerManager imp Pair after = node.update(); assert after != null : "can't invalidate inside modification"; String urlAfter = after.second; - if (URL_COMPARATOR.compare(urlBefore, urlAfter) != 0 || !urlAfter.endsWith(node.part)) { + if (URL_COMPARATOR.compare(urlBefore, urlAfter) != 0 || !StringUtil.endsWith(urlAfter, node.part)) { List myPointers = new SmartList<>(); node.addAllPointersTo(myPointers); diff --git a/platform/util/src/com/intellij/util/text/ByteArrayCharSequence.java b/platform/util/src/com/intellij/util/text/ByteArrayCharSequence.java index 7d1758037406..a4facbf14297 100644 --- a/platform/util/src/com/intellij/util/text/ByteArrayCharSequence.java +++ b/platform/util/src/com/intellij/util/text/ByteArrayCharSequence.java @@ -20,6 +20,7 @@ import com.intellij.openapi.util.text.CharSequenceWithStringHash; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.CharsetToolkit; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.Arrays; @@ -79,22 +80,41 @@ public class ByteArrayCharSequence implements CharSequenceWithStringHash { public static CharSequence convertToBytesIfAsciiString(@NotNull CharSequence name) { int length = name.length(); if (length == 0) return ""; - - byte[] bytes = new byte[length]; - for (int i = 0; i < length; i++) { - char c = name.charAt(i); - if (c >= 128) { - //noinspection RedundantStringConstructorCall - return new String(name.toString()); // So we don't hold whole char[] buffer of a lengthy path on JDK 6 - } - - bytes[i] = (byte)c; - } - return new ByteArrayCharSequence(bytes); + if (name instanceof ByteArrayCharSequence) return name; + byte[] bytes = toBytesIfPossible(name); + return bytes == null ? name : new ByteArrayCharSequence(bytes); } @NotNull byte[] getBytes() { return myStart == 0 && myEnd == myChars.length ? myChars : Arrays.copyOfRange(myChars, myStart , myEnd); } + + @Nullable + static byte[] toBytesIfPossible(CharSequence seq) { + if (seq instanceof ByteArrayCharSequence) { + return ((ByteArrayCharSequence)seq).getBytes(); + } + byte[] bytes = new byte[seq.length()]; + char[] chars = CharArrayUtil.fromSequenceWithoutCopying(seq); + if (chars == null) { + for (int i = 0; i < bytes.length; i++) { + char c = seq.charAt(i); + if ((c & 0xff00) != 0) { + return null; + } + bytes[i] = (byte)c; + } + } + else { + for (int i = 0; i < bytes.length; i++) { + char c = chars[i]; + if ((c & 0xff00) != 0) { + return null; + } + bytes[i] = (byte)c; + } + } + return bytes; + } } diff --git a/platform/util/src/com/intellij/util/text/ImmutableText.java b/platform/util/src/com/intellij/util/text/ImmutableText.java index 5ea693551409..d641098f2420 100644 --- a/platform/util/src/com/intellij/util/text/ImmutableText.java +++ b/platform/util/src/com/intellij/util/text/ImmutableText.java @@ -31,7 +31,6 @@ import com.intellij.openapi.util.text.CharSequenceWithStringHash; import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.ArrayUtil; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; /** * A pruned and optimized version of javolution.text.Text @@ -89,7 +88,7 @@ final class ImmutableText extends ImmutableCharSequence implements CharArrayExte } private static LeafNode createLeafNode(@NotNull CharSequence str) { - byte[] bytes = toBytesIfPossible(str); + byte[] bytes = ByteArrayCharSequence.toBytesIfPossible(str); if (bytes != null) { return new Leaf8BitNode(bytes); } @@ -98,34 +97,6 @@ final class ImmutableText extends ImmutableCharSequence implements CharArrayExte return new WideLeafNode(chars); } - @Nullable - private static byte[] toBytesIfPossible(CharSequence seq) { - if (seq instanceof ByteArrayCharSequence) { - return ((ByteArrayCharSequence)seq).getBytes(); - } - byte[] bytes = new byte[seq.length()]; - char[] chars = CharArrayUtil.fromSequenceWithoutCopying(seq); - if (chars == null) { - for (int i = 0; i < bytes.length; i++) { - char c = seq.charAt(i); - if ((c & 0xff00) != 0) { - return null; - } - bytes[i] = (byte)c; - } - } - else { - for (int i = 0; i < bytes.length; i++) { - char c = chars[i]; - if ((c & 0xff00) != 0) { - return null; - } - bytes[i] = (byte)c; - } - } - return bytes; - } - /** * When first loaded, ImmutableText contents are stored as a single large array. This saves memory but isn't * modification-friendly as it disallows slightly changed texts to retain most of the internal structure of the