Index: src/test/org/apache/lucene/index/TestIndexWriter.java
===================================================================
--- src/test/org/apache/lucene/index/TestIndexWriter.java	(revision 915199)
+++ src/test/org/apache/lucene/index/TestIndexWriter.java	(working copy)
@@ -3308,7 +3308,7 @@
   // LUCENE-510
   public void testAllUnicodeChars() throws Throwable {
 
-    UnicodeUtil.UTF8Result utf8 = new UnicodeUtil.UTF8Result();
+    BytesRef utf8 = new BytesRef(10);
     UnicodeUtil.UTF16Result utf16 = new UnicodeUtil.UTF16Result();
     char[] chars = new char[2];
     for(int ch=0;ch<0x0010FFFF;ch++) {
@@ -3328,16 +3328,16 @@
       UnicodeUtil.UTF16toUTF8(chars, 0, len, utf8);
       
       String s1 = new String(chars, 0, len);
-      String s2 = new String(utf8.result, 0, utf8.length, "UTF-8");
+      String s2 = new String(utf8.bytes, 0, utf8.length, "UTF-8");
       assertEquals("codepoint " + ch, s1, s2);
 
-      UnicodeUtil.UTF8toUTF16(utf8.result, 0, utf8.length, utf16);
+      UnicodeUtil.UTF8toUTF16(utf8.bytes, 0, utf8.length, utf16);
       assertEquals("codepoint " + ch, s1, new String(utf16.result, 0, utf16.length));
 
       byte[] b = s1.getBytes("UTF-8");
       assertEquals(utf8.length, b.length);
       for(int j=0;j<utf8.length;j++)
-        assertEquals(utf8.result[j], b[j]);
+        assertEquals(utf8.bytes[j], b[j]);
     }
   }
 
@@ -3402,7 +3402,7 @@
     char[] buffer = new char[20];
     char[] expected = new char[20];
 
-    UnicodeUtil.UTF8Result utf8 = new UnicodeUtil.UTF8Result();
+    BytesRef utf8 = new BytesRef(20);
     UnicodeUtil.UTF16Result utf16 = new UnicodeUtil.UTF16Result();
 
     for(int iter=0;iter<100000;iter++) {
@@ -3413,10 +3413,10 @@
         byte[] b = new String(buffer, 0, 20).getBytes("UTF-8");
         assertEquals(b.length, utf8.length);
         for(int i=0;i<b.length;i++)
-          assertEquals(b[i], utf8.result[i]);
+          assertEquals(b[i], utf8.bytes[i]);
       }
 
-      UnicodeUtil.UTF8toUTF16(utf8.result, 0, utf8.length, utf16);
+      UnicodeUtil.UTF8toUTF16(utf8.bytes, 0, utf8.length, utf16);
       assertEquals(utf16.length, 20);
       for(int i=0;i<20;i++)
         assertEquals(expected[i], utf16.result[i]);
@@ -3429,7 +3429,7 @@
     char[] buffer = new char[20];
     char[] expected = new char[20];
 
-    UnicodeUtil.UTF8Result utf8 = new UnicodeUtil.UTF8Result();
+    BytesRef utf8 = new BytesRef(new byte[20]);
     UnicodeUtil.UTF16Result utf16 = new UnicodeUtil.UTF16Result();
     UnicodeUtil.UTF16Result utf16a = new UnicodeUtil.UTF16Result();
 
@@ -3452,7 +3452,7 @@
         byte[] b = new String(buffer, 0, 20).getBytes("UTF-8");
         assertEquals(b.length, utf8.length);
         for(int i=0;i<b.length;i++)
-          assertEquals(b[i], utf8.result[i]);
+          assertEquals(b[i], utf8.bytes[i]);
       }
 
       int bytePrefix = 20;
@@ -3460,18 +3460,18 @@
         bytePrefix = 0;
       else
         for(int i=0;i<20;i++)
