Gson全解析之二:JsonReader的其它方法

上篇文章的示例代码

String json = "{\"name\":\"王成wisely\",\"age\":\"24\"}";
private void readJson2User(){
    User user = new User();
    StringReader reader = new StringReader(json);
    JsonReader jsonReader = new JsonReader(reader);
    try {
        jsonReader.beginObject();
        while (jsonReader.hasNext()){
            String name = jsonReader.nextName();
            switch (name){
                case "name":
                    user.name = jsonReader.nextString();
                    break;
                case "age":
                    user.age = jsonReader.nextInt();
                    break;
            }

        }
        jsonReader.endObject();
    } catch (IOException e) {
        e.printStackTrace();
    }

}

我们已经说完了beginObject()方法的源码,下面接着按示例代码的调用顺序来分析一下JsonReader的其它方法。

hasNext()


public boolean hasNext() throws IOException {
  int p = peeked;
  if (p == PEEKED_NONE) {
    p = doPeek();
  }
  return p != PEEKED_END_OBJECT && p != PEEKED_END_ARRAY;
}

在上一篇的最后,peeked被赋值为PEEKED_NONE,程序进入到doPeek()方法。

private int doPeek() throws IOException {
    int peekStack = stack[stackSize - 1];
    if (peekStack == JsonScope.EMPTY_ARRAY) {
      stack[stackSize - 1] = JsonScope.NONEMPTY_ARRAY;
    } else if (peekStack == JsonScope.NONEMPTY_ARRAY) {
      ...
    } else if (peekStack == JsonScope.EMPTY_OBJECT || peekStack == JsonScope.NONEMPTY_OBJECT) {
      stack[stackSize - 1] = JsonScope.DANGLING_NAME;
      // Look for a comma before the next element.
      if (peekStack == JsonScope.NONEMPTY_OBJECT) {
        ...
      }
      int c = nextNonWhitespace(true);
      switch (c) {
      case '"':
        return peeked = PEEKED_DOUBLE_QUOTED_NAME;
      case '\'':
        ...
      case '}':
        ...
      default:
        ...
      }
    } else if (peekStack == JsonScope.DANGLING_NAME) {
      ...
    } else if (peekStack == JsonScope.EMPTY_DOCUMENT) {
      ...
    } else if (peekStack == JsonScope.NONEMPTY_DOCUMENT) {
      ...
    } else if (peekStack == JsonScope.CLOSED) {
      ...
    }

    int c = nextNonWhitespace(true);
    switch (c) {
    case ']':
      ...
    case ';':
    case ',':
      ...
    case '\'':
      ...
    case '"':
      ...
    case '[':
      return peeked = PEEKED_BEGIN_ARRAY;
    case '{':
      return peeked = PEEKED_BEGIN_OBJECT;
    default:
      pos--; // Don't consume the first character in a literal value.
    }
    ...
    return peeked = PEEKED_UNQUOTED;
  }

第2行,stackSize=2,peekStack=stack[1]=JsonScope.EMPTY_OBJECT。
第8行,stack[1]=JsonScope.DANGLING_NAME;

stack数组value
stack[0]JsonScope.EMPTY_DOCUMENT JsonScope.NONEMPTY_DOCUMENT
stack[1]JsonScope.EMPTY_OBJECT
stack[1]JsonScope.DANGLING_NAME

第13行

int c = nextNonWhitespace(true);
private int nextNonWhitespace(boolean throwOnEof) throws IOException {
    char[] buffer = this.buffer;
    int p = pos;
    int l = limit;
    while (true) {
      if (p == l) {
        ...
      }

      int c = buffer[p++];
      if (c == '\n') {
        lineNumber++;
        lineStart = p;
        continue;
      } else if (c == ' ' || c == '\r' || c == '\t') {
        continue;
      }

      if (c == '/') {
        ...
      } else if (c == '#') {
        ...
      } else {
        pos = p;
        return c;
      }
    }
    ...
  }

直接看第10行

int c = buffer[p++];

int c = buffer[1]=34,在ANSCII码中34就是双引号,p=2。

