Gson全解析之一:JsonReader的beginObject()

Posted 王梵

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Gson全解析之一:JsonReader的beginObject()相关的知识,希望对你有一定的参考价值。

如果按重要性,对Gson中的类排个序,JsonReader绝对排在第一,它是底层真正解析json格式数据的支持类,非常值得一看。
我们来看一个简单的解析。

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();
    


一、构造方法


我们来看JsonReader的构造方法,如下:

private int[] stack = new int[32];
private int stackSize = 0;

  stack[stackSize++] = JsonScope.EMPTY_DOCUMENT;

public JsonReader(Reader in) 
  if (in == null) 
    throw new NullPointerException("in == null");
  
  this.in = in;

JsonReader的构造方法,需要一个Reader的实例,它的作用是用来读取json格式的数据流。上面的代码中,除了构造方法外,还有一个静态代码块,初始化了一个名为stack的数组的第0项,后面会用到。

二、beginObject()


int peeked = PEEKED_NONE;
public void beginObject() throws IOException 
    int p = peeked;
    if (p == PEEKED_NONE) 
      p = doPeek();
    
    if (p == PEEKED_BEGIN_OBJECT) 
      push(JsonScope.EMPTY_OBJECT);
      peeked = PEEKED_NONE;
     else 
      throw new IllegalStateException("Expected BEGIN_OBJECT but was " + peek() + locationString());
    
  

peeked是个很重要的变量,默认值为PEEKED_NONE。在上面的方法中,调用了一个方法doPeek(),这个类可以说是JsonReader类的核心方法之一。

三、doPeek()


int doPeek() throws IOException 
    int peekStack = stack[stackSize - 1];
    if (peekStack == JsonScope.EMPTY_ARRAY) 
      ......
     else if (peekStack == JsonScope.NONEMPTY_ARRAY) 
      ......
     else if (peekStack == JsonScope.EMPTY_OBJECT || peekStack == JsonScope.NONEMPTY_OBJECT) 
      ......
     else if (peekStack == JsonScope.DANGLING_NAME) 
     ......
     else if (peekStack == JsonScope.EMPTY_DOCUMENT) 
      if (lenient) 
        consumeNonExecutePrefix();
      
      stack[stackSize - 1] = JsonScope.NONEMPTY_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 ',':
      // In lenient mode, a 0-length literal in an array means 'null'.
      if (peekStack == JsonScope.EMPTY_ARRAY || peekStack == JsonScope.NONEMPTY_ARRAY) 
        checkLenient();
        pos--;
        return peeked = PEEKED_NULL;
       else 
        throw syntaxError("Unexpected value");
      
    case '\\'':
      checkLenient();
      return peeked = PEEKED_SINGLE_QUOTED;
    case '"':
      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.
    

    int result = peekKeyword();
    if (result != PEEKED_NONE) 
      return result;
    

    result = peekNumber();
    if (result != PEEKED_NONE) 
      return result;
    

    if (!isLiteral(buffer[pos])) 
      throw syntaxError("Expected value");
    

    checkLenient();
    return peeked = PEEKED_UNQUOTED;
  

第2行,初始化变量peekStack,stackSize = 1,peekStack=stack[0],也就是JsonScope.EMPTY_DOCUMENT,在静态代码块中进行的初始化 。
第15行,重新为stack[0]赋值,stack[0]=JsonScope.NONEMPTY_DOCUMENT。
下面是stack数组的值的变迁

stack数组value
stack[0]JsonScope.EMPTY_DOCUMENT JsonScope.NONEMPTY_DOCUMENT

第22行,调用了nextNonWhitespace()方法,如下:

四、nextNonWhitespace()


private final char[] buffer = new char[1024];
private int pos = 0;
private int limit = 0;
private int nextNonWhitespace(boolean throwOnEof) throws IOException 
    char[] buffer = this.buffer;
    int p = pos;
    int l = limit;
    while (true) 
      if (p == l) 
        pos = p;
        if (!fillBuffer(1)) 
          break;
        
        p = pos;
        l = limit;
      

      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;
      
    
    if (throwOnEof) 
      throw new EOFException("End of input"
          + " at line " + getLineNumber() + " column " + getColumnNumber());
     else 
      return -1;
    
  

第5~7行,buffer是个空数组,p=pos=0,l=limit=0,代码运行到第11行,调用了一个方法fillBuffer()