-          if (last[i] != utf8.result[i]) {
+          if (last[i] != utf8.bytes[i]) {
             bytePrefix = i;
             break;
           }
-      System.arraycopy(utf8.result, 0, last, 0, utf8.length);
+      System.arraycopy(utf8.bytes, 0, last, 0, utf8.length);
 
-      UnicodeUtil.UTF8toUTF16(utf8.result, bytePrefix, utf8.length-bytePrefix, utf16);
+      UnicodeUtil.UTF8toUTF16(utf8.bytes, bytePrefix, utf8.length-bytePrefix, utf16);
       assertEquals(20, utf16.length);
       for(int i=0;i<20;i++)
         assertEquals(expected[i], utf16.result[i]);
 
-      UnicodeUtil.UTF8toUTF16(utf8.result, 0, utf8.length, utf16a);
+      UnicodeUtil.UTF8toUTF16(utf8.bytes, 0, utf8.length, utf16a);
       assertEquals(20, utf16a.length);
       for(int i=0;i<20;i++)
         assertEquals(expected[i], utf16a.result[i]);
Index: src/test/org/apache/lucene/index/TestPayloads.java
===================================================================
--- src/test/org/apache/lucene/index/TestPayloads.java	(revision 915199)
+++ src/test/org/apache/lucene/index/TestPayloads.java	(working copy)
@@ -39,6 +39,7 @@
 import org.apache.lucene.store.Directory;
 import org.apache.lucene.store.FSDirectory;
 import org.apache.lucene.store.MockRAMDirectory;
+import org.apache.lucene.util.BytesRef;
 import org.apache.lucene.util.LuceneTestCase;
 import org.apache.lucene.util.UnicodeUtil;
 import org.apache.lucene.util._TestUtil;
@@ -573,13 +574,13 @@
             }
         }
         
-        private UnicodeUtil.UTF8Result utf8Result = new UnicodeUtil.UTF8Result();
+        private BytesRef utf8Result = new BytesRef(10);
 
         synchronized String bytesToString(byte[] bytes) {
             String s = new String(bytes);
             UnicodeUtil.UTF16toUTF8(s, 0, s.length(), utf8Result);
             try {
-                return new String(utf8Result.result, 0, utf8Result.length, "UTF-8");
+                return new String(utf8Result.bytes, 0, utf8Result.length, "UTF-8");
             } catch (UnsupportedEncodingException uee) {
                 return null;
             }
Index: src/java/org/apache/lucene/search/AutomatonTermsEnum.java
===================================================================
--- src/java/org/apache/lucene/search/AutomatonTermsEnum.java	(revision 915199)
+++ src/java/org/apache/lucene/search/AutomatonTermsEnum.java	(working copy)
@@ -73,10 +73,8 @@
   private final BitSet visited;
   // used for unicode conversion from BytesRef byte[] to char[]
   private final UnicodeUtil.UTF16Result utf16 = new UnicodeUtil.UTF16Result();
-  // used for unicode conversion from char[] to BytesRef byte[]
-  private final UnicodeUtil.UTF8Result utf8 = new UnicodeUtil.UTF8Result();
   // the reference used for seeking forwards through the term dictionary
-  private final BytesRef seekBytesRef = new BytesRef();
+  private final BytesRef seekBytesRef = new BytesRef(10);
   
   // this accept stati will be returned by accept() dependent on internal mode
   private final AcceptStatus NO_MATCH, YES_MATCH;
@@ -205,10 +203,7 @@
         if (!nextString())
           return null;
         UnicodeUtil.nextValidUTF16String(utf16);
-        UnicodeUtil.UTF16toUTF8(utf16.result, 0, utf16.length, utf8);
-        seekBytesRef.bytes = utf8.result;
-        seekBytesRef.offset = 0;
-        seekBytesRef.length = utf8.length;
+        UnicodeUtil.UTF16toUTF8(utf16.result, 0, utf16.length, seekBytesRef);
       }
       return seekBytesRef;
     } else if (!linearMode) {
@@ -217,10 +212,7 @@
       if (nextString()) {
         // reposition
         UnicodeUtil.nextValidUTF16String(utf16);
-        UnicodeUtil.UTF16toUTF8(utf16.result, 0, utf16.length, utf8);
-        seekBytesRef.bytes = utf8.result;
-        seekBytesRef.offset = 0;
-        seekBytesRef.length = utf8.length;
+        UnicodeUtil.UTF16toUTF8(utf16.result, 0, utf16.length, seekBytesRef);
         return seekBytesRef;
       }
     }
