Restore fast skips.

    document            api      ns linear runtime
      TWEETS    GSON_STREAM  397568 =========
      TWEETS      GSON_SKIP  300058 =======
READER_SHORT    GSON_STREAM   76632 =
READER_SHORT      GSON_SKIP   57796 =
 READER_LONG    GSON_STREAM  894690 =====================
 READER_LONG      GSON_SKIP  565114 =============
diff --git a/gson/src/main/java/com/google/gson/stream/JsonReader.java b/gson/src/main/java/com/google/gson/stream/JsonReader.java
index c7be577..9649230 100644
--- a/gson/src/main/java/com/google/gson/stream/JsonReader.java
+++ b/gson/src/main/java/com/google/gson/stream/JsonReader.java
@@ -202,6 +202,7 @@
   private static final int PEEKED_SINGLE_QUOTED = 8;
   private static final int PEEKED_DOUBLE_QUOTED = 9;
   private static final int PEEKED_UNQUOTED = 10;
+  /** When this is returned, the string value is stored in peekedString. */
   private static final int PEEKED_BUFFERED = 11;
   private static final int PEEKED_SINGLE_QUOTED_NAME = 12;
   private static final int PEEKED_DOUBLE_QUOTED_NAME = 13;
@@ -242,10 +243,16 @@
   private long peekedInteger;
 
   /**
-   * The number of characters in the peeked number.
+   * The number of characters in a peeked number literal. Increment 'pos' by
+   * this after reading a number.
    */
   private int peekedNumberLength;
 