五、fillBuffer()


private int lineNumber = 0;
private int lineStart = 0;
private boolean fillBuffer(int minimum) throws IOException 
    char[] buffer = this.buffer;
    lineStart -= pos;
    if (limit != pos) 
      limit -= pos;
      System.arraycopy(buffer, pos, buffer, 0, limit);
     else 
      limit = 0;
    

    pos = 0;
    int total;
    while ((total = in.read(buffer, limit, buffer.length - limit)) != -1) 
      limit += total;

      // if this is the first read, consume an optional byte order mark (BOM) if it exists
      if (lineNumber == 0 && lineStart == 0 && limit > 0 && buffer[0] == '\\ufeff') 
        pos++;
        lineStart++;
        minimum++;
      

      if (limit >= minimum) 
        return true;
      
    
    return false;
  

从方法名就可以看出,这个方法的主要作用是填充空的buffer数组。第5行,lineStart = 0。第6行的if判断,limit=pos=0,程序运行到第10行,limit=0。

第15行,将字符数组读入buffer数组中,total=30。第15行,limit=30。

01234567891011121314
name:wise
151617181920212223242526272829
ly,age:24

第19~23行,如下:

// if this is the first read, consume an optional byte order mark (BOM) if it exists
if (lineNumber == 0 && lineStart == 0 && limit > 0 && buffer[0] == '\\ufeff') 
  pos++;
  lineStart++;
  minimum++;

if条件中,前3个条件都满足,第4个条件判断buffer[0]是不是等于“\\ufeff”,其实就是判断字符串是不是UTF-8编码,并且有BOM。关于BOM的内容,在最下面。

不满足第4个条件,程序运行到第25行,limit=30>minimum=1。

fillBuffer()方法运行完,再回到nextNonWhitespace(),第14,15行,p=pos=0,l=limit=30。

第18行,这是一句重要代码。

int c = buffer[p++];

首先,将buffer[0]赋值给c,也就是,int c = buffer[0] = 123,左大括号在ANSCII中的值就是123。
然后,将p的值加1,现在p=1。

程序运行到第32行,pos=p=1,将c作为返回值返回。程序再次回到doPeek()方法中。

在doPeek()方法的第22行,

int c = nextNonWhitespace(true);

得到c=123,也就是左大括号。程序继续运行到第47行,为peeked赋值

peeked = PEEKED_BEGIN_OBJECT
peeked的值
PEEKED_NONE
PEEKED_BEGIN_OBJECT

代码再次回到beginObject(),程序运行到第7句,p == PEEKED_BEGIN_OBJECT,进入if语句,运行如下代码

push(JsonScope.EMPTY_OBJECT);
peeked = PEEKED_NONE;

先运行push方法

private void push(int newTop) 
    if (stackSize == stack.length) 
      int[] newStack = new int[stackSize * 2];
      System.arraycopy(stack, 0, newStack, 0, stackSize);
      stack = newStack;
    
    stack[stackSize++] = newTop;
  

stackSize=1,stack.length=32,程序运行到最后一句,stack[1]=JsonScope.EMPTY_OBJECT,之后stackSize=2。

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

再次回到beginObject()方法,peeked = PEEKED_NONE;

peeked的值
PEEKED_NONE
PEEKED_BEGIN_OBJECT
PEEKED_NONE

至此,beginObject()结束。已经读取的数组如下

0

pos=1。

附录1:BOM


使用UTF-8编码的字符串,默认在前面添加BOM(byte order mark),比如字符串aaa,如果它的ASCII编码为

61 61 61

而如果使用UTF-8编码,那么就变成了

EF BB BF 61 61 61

在java中,EF BB BF被当成‘\\ufeff’处理,在其它语言或编码中则各有不同,参见这里这里
BOM原意是Unicode编码中标记字节顺序的方法,现在大都用来查看是编码形式,比如前面如果是EF BB BF,那么就是UTF-8编码的。


以上是关于Gson全解析之一:JsonReader的beginObject()的主要内容,如果未能解决你的问题,请参考以下文章

JsonReader 无法正确解析字符串数组

Gson源码解析

Gson源码解析

Gson使用google的JsonReader读取Json文件并转化成对象

我如何查看从 GSON JsonReader 收到了多少字节

使用 GSON 的 JsonReader 流式传输 Json 文件时,您可以将对象转储为字符串吗?