Index: src/java/org/apache/lucene/index/TermsHashPerField.java
===================================================================
--- src/java/org/apache/lucene/index/TermsHashPerField.java	(revision 915199)
+++ src/java/org/apache/lucene/index/TermsHashPerField.java	(working copy)
@@ -51,7 +51,7 @@
   private int postingsHashMask = postingsHashSize-1;
   private RawPostingList[] postingsHash = new RawPostingList[postingsHashSize];
   private RawPostingList p;
-  private final UnicodeUtil.UTF8Result utf8;
+  private final BytesRef utf8;
   private BytesRef.Comparator termComp;
 
   // nocommit -- move to thread level
@@ -249,7 +249,7 @@
     }
 
     if (len == utf8.length) {
-      final byte[] utf8Bytes = utf8.result;
+      final byte[] utf8Bytes = utf8.bytes;
       for(int tokenPos=0;tokenPos<utf8.length;pos++,tokenPos++) {
         if (utf8Bytes[tokenPos] != text[pos]) {
           return false;
@@ -366,20 +366,13 @@
     // term text into textStart address
 
     // Get the text of this term.
-    final char[] tokenText = termAtt.termBuffer();;
+    final char[] tokenText = termAtt.termBuffer();
     final int tokenTextLen = termAtt.termLength();
     
     //System.out.println("\nfield=" + fieldInfo.name + " add text=" + new String(tokenText, 0, tokenTextLen) + " len=" + tokenTextLen);
     
-    UnicodeUtil.UTF16toUTF8(tokenText, 0, tokenTextLen, utf8);
+    int code = UnicodeUtil.UTF16toUTF8WithHash(tokenText, 0, tokenTextLen, utf8);
 
-    // nocommit -- modify UnicodeUtil to compute hash for us
-    // so we don't do 2nd pass here
-    int code = 0;
-    for(int i=0;i<utf8.length;i++) {
-      code = 31*code + utf8.result[i];
-    }
-
     int hashPos = code & postingsHashMask;
 
     // Locate RawPostingList in hash
@@ -445,13 +438,13 @@
         // 1 byte to store length
         text[textUpto] = (byte) utf8.length;
         bytePool.byteUpto += utf8.length + 1;
-        System.arraycopy(utf8.result, 0, text, textUpto+1, utf8.length);
+        System.arraycopy(utf8.bytes, 0, text, textUpto+1, utf8.length);
       } else {
         // 2 byte to store length
         text[textUpto] = (byte) (0x80 | (utf8.length & 0x7f));
         text[textUpto+1] = (byte) ((utf8.length>>7) & 0xff);
         bytePool.byteUpto += utf8.length + 2;
-        System.arraycopy(utf8.result, 0, text, textUpto+2, utf8.length);
+        System.arraycopy(utf8.bytes, 0, text, textUpto+2, utf8.length);
       }
 
       assert postingsHash[hashPos] == null;
Index: src/java/org/apache/lucene/index/FreqProxTermsWriter.java
===================================================================
--- src/java/org/apache/lucene/index/FreqProxTermsWriter.java	(revision 915199)
+++ src/java/org/apache/lucene/index/FreqProxTermsWriter.java	(working copy)
@@ -307,8 +307,6 @@
     termsConsumer.finish();
   }
 
-  final UnicodeUtil.UTF8Result termsUTF8 = new UnicodeUtil.UTF8Result();
-
   //nocommit: needed?
   void files(Collection<String> files) {}
 
Index: src/java/org/apache/lucene/index/TermsHashPerThread.java
===================================================================
--- src/java/org/apache/lucene/index/TermsHashPerThread.java	(revision 915199)
+++ src/java/org/apache/lucene/index/TermsHashPerThread.java	(working copy)
@@ -17,7 +17,7 @@
  * limitations under the License.
  */
 
-import org.apache.lucene.util.UnicodeUtil;
+import org.apache.lucene.util.BytesRef;
 
 import java.io.IOException;
 
@@ -37,7 +37,7 @@
   int freePostingsCount;
 
   // Used by perField:
-  final UnicodeUtil.UTF8Result utf8 = new UnicodeUtil.UTF8Result();
+  final BytesRef utf8 = new BytesRef(10);
 
   public TermsHashPerThread(DocInverterPerThread docInverterPerThread, final TermsHash termsHash, final TermsHash nextTermsHash, final TermsHashPerThread primaryPerThread) {
     docState = docInverterPerThread.docState;
Index: src/java/org/apache/lucene/index/TermVectorsWriter.java
===================================================================
--- src/java/org/apache/lucene/index/TermVectorsWriter.java	(revision 915199)
+++ src/java/org/apache/lucene/index/TermVectorsWriter.java	(working copy)
@@ -19,6 +19,7 @@
 
 import org.apache.lucene.store.Directory;
 import org.apache.lucene.store.IndexOutput;
+import org.apache.lucene.util.BytesRef;
 import org.apache.lucene.util.StringHelper;
 import org.apache.lucene.util.UnicodeUtil;
 
@@ -28,8 +29,7 @@
   
   private IndexOutput tvx = null, tvd = null, tvf = null;
   private FieldInfos fieldInfos;
-  final UnicodeUtil.UTF8Result[] utf8Results = new UnicodeUtil.UTF8Result[] {new UnicodeUtil.UTF8Result(),
-                                                                             new UnicodeUtil.UTF8Result()};
+  final BytesRef[] utf8Results = new BytesRef[] {new BytesRef(10), new BytesRef(10)};
 
   public TermVectorsWriter(Directory directory, String segment,
                            FieldInfos fieldInfos)
@@ -107,14 +107,14 @@
 
           UnicodeUtil.UTF16toUTF8(terms[j], 0, terms[j].length(), utf8Results[utf8Upto]);
           
-          int start = StringHelper.bytesDifference(utf8Results[1-utf8Upto].result,
+          int start = StringHelper.bytesDifference(utf8Results[1-utf8Upto].bytes,
                                                    utf8Results[1-utf8Upto].length,
-                                                   utf8Results[utf8Upto].result,
+                                                   utf8Results[utf8Upto].bytes,
                                                    utf8Results[utf8Upto].length);
           int length = utf8Results[utf8Upto].length - start;
           tvf.writeVInt(start);       // write shared prefix length
           tvf.writeVInt(length);        // write delta length
-          tvf.writeBytes(utf8Results[utf8Upto].result, start, length);  // write delta bytes
+          tvf.writeBytes(utf8Results[utf8Upto].bytes, start, length);  // write delta bytes
           utf8Upto = 1-utf8Upto;
 
           final int termFreq = freqs[j];
Index: src/java/org/apache/lucene/index/codecs/preflex/TermBuffer.java
===================================================================
--- src/java/org/apache/lucene/index/codecs/preflex/TermBuffer.java	(revision 915199)
+++ src/java/org/apache/lucene/index/codecs/preflex/TermBuffer.java	(working copy)
@@ -19,6 +19,8 @@
 
 import java.io.IOException;
 import org.apache.lucene.store.IndexInput;
+import org.apache.lucene.util.ArrayUtil;
+import org.apache.lucene.util.BytesRef;
 import org.apache.lucene.util.UnicodeUtil;
 import org.apache.lucene.index.Term;
 import org.apache.lucene.index.FieldInfos;
@@ -31,7 +33,7 @@
   private boolean dirty;                          // true if text was set externally (ie not read via UTF8 bytes)
 
   private UnicodeUtil.UTF16Result text = new UnicodeUtil.UTF16Result();
-  private UnicodeUtil.UTF8Result bytes = new UnicodeUtil.UTF8Result();
+  private BytesRef bytes = new BytesRef(10);
 
   public final int compareTo(TermBuffer other) {
     if (field == other.field) 	  // fields are interned
@@ -74,15 +76,19 @@
       if (dirty) {
         // Fully convert all bytes since bytes is dirty
         UnicodeUtil.UTF16toUTF8(text.result, 0, text.length, bytes);
-        bytes.setLength(totalLength);
-        input.readBytes(bytes.result, start, length);
-        UnicodeUtil.UTF8toUTF16(bytes.result, 0, totalLength, text);
+        if (bytes.bytes.length < totalLength)
+          bytes.bytes = new byte[totalLength];
+        bytes.length = totalLength;
+        input.readBytes(bytes.bytes, start, length);
+        UnicodeUtil.UTF8toUTF16(bytes.bytes, 0, totalLength, text);
         dirty = false;
       } else {
         // Incrementally convert only the UTF8 bytes that are new:
-        bytes.setLength(totalLength);
-        input.readBytes(bytes.result, start, length);
-        UnicodeUtil.UTF8toUTF16(bytes.result, start, length, text);
+        if (bytes.bytes.length < totalLength)
+          bytes.bytes = ArrayUtil.grow(bytes.bytes, totalLength);
+        bytes.length = totalLength;
+        input.readBytes(bytes.bytes, start, length);
+        UnicodeUtil.UTF8toUTF16(bytes.bytes, start, length, text);
       }
     }
     this.field = fieldInfos.fieldName(input.readVInt());
@@ -134,7 +140,7 @@
     } catch (CloneNotSupportedException e) {}
 
     clone.dirty = true;
-    clone.bytes = new UnicodeUtil.UTF8Result();
+    clone.bytes = new BytesRef(10);
     clone.text = new UnicodeUtil.UTF16Result();
     clone.text.copyText(text);
     return clone;
Index: src/java/org/apache/lucene/store/IndexOutput.java
===================================================================
--- src/java/org/apache/lucene/store/IndexOutput.java	(revision 915199)
+++ src/java/org/apache/lucene/store/IndexOutput.java	(working copy)
@@ -20,6 +20,8 @@
 import java.io.IOException;
 import java.io.Closeable;
 import java.util.Map;
+
+import org.apache.lucene.util.BytesRef;
 import org.apache.lucene.util.UnicodeUtil;
 
 /** Abstract base class for output to a file in a Directory.  A random-access
@@ -29,7 +31,7 @@
  */
 public abstract class IndexOutput implements Closeable {
 
-  private UnicodeUtil.UTF8Result utf8Result = new UnicodeUtil.UTF8Result();
+  private BytesRef utf8Result = new BytesRef(10);
 
   /** Writes a single byte.
    * @see IndexInput#readByte()
@@ -103,7 +105,7 @@
   public void writeString(String s) throws IOException {
     UnicodeUtil.UTF16toUTF8(s, 0, s.length(), utf8Result);
     writeVInt(utf8Result.length);
-    writeBytes(utf8Result.result, 0, utf8Result.length);
+    writeBytes(utf8Result.bytes, 0, utf8Result.length);
   }
 
   /** Writes a sub sequence of characters from s as the old
Index: src/java/org/apache/lucene/util/BytesRef.java
===================================================================
--- src/java/org/apache/lucene/util/BytesRef.java	(revision 915199)
+++ src/java/org/apache/lucene/util/BytesRef.java	(working copy)
@@ -44,12 +44,16 @@
     this.length = bytes.length;
   }
 
+  public BytesRef(int capacity) {
+    this.bytes = new byte[capacity];
+  }
+
   /**
    * @param text Initialize the byte[] from the UTF8 bytes
    * for the provided Sring.  This must be well-formed
    * unicode text, with no unpaired surrogates or U+FFFF.
    */
-  public BytesRef(String text) {
+  public BytesRef(CharSequence text) {
     copy(text);
   }
 
@@ -57,25 +61,18 @@
     copy(other);
   }
 
-  // nocommit: we could do this w/ UnicodeUtil w/o requiring
-  // allocation of new bytes[]?
   /**
    * Copies the UTF8 bytes for this string.
    * 
    * @param text Must be well-formed unicode text, with no
    * unpaired surrogates or U+FFFF.
    */
-  public void copy(String text) {
+  public void copy(CharSequence text) {
     // nocommit -- remove this paranoia
     assert UnicodeUtil.validUTF16String(text);
-    try {
-      bytes = text.getBytes("UTF-8");
-    } catch (UnsupportedEncodingException uee) {
-      // should not happen:
-      throw new RuntimeException("unable to encode to UTF-8");
-    }
-    offset = 0;
-    length = bytes.length;
+    if (bytes == null)
+      bytes = new byte[10];
+    UnicodeUtil.UTF16toUTF8(text, 0, text.length(), this);
   }
 
   public boolean bytesEquals(BytesRef other) {
Index: src/java/org/apache/lucene/util/UnicodeUtil.java
===================================================================
--- src/java/org/apache/lucene/util/UnicodeUtil.java	(revision 915199)
+++ src/java/org/apache/lucene/util/UnicodeUtil.java	(working copy)
@@ -72,7 +72,10 @@
   private static final long HALF_SHIFT = 10;
   private static final long HALF_MASK = 0x3FFL;
 
+  /** Use {@link BytesRef} instead. */
+  @Deprecated
   public static final class UTF8Result {
+    private final BytesRef wrapper = new BytesRef();
     public byte[] result;
     public int length;
 
@@ -117,89 +120,96 @@
       length = otherLength;
     }
   }
-
+  
+  /** @deprecated Use {@link #UTF16toUTF8(char[], int, int, BytesRef)} instead. */
+  @Deprecated
+  public static void UTF16toUTF8(final char[] source, final int offset, final int length, UTF8Result result) {
+    result.wrapper.bytes = result.result;
+    result.wrapper.length = result.length;
+    UTF16toUTF8(source, offset, length, result.wrapper);
+    result.result = result.wrapper.bytes;
+    result.length = result.wrapper.length;
+  }
+  
   /** Encode characters from a char[] source, starting at
-   *  offset and stopping when the character 0xffff is seen.
-   *  Returns the number of bytes written to bytesOut.
-   *
-   * @deprecated Use {@link #UTF16toUTF8(char[], int, int,
-   * UTF8Result)} instead. */
-  @Deprecated
-  public static void UTF16toUTF8(final char[] source, final int offset, UTF8Result result) {
-
+   *  offset for length chars.  Returns a hash of the resulting bytes */
+  public static int UTF16toUTF8WithHash(final char[] source, final int offset, final int length, BytesRef result) {
+    int hash = 0;
     int upto = 0;
     int i = offset;
-    byte[] out = result.result;
+    final int end = offset + length;
+    byte[] out = result.bytes;
+    // Pre-allocate for worst case 4-for-1
+    final int maxLen = length * 4;
+    if (out.length < maxLen)
+      out = result.bytes = new byte[maxLen];
+    result.offset = 0;
 
-    while(true) {
+    while(i < end) {
       
       final int code = (int) source[i++];
 
-      if (upto+4 > out.length) {
-        byte[] newOut = new byte[2*out.length];
-        assert newOut.length >= upto+4;
-        System.arraycopy(out, 0, newOut, 0, upto);
-        result.result = out = newOut;
-      }
-      if (code < 0x80)
-        out[upto++] = (byte) code;
-      else if (code < 0x800) {
-        out[upto++] = (byte) (0xC0 | (code >> 6));
-        out[upto++] = (byte)(0x80 | (code & 0x3F));
+      if (code < 0x80) {
+        hash = 31*hash + (out[upto++] = (byte) code);
+      } else if (code < 0x800) {
+        hash = 31*hash + (out[upto++] = (byte) (0xC0 | (code >> 6)));
+        hash = 31*hash + (out[upto++] = (byte)(0x80 | (code & 0x3F)));
       } else if (code < 0xD800 || code > 0xDFFF) {
-        if (code == 0xffff)
-          // END
-          break;
-        out[upto++] = (byte)(0xE0 | (code >> 12));
-        out[upto++] = (byte)(0x80 | ((code >> 6) & 0x3F));
-        out[upto++] = (byte)(0x80 | (code & 0x3F));
+        hash = 31*hash + (out[upto++] = (byte)(0xE0 | (code >> 12)));
+        hash = 31*hash + (out[upto++] = (byte)(0x80 | ((code >> 6) & 0x3F)));
+        hash = 31*hash + (out[upto++] = (byte)(0x80 | (code & 0x3F)));
       } else {
         // surrogate pair
         // confirm valid high surrogate
-        if (code < 0xDC00 && source[i] != 0xffff) {
+        // nocommit -- I removed the 0xffff check, here, but
+        // technically that's a break in back-compat, though
+        // it seems crazy that any external apps would rely
+        // on this?
+        //if (code < 0xDC00 && i < end && source[i] != 0xffff) {
+        if (code < 0xDC00 && i < end) {
           int utf32 = (int) source[i];
           // confirm valid low surrogate and write pair
           if (utf32 >= 0xDC00 && utf32 <= 0xDFFF) { 
             utf32 = ((code - 0xD7C0) << 10) + (utf32 & 0x3FF);
             i++;
-            out[upto++] = (byte)(0xF0 | (utf32 >> 18));
-            out[upto++] = (byte)(0x80 | ((utf32 >> 12) & 0x3F));
-            out[upto++] = (byte)(0x80 | ((utf32 >> 6) & 0x3F));
-            out[upto++] = (byte)(0x80 | (utf32 & 0x3F));
+            hash = 31*hash + (out[upto++] = (byte)(0xF0 | (utf32 >> 18)));
+            hash = 31*hash + (out[upto++] = (byte)(0x80 | ((utf32 >> 12) & 0x3F)));
+            hash = 31*hash + (out[upto++] = (byte)(0x80 | ((utf32 >> 6) & 0x3F)));
+            hash = 31*hash + (out[upto++] = (byte)(0x80 | (utf32 & 0x3F)));
             continue;
           }
         }
         // replace unpaired surrogate or out-of-order low surrogate
         // with substitution character
-        out[upto++] = (byte) 0xEF;
-        out[upto++] = (byte) 0xBF;
-        out[upto++] = (byte) 0xBD;
+        hash = 31*hash + (out[upto++] = (byte) 0xEF);
+        hash = 31*hash + (out[upto++] = (byte) 0xBF);
+        hash = 31*hash + (out[upto++] = (byte) 0xBD);
       }
     }
-    //assert matches(source, offset, i-offset-1, out, upto);
+    //assert matches(source, offset, length, out, upto);
     result.length = upto;
+    return hash;
   }
 
   /** Encode characters from a char[] source, starting at
    *  offset for length chars.  Returns the number of bytes
    *  written to bytesOut. */
-  public static void UTF16toUTF8(final char[] source, final int offset, final int length, UTF8Result result) {
+  public static void UTF16toUTF8(final char[] source, final int offset, final int length, BytesRef result) {
 
     int upto = 0;
     int i = offset;
     final int end = offset + length;
-    byte[] out = result.result;
+    byte[] out = result.bytes;
+    // Pre-allocate for worst case 4-for-1
+    final int maxLen = length * 4;
+    if (out.length < maxLen)
+      out = result.bytes = new byte[maxLen];
+    result.offset = 0;
 
     while(i < end) {
       
       final int code = (int) source[i++];
 
-      if (upto+4 > out.length) {
-        byte[] newOut = new byte[2*out.length];
-        assert newOut.length >= upto+4;
-        System.arraycopy(out, 0, newOut, 0, upto);
-        result.result = out = newOut;
-      }
       if (code < 0x80)
         out[upto++] = (byte) code;
       else if (code < 0x800) {
@@ -241,24 +251,33 @@
     result.length = upto;
   }
 
+  /** @deprecated Use {@link #UTF16toUTF8(CharSequence, int, int, BytesRef)} instead. */
+  @Deprecated
+  public static void UTF16toUTF8(String source, final int offset, final int length, UTF8Result result) {
+    result.wrapper.bytes = result.result;
+    result.wrapper.length = result.length;
+    UTF16toUTF8(source, offset, length, result.wrapper);
+    result.result = result.wrapper.bytes;
+    result.length = result.wrapper.length;
+  }
+  
   /** Encode characters from this String, starting at offset
    *  for length characters.  Returns the number of bytes
    *  written to bytesOut. */
-  public static void UTF16toUTF8(final String s, final int offset, final int length, UTF8Result result) {
+  public static void UTF16toUTF8(final CharSequence s, final int offset, final int length, BytesRef result) {
     final int end = offset + length;
 
-    byte[] out = result.result;
+    byte[] out = result.bytes;
+    result.offset = 0;
+    // Pre-allocate for worst case 4-for-1
+    final int maxLen = length * 4;
+    if (out.length < maxLen)
+      out = result.bytes = new byte[maxLen];
 
     int upto = 0;
     for(int i=offset;i<end;i++) {
       final int code = (int) s.charAt(i);
 
-      if (upto+4 > out.length) {
-        byte[] newOut = new byte[2*out.length];
-        assert newOut.length >= upto+4;
-        System.arraycopy(out, 0, newOut, 0, upto);
-        result.result = out = newOut;
-      }
       if (code < 0x80)
         out[upto++] = (byte) code;
       else if (code < 0x800) {
@@ -479,7 +498,7 @@
     }
   }
   */
-  public static final boolean validUTF16String(String s) {
+  public static final boolean validUTF16String(CharSequence s) {
     final int size = s.length();
     for(int i=0;i<size;i++) {
       char ch = s.charAt(i);
Index: src/java/org/apache/lucene/document/CompressionTools.java
===================================================================
--- src/java/org/apache/lucene/document/CompressionTools.java	(revision 915199)
+++ src/java/org/apache/lucene/document/CompressionTools.java	(working copy)
@@ -21,6 +21,8 @@
 import java.util.zip.Inflater;
 import java.util.zip.DataFormatException;
 import java.io.ByteArrayOutputStream;
+
+import org.apache.lucene.util.BytesRef;
 import org.apache.lucene.util.UnicodeUtil;
 
 /** Simple utility class providing static methods to
@@ -84,9 +86,9 @@
    *  compressionLevel (constants are defined in
    *  java.util.zip.Deflater). */
   public static byte[] compressString(String value, int compressionLevel) {
-    UnicodeUtil.UTF8Result result = new UnicodeUtil.UTF8Result();
+    BytesRef result = new BytesRef(10);
     UnicodeUtil.UTF16toUTF8(value, 0, value.length(), result);
-    return compress(result.result, 0, result.length, compressionLevel);
+    return compress(result.bytes, 0, result.length, compressionLevel);
   }
 
   /** Decompress the byte array previously returned by