+  /**
+   * A peeked string that should be parsed on the next double, long or string.
+   * This is populated before a numeric value is parsed and used if that parsing
+   * fails.
+   */
   private String peekedString;
 
   /*
@@ -945,9 +952,7 @@
 
         if (c == quote) {
           pos = p;
-          if (false /* TODO: fast skipping */) {
-            return "skipped!";
-          } else if (builder == null) {
+          if (builder == null) {
             return new String(buffer, start, p - start - 1);
           } else {
             builder.append(buffer, start, p - start - 1);
@@ -1033,9 +1038,7 @@
     }
 
     String result;
-    if (false /* TODO: fast skipping */) {
-      result = "skipped!";
-    } else if (builder == null) {
+    if (builder == null) {
       result = new String(buffer, pos, i);
     } else {
       builder.append(buffer, pos, i);
@@ -1045,6 +1048,60 @@
     return result;
   }
 
+  private void skipQuotedValue(char quote) throws IOException {
+    // Like nextNonWhitespace, this uses locals 'p' and 'l' to save inner-loop field access.
+    char[] buffer = this.buffer;
+    do {
+      int p = pos;
+      int l = limit;
+      /* the index of the first character not yet appended to the builder. */
+      while (p < l) {
+        int c = buffer[p++];
+        if (c == quote) {
+          pos = p;
+          return;
+        } else if (c == '\\') {
+          pos = p;
+          readEscapeCharacter();
+          p = pos;
+          l = limit;
+        }
+      }
+      pos = p;
+    } while (fillBuffer(1));
+    throw syntaxError("Unterminated string");
+  }
+
+  private void skipUnquotedValue() throws IOException {
+    do {
+      int i = 0;
+      for (; pos + i < limit; i++) {
+        switch (buffer[pos + i]) {
+        case '/':
+        case '\\':
+        case ';':
+        case '#':
+        case '=':
+          checkLenient(); // fall-through
+        case '{':
+        case '}':
+        case '[':
+        case ']':
+        case ':':
+        case ',':
+        case ' ':
+        case '\t':
+        case '\f':
+        case '\r':
+        case '\n':
+          pos += i;
+          return;
+        }
+      }
+      pos += i;
+    } while (fillBuffer(1));
+  }
+
   /**
    * Returns the {@link com.google.gson.stream.JsonToken#NUMBER int} value of the next token,
    * consuming it. If the next token is a string, this method will attempt to
@@ -1133,11 +1190,11 @@
         stackSize--;
         count--;
       } else if (p == PEEKED_UNQUOTED_NAME || p == PEEKED_UNQUOTED) {
-        nextUnquotedValue();
+        skipUnquotedValue();
       } else if (p == PEEKED_SINGLE_QUOTED || p == PEEKED_SINGLE_QUOTED_NAME) {
-        nextQuotedValue('\'');
+        skipQuotedValue('\'');
       } else if (p == PEEKED_DOUBLE_QUOTED || p == PEEKED_DOUBLE_QUOTED_NAME) {
-        nextQuotedValue('"');
+        skipQuotedValue('"');
       } else if (p == PEEKED_NUMBER) {
         pos += peekedNumberLength;
       }
diff --git a/gson/src/test/java/com/google/gson/stream/JsonReaderTest.java b/gson/src/test/java/com/google/gson/stream/JsonReaderTest.java
index f8c7774..cddec2b 100644
--- a/gson/src/test/java/com/google/gson/stream/JsonReaderTest.java
+++ b/gson/src/test/java/com/google/gson/stream/JsonReaderTest.java
@@ -1172,6 +1172,24 @@
         "[\n\n" + spaces + "\n\n\n0,}]");
   }
 
+  public void disabled_testVeryLongNumber() throws IOException {
+    // TODO: this is a completely broken case that needs to be fixed!
+    JsonReader reader = new JsonReader(new StringReader("[0." + repeat('9', 8192) + "]"));
+    reader.beginArray();
+    assertEquals(1d, reader.nextDouble());
+    reader.endArray();
+    assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+  }
+
+  public void testVeryLongUnquotedLiteral() throws IOException {
+    String literal = "a" + repeat('b', 8192) + "c";
+    JsonReader reader = new JsonReader(new StringReader("[" + literal + "]"));
+    reader.setLenient(true);
+    reader.beginArray();
+    assertEquals(literal, reader.nextString());
+    reader.endArray();
+  }
+
   public void testFailWithPositionIsOffsetByBom() throws IOException {
     testFailWithPosition("Expected value at line 1 column 4",
         "\ufeff[0,}]");
@@ -1306,17 +1324,34 @@
   }
 
   public void testSkipVeryLongUnquotedString() throws IOException {
-    char[] stringChars = new char[1024 * 16];
-    Arrays.fill(stringChars, 'x');
-    String string = new String(stringChars);
-    String json = "[" + string + "]";
-    JsonReader reader = new JsonReader(new StringReader(json));
+    JsonReader reader = new JsonReader(new StringReader("[" + repeat('x', 8192) + "]"));
     reader.setLenient(true);
     reader.beginArray();
     reader.skipValue();
     reader.endArray();
   }
 
+  public void testSkipTopLevelUnquotedString() throws IOException {
+    JsonReader reader = new JsonReader(new StringReader(repeat('x', 8192)));
+    reader.setLenient(true);
+    reader.skipValue();
+    assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+  }
+
+  public void testSkipVeryLongQuotedString() throws IOException {
+    JsonReader reader = new JsonReader(new StringReader("[\"" + repeat('x', 8192) + "\"]"));
+    reader.beginArray();
+    reader.skipValue();
+    reader.endArray();
+  }
+
+  public void testSkipTopLevelQuotedString() throws IOException {
+    JsonReader reader = new JsonReader(new StringReader("\"" + repeat('x', 8192) + "\""));
+    reader.setLenient(true);
+    reader.skipValue();
+    assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+  }
+
   public void testStringAsNumberWithTruncatedExponent() throws IOException {
     JsonReader reader = new JsonReader(new StringReader("[123e]"));
     reader.setLenient(true);
diff --git a/metrics/src/main/java/com/google/gson/metrics/ParseBenchmark.java b/metrics/src/main/java/com/google/gson/metrics/ParseBenchmark.java
index 59b9e0f..3007706 100644
--- a/metrics/src/main/java/com/google/gson/metrics/ParseBenchmark.java
+++ b/metrics/src/main/java/com/google/gson/metrics/ParseBenchmark.java
@@ -81,6 +81,11 @@
         return new GsonStreamParser();
       }
     },
+    GSON_SKIP {
+      @Override Parser newParser() {
+        return new GsonSkipParser();
+      }
+    },
     GSON_DOM {
       @Override Parser newParser() {
         return new GsonDomParser();
@@ -184,6 +189,15 @@
     }
   }
 
+  private static class GsonSkipParser implements Parser {
+    public void parse(char[] data, Document document) throws Exception {
+      com.google.gson.stream.JsonReader jsonReader
+          = new com.google.gson.stream.JsonReader(new CharArrayReader(data));
+      jsonReader.skipValue();
+      jsonReader.close();
+    }
+  }
+
   private static class JacksonStreamParser implements Parser {
     public void parse(char[] data, Document document) throws Exception {
       JsonFactory jsonFactory = new JsonFactory();