Garbage friendly alternative to substring()
garbage-collection, java, string
Solution
Basically, `if String[0].equals("true")` then enable debugging.
You can get rid of the creation of the array and substrings by comparing directly to the input string. That's not avoiding to create the input string like Peter Lawrey's solution does but could be less work to change (although I doubt that).
public static boolean fieldMatches(String line, int fieldIndex, String other) {
int currentField = 0;
int startIndex = 0;
while (currentField < fieldIndex) {
startIndex = line.indexOf('|', startIndex);
// not enough fields
if (startIndex < 0)
return false;
startIndex++;
currentField++;
}
int start = startIndex;
int end = line.indexOf('|', startIndex);
if (end == -1) {
end = line.length();
}
int fieldLength = end - start;
// make sure both strings have the same length
if (fieldLength != other.length())
return false;
// regionMatches does not allocate objects
return line.regionMatches(start, other, 0, fieldLength);
}
public static void main(String[] args) {
String line = "Config|true"; // from BufferedReader
System.out.println(fieldMatches(line, 0, "Config"));
System.out.println(fieldMatches(line, 1, "true"));
System.out.println(fieldMatches(line, 1, "foobar"));
System.out.println(fieldMatches(line, 2, "thereisnofield"));
}
Output
true
true
false
false
Problem
I have a pipe delimited file which I parse to get system options. The environment is sensitive to heap allocation and we are trying to avoid garbage collection. Below is the code I am using to parse the pipe delimited string. This function is called about 35000 times. I was wondering if there was a better approach that doesn't create as much memory churn. ``` static int countFields(String s) { int n = 1; for (int i = 0; i < s.length(); i++) if (s.charAt(i) == '|') n++; return n; } static String[] splitFields(String s) { String[] l = new String[countFields(s)]; for (int pos = 0, i = 0; i < l.length; i++) { int end = s.indexOf('|', pos); if (end == -1) end = s.length(); l[i] = s.substring(pos, end); pos = end + 1; } return l; } ``` EDIT 1, about java version: For business reasons we are stuck at JDK 1.6.0_25. EDIT 2 about String and String[] usage: The String[] is used to perform the system setup logic. Basically, if String[0].equals("true") then enable debugging. That's the usage pattern EDIT 3 about garbage collected objects: The input String and the String[] are eventually GC'd. The input String is a single line from system setup file which is GC'd after the entire file is processed and the String[] is GC'd after the entire line has been processed. EDIT - SOLUTION: This is a combo of Peter Lawrey and zapl's solutions. Also, this class is NOT thread safe. ``` public class DelimitedString { private static final Field EMPTY = new Field(""); private char delimiter = '|'; private String line = null; private Field field = new Field(); public DelimitedString() { } public DelimitedString(char delimiter) { this.delimiter = delimiter; } public void set(String line) { this.line = line; } public int length() { int numberOfFields = 0; if (line == null) return numberOfFields; int idx = line.indexOf(delimiter); while (idx >= 0) { numberOfFields++; idx = line.indexOf(delimiter, idx + 1); } return ++numberOfFields; } public Field get(int fieldIndex) { if (line == null) return EMPTY; int currentField = 0; int startIndex = 0; while (currentField < fieldIndex) { startIndex = line.indexOf(delimiter, startIndex); // not enough fields if (startIndex < 0) return EMPTY; startIndex++; currentField++; } int endIndex = line.indexOf(delimiter, startIndex); if (endIndex == -1) endIndex = line.length(); fieldLength = endIndex - startIndex; if (fieldLength == 0) return EMPTY; // Populate field for (int i = 0; i < fieldLength; i++) { char c = line.charAt(startIndex + i); field.bytes[i] = (byte) c; } field.fieldLength = fieldLength; return field; } @Override public String toString() { return new String(line + " current field = " + field.toString()); } public static class Field { // Max size of a field private static final int DEFAULT_SIZE = 1024; private byte[] bytes = null; private int fieldLength = Integer.MIN_VALUE; public Field() { bytes = new byte[DEFAULT_SIZE]; fieldLength = Integer.MIN_VALUE; } public Field(byte[] bytes) { set(bytes); } public Field(String str) { set(str.getBytes()); } public void set(byte[] str) { int len = str.length; bytes = new byte[len]; for (int i = 0; i < len; i++) { byte b = str[i]; bytes[i] = b; } fieldLength = len; } public char charAt(int i) { return (char) bytes[i]; } public byte[] getBytes() { return bytes; } public int length() { return fieldLength; } public short getShort() { return (short) readLong(); } public int getInt() { return (int) readLong(); } public long getLong() { return readLong(); } @Override public String toString() { return (new String(bytes, 0, fieldLength)); } // Code taken from Java class Long method parseLong() public long readLong() { int radix = 10; long result = 0; boolean negative = false; int i = 0, len = fieldLength; long limit = -Long.MAX_VALUE; long multmin; int digit; if (len > 0) { char firstChar = (char) bytes[0]; if (firstChar < '0') { // Possible leading "-" if (firstChar == '-') { negative = true; limit = Long.MIN_VALUE; } else throw new NumberFormatException("Invalid leading character."); if (len == 1) // Cannot have lone "-" throw new NumberFormatException("Negative sign without trailing digits."); i++; } multmin = limit / radix; while (i < len) { // Accumulating negatively avoids surprises near MAX_VALUE digit = Character.digit(bytes[i++], radix); if (digit < 0) throw new NumberFormatException("Single digit is less than zero."); if (result < multmin) throw new NumberFormatException("Result is less than limit."); result *= radix; if (result < limit + digit) throw new NumberFormatException("Result is less than limit plus new digit."); result -= digit; } } else { throw new NumberFormatException("Called readLong with a length <= 0. len=" + len); } return negative ? result : -result; } } } ```