title: Garbage friendly alternative to substring()
body: <p>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.</p>
  
  <p>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.</p>
  
  <pre><code>static int countFields(String s) {
      int n = 1;
      for (int i = 0; i &lt; 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 &lt; 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;
  }
  </code></pre>
  
  <p>EDIT 1, about java version:</p>
  
  <p>For business reasons we are stuck at JDK 1.6.0_25.</p>
  
  <p>EDIT 2 about String and String[] usage:</p>
  
  <p>The String[] is used to perform the system setup logic. Basically, if String[0].equals("true") then enable debugging. That's the usage pattern</p>
  
  <p>EDIT 3 about garbage collected objects:</p>
  
  <p>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.</p>
  
  <p>EDIT - SOLUTION:</p>
  
  <p>This is a combo of Peter Lawrey and zapl's solutions. Also, this class is NOT thread safe.</p>
  
  <pre><code>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 &gt;= 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 &lt; fieldIndex) {
              startIndex = line.indexOf(delimiter, startIndex);
  
              // not enough fields
              if (startIndex &lt; 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 &lt; 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 &lt; 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 &gt; 0) {
                  char firstChar = (char) bytes[0];
                  if (firstChar &lt; '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 &lt; len) {
                      // Accumulating negatively avoids surprises near MAX_VALUE
                      digit = Character.digit(bytes[i++], radix);
                      if (digit &lt; 0)
                          throw new NumberFormatException("Single digit is less than zero.");
                      if (result &lt; multmin)
                          throw new NumberFormatException("Result is less than limit.");
  
                      result *= radix;
                      if (result &lt; limit + digit)
                          throw new NumberFormatException("Result is less than limit plus new digit.");
  
                      result -= digit;
                  }
              } else {
                  throw new NumberFormatException("Called readLong with a length &lt;= 0. len=" + len);
              }
              return negative ? result : -result;
          }
      }
  }
  </code></pre>