01
{

第24行,pos=p=2,将c作为返回值返回到doPeek()方法中。
doPeek()中运行到第16行

peeked = PEEKED_DOUBLE_QUOTED_NAME;
peeked的值
PEEKED_NONE
PEEKED_BEGIN_OBJECT
PEEKED_NONE
PEEKED_DOUBLE_QUOTED_NAME

程序回到hasNext()方法中

return p != PEEKED_END_OBJECT && p != PEEKED_END_ARRAY

由于p=PEEKED_DOUBLE_QUOTED_NAME,所以hasNext()方法返回true。

nextName()


public String nextName() throws IOException {
    int p = peeked;
    if (p == PEEKED_NONE) {
      p = doPeek();
    }
    String result;
    if (p == PEEKED_UNQUOTED_NAME) {
      result = nextUnquotedValue();
    } else if (p == PEEKED_SINGLE_QUOTED_NAME) {
      result = nextQuotedValue('\'');
    } else if (p == PEEKED_DOUBLE_QUOTED_NAME) {
      result = nextQuotedValue('"');
    } else {
      throw new IllegalStateException("Expected a name but was " + peek()
          + " at line " + getLineNumber() + " column " + getColumnNumber());
    }
    peeked = PEEKED_NONE;
    return result;
  }

根据hasNext()方法可知,p=PEEKED_DOUBLE_QUOTED_NAME,程序运行到第12行,

nextQuotedValue('"');
private String nextQuotedValue(char quote) throws IOException {
    // Like nextNonWhitespace, this uses locals 'p' and 'l' to save inner-loop field access.
    char[] buffer = this.buffer;
    StringBuilder builder = new StringBuilder();
    while (true) {
      int p = pos;
      int l = limit;
      /* the index of the first character not yet appended to the builder. */
      int start = p;
      while (p < l) {
        int c = buffer[p++];

        if (c == quote) {
          pos = p;
          builder.append(buffer, start, p - start - 1);
          return builder.toString();
        } else if (c == '\\') {
          ...
        } else if (c == '\n') {
          ...
        }
      }
      ...
    }
  }

第9行,start=p=2,之后进入while循环

int c = buffer[p++];

当p=6时,c=buffer[6]=34,也就是双引号,同时,p增加1,p=7

builder.append(buffer, start, p - start - 1);

上面是第15行,p-start-1=7-2-1=4。

01234567
{name

获取到的字符串就是2-6之间的字符拼接而成,也就是name。将name作为返回值返回到nextName()中,程序运行到第17行

peeked = PEEKED_NONE;
peeked的值
PEEKED_NONE
PEEKED_BEGIN_OBJECT
PEEKED_NONE
PEEKED_DOUBLE_QUOTED_NAME
PEEKED_NONE

最终,nextName()将字符串name作为返回值返回。

nextString()


public String nextString() throws IOException {
    int p = peeked;
    if (p == PEEKED_NONE) {
      p = doPeek();
    }
    String result;
    if (p == PEEKED_UNQUOTED) {
      result = nextUnquotedValue();
    } else if (p == PEEKED_SINGLE_QUOTED) {
      result = nextQuotedValue('\'');
    } else if (p == PEEKED_DOUBLE_QUOTED) {
      result = nextQuotedValue('"');
    } else if (p == PEEKED_BUFFERED) {
      result = peekedString;
      peekedString = null;
    } else if (p == PEEKED_LONG) {
      result = Long.toString(peekedLong);
    } else if (p == PEEKED_NUMBER) {
      result = new String(buffer, pos, peekedNumberLength);
      pos += peekedNumberLength;
    } else {
      throw new IllegalStateException("Expected a string but was " + peek()
          + " at line " + getLineNumber() + " column " + getColumnNumber());
    }
    peeked = PEEKED_NONE;
    return result;
  }

第3行代码,p=PEEKED_NONE,程序运行第4行,代码再次进入doPeek()。

private int doPeek() throws IOException {
    int peekStack = stack[stackSize - 1];
    if (peekStack == JsonScope.EMPTY_ARRAY) {
      stack[stackSize - 1] = JsonScope.NONEMPTY_ARRAY;
    } else if (peekStack == JsonScope.NONEMPTY_ARRAY) {
      ...
    } else if (peekStack == JsonScope.EMPTY_OBJECT || peekStack == JsonScope.NONEMPTY_OBJECT) {
      ...
    } else if (peekStack == JsonScope.DANGLING_NAME) {
      stack[stackSize - 1] = JsonScope.NONEMPTY_OBJECT;
      // Look for a colon before the value.
      int c = nextNonWhitespace(true);
      switch (c) {
      case ':':
        break;
      case '=':
        checkLenient();
        if ((pos < limit || fillBuffer(1)) && buffer[pos] == '>') {
          pos++;
        }
        break;
      default:
        throw syntaxError("Expected ':'");
      }
    } else if (peekStack == JsonScope.EMPTY_DOCUMENT) {
      ...
    } else if (peekStack == JsonScope.NONEMPTY_DOCUMENT) {
      ...
    } else if (peekStack == JsonScope.CLOSED) {
      throw new IllegalStateException("JsonReader is closed");
    }

    int c = nextNonWhitespace(true);
    switch (c) {
    case ']':
      if (peekStack == JsonScope.EMPTY_ARRAY) {
        return peeked = PEEKED_END_ARRAY;
      }
      // fall-through to handle ",]"
    case ';':
    case ',':
      ...
    case '\'':
      ...
    case '"':
      if (stackSize == 1) {
        checkLenient();
      }
      return peeked = PEEKED_DOUBLE_QUOTED;
    case '[':
      return peeked = PEEKED_BEGIN_ARRAY;
    case '{':
      return peeked = PEEKED_BEGIN_OBJECT;
    default:
      pos--; // Don't consume the first character in a literal value.
    }
    ...
  }

stackSize = 2,peekStack=stack[1]=JsonScope.DANGLING_NAME。
程序运行到第10行,stack[1]=JsonScope.NONEMPTY_OBJECT

stack数组value
stack[0]JsonScope.EMPTY_DOCUMENT JsonScope.NONEMPTY_DOCUMENT
stack[1]JsonScope.EMPTY_OBJECT
stack[1]JsonScope.DANGLING_NAME
stack[1]JsonScope.NONEMPTY_OBJECT
int c = nextNonWhitespace(true);
private int nextNonWhitespace(boolean throwOnEof) throws IOException {
    char[] buffer = this.buffer;
    int p = pos;
    int l = limit;
    while (true) {
      if (p == l) {
        ...
      }

      int c = buffer[p++];
      if (c == '\n') {
        lineNumber++;
        lineStart = p;
        continue;
      } else if (c == ' ' || c == '\r' || c == '\t') {
        continue;
      }

      if (c == '/') {
        ...
      } else if (c == '#') {
        ...
      } else {
        pos = p;
        return c;
      }
    }
    ...
  }

p=pos=7,l=limit=30,程序运行到第10行,c=buffer[7]=58,在ASCII码中是冒号:同时,p加1,p=8
程序运行到第24行,pos=p=8,将c作为返回值返回到doPeek()方法中。

012345678
{name:

doPeek()方法中,运行到第12行,c=58,也就是冒号,程序运行第14行,15行,跳出switch语句。程序运行到第33行,再次运行nextNonWhitespace()方法。

private int nextNonWhitespace(boolean throwOnEof) throws IOException {
    char[] buffer = this.buffer;
    int p = pos;
    int l = limit;
    while (true) {
      if (p == l) {
        ...
      }

      int c = buffer[p++];
      if (c == '\n') {
        lineNumber++;
        lineStart = p;
        continue;
      } else if (c == ' ' || c == '\r' || c == '\t') {
        continue;
      }

      if (c == '/') {
        ...
      } else if (c == '#') {
        ...
      } else {
        pos = p;
        return c;
      }
    }
    ...
  }

第10行

int c = buffer[p++];

p=8,c=buffer[8]=34,也就是双引号,p加1,p=9

0123456789
{name:

方法再次回到doPeek()方法,第33行

int c = nextNonWhitespace(true);

第45~49行,

case '"':
  if (stackSize == 1) {
    checkLenient();
  }
  return peeked = PEEKED_DOUBLE_QUOTED;

stackSize=2,将PEEKED_DOUBLE_QUOTED赋值给peeked,方法将peeked返回。

peeked的值
PEEKED_NONE
PEEKED_BEGIN_OBJECT
PEEKED_NONE
PEEKED_DOUBLE_QUOTED_NAME
PEEKED_NONE
PEEKED_DOUBLE_QUOTED

方法回到nextString()方法的第4行,继续往下运行,执行到第12行

result = nextQuotedValue('"');
 private String nextQuotedValue(char quote) throws IOException {
    // Like nextNonWhitespace, this uses locals 'p' and 'l' to save inner-loop field access.
    char[] buffer = this.buffer;
    StringBuilder builder = new StringBuilder();
    while (true) {
      int p = pos;
      int l = limit;
      /* the index of the first character not yet appended to the builder. */
      int start = p;
      while (p < l) {
        int c = buffer[p++];

        if (c == quote) {
          pos = p;
          builder.append(buffer, start, p - start - 1);
          return builder.toString();
        } else if (c == '\\') {
          pos = p;
          builder.append(buffer, start, p - start - 1);
          builder.append(readEscapeCharacter());
          p = pos;
          l = limit;
          start = p;
        } else if (c == '\n') {
          lineNumber++;
          lineStart = p;
        }
      }

      builder.append(buffer, start, p - start);
      pos = p;
      if (!fillBuffer(1)) {
        throw syntaxError("Unterminated string");
      }
    }
  }

第9行,start=p=9,进入while循环,第11行

 int c = buffer[p++];
 builder.append(buffer, start, p - start - 1);

当p=17时,c=buffer[17]=34,也就是双引号,p加1,p=18
p-start-1=18-9-1=8

0123456789101112131415161718
{name:wisely

将“王成wisely”这8个字符作为返回值返回到nextString()方法中的第12行,

result = nextQuotedValue('"');

result = “王成wisely”
之后,程序运行到第25行

peeked = PEEKED_NONE;
peeked的值
PEEKED_NONE
PEEKED_BEGIN_OBJECT
PEEKED_NONE
PEEKED_DOUBLE_QUOTED_NAME
PEEKED_NONE
PEEKED_DOUBLE_QUOTED
PEEKED_NONE

hasNext()

程序再次执行hasNext()方法
调用过程hasNext()–>doPeek(),如下

private int doPeek() throws IOException {
    int peekStack = stack[stackSize - 1];
    if (peekStack == JsonScope.EMPTY_ARRAY) {
      stack[stackSize - 1] = JsonScope.NONEMPTY_ARRAY;
    } else if (peekStack == JsonScope.NONEMPTY_ARRAY) {
      ...
    } else if (peekStack == JsonScope.EMPTY_OBJECT || peekStack == JsonScope.NONEMPTY_OBJECT) {
      stack[stackSize - 1] = JsonScope.DANGLING_NAME;
      // Look for a comma before the next element.
      if (peekStack == JsonScope.NONEMPTY_OBJECT) {
        int c = nextNonWhitespace(true);
        switch (c) {
        case '}':
          return peeked = PEEKED_END_OBJECT;
        case ';':
          checkLenient(); // fall-through
        case ',':
          break;
        default:
          throw syntaxError("Unterminated object");
        }
      }
      int c = nextNonWhitespace(true);
      switch (c) {
      case '"':
        return peeked = PEEKED_DOUBLE_QUOTED_NAME;
      ...
    } else if (peekStack == JsonScope.DANGLING_NAME) {
      ...
    } else if (peekStack == JsonScope.EMPTY_DOCUMENT) {
      ...
    } else if (peekStack == JsonScope.NONEMPTY_DOCUMENT) {
      ...
    } else if (peekStack == JsonScope.CLOSED) {
      ...
    }
     ...

  }

第11行,

int c = nextNonWhitespace(true);

c=44,也就是逗号。同时p=19

012345678910111213141516171819
{name:wisely,

程序继续执行,运行到第23行

int c = nextNonWhitespace(true);

c=34,也就是双引号,同时p=20

01234567891011121314151617181920
{name:wisely,

整个doPeek()方法运行完毕前,peeked被重新赋值为PEEKED_DOUBLE_QUOTED_NAME。

peeked的值
PEEKED_NONE
PEEKED_BEGIN_OBJECT
PEEKED_NONE
PEEKED_DOUBLE_QUOTED_NAME
PEEKED_NONE
PEEKED_DOUBLE_QUOTED
PEEKED_NONE
PEEKED_DOUBLE_QUOTED_NAME

程序执行回hasNext()方法,返回true。

nextName()


第二次运行,调用过程依旧,nextName()–>nextQuotedValue(‘”’)

private String nextQuotedValue(char quote) throws IOException {
    // Like nextNonWhitespace, this uses locals 'p' and 'l' to save inner-loop field access.
    char[] buffer = this.buffer;
    StringBuilder builder = new StringBuilder();
    while (true) {
      int p = pos;
      int l = limit;
      /* the index of the first character not yet appended to the builder. */
      int start = p;
      while (p < l) {
        int c = buffer[p++];

        if (c == quote) {
          pos = p;
          builder.append(buffer, start, p - start - 1);
          return builder.toString();
        } else if (c == '\\') {
          pos = p;
          builder.append(buffer, start, p - start - 1);
          builder.append(readEscapeCharacter());
          p = pos;
          l = limit;
          start = p;
        } else if (c == '\n') {
          lineNumber++;
          lineStart = p;
        }
      }

      builder.append(buffer, start, p - start);
      pos = p;
      if (!fillBuffer(1)) {
        throw syntaxError("Unterminated string");
      }
    }
  }

start=20,当p=23时,int c = buffer[23]=34,也就是双引号,同时p加1,p=24。

builder.append(buffer, start, p - start - 1);

p-start-1=24-20-1=3。

01234567891011121314
{name:wise
15161718192021222324
ly,age

最终nextName()方法返回age3个字符,并且将peeked重置为PEEKED_NONE。

peeked的值
PEEKED_NONE
PEEKED_BEGIN_OBJECT
PEEKED_NONE
PEEKED_DOUBLE_QUOTED_NAME
PEEKED_NONE
PEEKED_DOUBLE_QUOTED
PEEKED_NONE
PEEKED_DOUBLE_QUOTED_NAME
PEEKED_NONE

nextInt()


public int nextInt() throws IOException {
    int p = peeked;
    if (p == PEEKED_NONE) {
      p = doPeek();
    }

    int result;
    if (p == PEEKED_LONG) {
      ...
    }

    if (p == PEEKED_NUMBER) {
      ...
    } else if (p == PEEKED_SINGLE_QUOTED || p == PEEKED_DOUBLE_QUOTED) {
      peekedString = nextQuotedValue(p == PEEKED_SINGLE_QUOTED ? '\'' : '"');
      try {
        result = Integer.parseInt(peekedString);
        peeked = PEEKED_NONE;
        return result;
      } catch (NumberFormatException ignored) {
        // Fall back to parse as a double below.
      }
    } else {
      throw new IllegalStateException("Expected an int but was " + peek()
          + " at line " + getLineNumber() + " column " + getColumnNumber());
    }

    peeked = PEEKED_BUFFERED;
    double asDouble = Double.parseDouble(peekedString); // don't catch this NumberFormatException.
    result = (int) asDouble;
    if (result != asDouble) { // Make sure no precision was lost casting to 'int'.
      throw new NumberFormatException("Expected an int but was " + peekedString
          + " at line " + getLineNumber() + " column " + getColumnNumber());
    }
    peekedString = null;
    peeked = PEEKED_NONE;
    return result;
  }

方法进入调用了doPeek()方法

private int doPeek() throws IOException {
    int peekStack = stack[stackSize - 1];
    if (peekStack == JsonScope.EMPTY_ARRAY) {
      stack[stackSize - 1] = JsonScope.NONEMPTY_ARRAY;
    } else if (peekStack == JsonScope.NONEMPTY_ARRAY) {
      ...
      }
    } else if (peekStack == JsonScope.EMPTY_OBJECT || peekStack == JsonScope.NONEMPTY_OBJECT) {
      ...
    } else if (peekStack == JsonScope.DANGLING_NAME) {
      stack[stackSize - 1] = JsonScope.NONEMPTY_OBJECT;
      // Look for a colon before the value.
      int c = nextNonWhitespace(true);
      switch (c) {
      case ':':
        break;
      case '=':
        ...
      }
    } else if (peekStack == JsonScope.EMPTY_DOCUMENT) {
      ...
    } else if (peekStack == JsonScope.NONEMPTY_DOCUMENT) {
      ...
    } else if (peekStack == JsonScope.CLOSED) {
      ...
    }

    int c = nextNonWhitespace(true);
    switch (c) {
    case ']':
      ...
    case ';':
    case ',':
      ...
    case '\'':
      ...
    case '"':
      if (stackSize == 1) {
        checkLenient();
      }
      return peeked = PEEKED_DOUBLE_QUOTED;
    case '[':
      return peeked = PEEKED_BEGIN_ARRAY;
    case '{':
      return peeked = PEEKED_BEGIN_OBJECT;
    default:
      pos--; // Don't consume the first character in a literal value.
    }
    ...
  }

peekStack=stack[1]=JsonScope.JsonScope.DANGLING_NAME。
代码运行到第11行,为stack[1]赋值为JsonScope.NONEMPTY_OBJECT。

stack数组value
stack[0]JsonScope.EMPTY_DOCUMENT JsonScope.NONEMPTY_DOCUMENT
stack[1]JsonScope.EMPTY_OBJECT
stack[1]JsonScope.DANGLING_NAME
stack[1]JsonScope.NONEMPTY_OBJECT
stack[1]JsonScope.DANGLING_NAME
stack[1]JsonScope.NONEMPTY_OBJECT

程序运行到第13行,

int c = nextNonWhitespace(true);

获取到的c=58,也就是冒号:之后跳出switch语句再次运行了一个同样的语句,获得了一个c,c=34,也就是双引号。
然后为peeked重新赋值为PEEKED_DOUBLE_QUOTED,退出doPeek()方法

peeked的值
PEEKED_NONE
PEEKED_BEGIN_OBJECT
PEEKED_NONE
PEEKED_DOUBLE_QUOTED_NAME
PEEKED_NONE
PEEKED_DOUBLE_QUOTED
PEEKED_NONE
PEEKED_DOUBLE_QUOTED_NAME
PEEKED_NONE
PEEKED_DOUBLE_QUOTED

代码回到nextInt中,继续运行第15行,最终又调用了nextQuotedValue(“)方法。

peekedString = nextQuotedValue(p == PEEKED_SINGLE_QUOTED ? '\'' : '"');

在nextQuotedValue()方法运行时,start=26,p=28时,c=34,也就是双引号,之后p加1,p=29,最终p-start-1=2,也就是24。

01234567891011121314
{name:wise
151617181920212223242526272829
ly,age:24

之后为peeked赋值为PEEKED_NONE,

peeked的值
PEEKED_NONE
PEEKED_BEGIN_OBJECT
PEEKED_NONE
PEEKED_DOUBLE_QUOTED_NAME
PEEKED_NONE
PEEKED_DOUBLE_QUOTED
PEEKED_NONE
PEEKED_DOUBLE_QUOTED_NAME
PEEKED_NONE
PEEKED_DOUBLE_QUOTED
PEEKED_NONE

hasNext()


最后一次调用hasNext()方法。
hasNext()–>doPeek()–>nextNonWhitespace()
通过nextNonWhitespace方法获取到了一个右大括号,在doPeek()方法中重置peeked值为peeked = PEEKED_END_OBJECT。之后代码再返回到hasNext()中,返回false。

01234567891011121314
{name:wise
151617181920212223242526272829
ly,age:24}
peeked的值
PEEKED_NONE
PEEKED_BEGIN_OBJECT
PEEKED_NONE
PEEKED_DOUBLE_QUOTED_NAME
PEEKED_NONE
PEEKED_DOUBLE_QUOTED
PEEKED_NONE
PEEKED_DOUBLE_QUOTED_NAME
PEEKED_NONE
PEEKED_DOUBLE_QUOTED
PEEKED_NONE
PEEKED_END_OBJECT

endObject()

public void endObject() throws IOException {
    int p = peeked;
    if (p == PEEKED_NONE) {
      p = doPeek();
    }
    if (p == PEEKED_END_OBJECT) {
      stackSize--;
      peeked = PEEKED_NONE;
    } else {
      throw new IllegalStateException("Expected END_OBJECT but was " + peek()
          + " at line " + getLineNumber() + " column " + getColumnNumber());
    }
  }

第7行时,将stackSize-1,变为了1。最后将peeked重置为PEEKED_NONE。

peeked的值
PEEKED_NONE
PEEKED_BEGIN_OBJECT
PEEKED_NONE
PEEKED_DOUBLE_QUOTED_NAME
PEEKED_NONE
PEEKED_DOUBLE_QUOTED
PEEKED_NONE
PEEKED_DOUBLE_QUOTED_NAME
PEEKED_NONE
PEEKED_DOUBLE_QUOTED
PEEKED_NONE
PEEKED_END_OBJECT
PEEKED_NONE

版权声明:本文为u013673799原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。