memory optimization: store byte array instead of String in FilePartNode

This commit is contained in:
Alexey Kudravtsev
2018-09-28 14:51:38 +03:00
parent 76d2828a85
commit b8d561932f
4 changed files with 47 additions and 54 deletions
@@ -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<VirtualFile,String> fileAndUrl, int pointersToStore) {
this.part = part;
FilePointerPartNode(@NotNull CharSequence part, FilePointerPartNode parent, Pair<VirtualFile,String> 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() {
@@ -458,7 +458,7 @@ public class VirtualFilePointerManagerImpl extends VirtualFilePointerManager imp
Pair<VirtualFile,String> 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<VirtualFilePointerImpl> myPointers = new SmartList<>();
node.addAllPointersTo(myPointers);
@@ -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;
}
}
@@ -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