经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » JS/JS库/框架 » JSON » 查看文章
使用JsonTextReader提高Json.NET反序列化的性能
来源:cnblogs  作者:无风听海  时间:2023/2/24 9:07:38  对本文有异议

一、碰到的问题
在服务器的文件系统上有一个业务生成的BigTable.json文件,其可能包含的JSON字符串很大,同时里边的集合会包含很多的记录;我们使用以下的代码来反序列化,虽然使用了异步的ReadAllTextAsync来读取文件,但是还是需要将整个的文件内容都读取到内存中,这样会极大的占用服务器内存,同时分配太多对象或分配非常大的对象会导致垃圾收集减慢甚至停止应用程序;

  1. string jsonStr = File.ReadAllTextAsync("json.txt").Result;
  2. BigTable table = JsonConvert.DeserializeObject<BigTable>(jsonStr);

二、JsonTextReader的简单用法

为了尽量减少内存使用和分配对象的数量,Json.NET提供了了解JsonTextReader,支持直接对流进行序列化和反序列化。每次读取或写入JSON片段,而不是将整个JSON字符串加载到内存中,这在处理大于85kb的JSON文档时尤其重要,以避免JSON字符串最终出现在大对象堆中.

  1. string json = @"{
  2. 'CPU': 'Intel',
  3. 'PSU': '500W',
  4. 'Drives': [
  5. 'DVD read/writer'
  6. /*(broken)*/,
  7. '500 gigabyte hard drive',
  8. '200 gigabyte hard drive'
  9. ]
  10. }";
  11. JsonTextReader reader = new JsonTextReader(new StringReader(json));
  12. while (reader.Read())
  13. {
  14. if (reader.Value != null)
  15. {
  16. Console.WriteLine("Token: {0}, Value: {1}", reader.TokenType, reader.Value);
  17. }
  18. else
  19. {
  20. Console.WriteLine("Token: {0}", reader.TokenType);
  21. }
  22. }
  23. Token: StartObject
  24. Token: PropertyName, Value: CPU
  25. Token: String, Value: Intel
  26. Token: PropertyName, Value: PSU
  27. Token: String, Value: 500W
  28. Token: PropertyName, Value: Drives
  29. Token: StartArray
  30. Token: String, Value: DVD read/writer
  31. Token: Comment, Value: (broken)
  32. Token: String, Value: 500 gigabyte hard drive
  33. Token: String, Value: 200 gigabyte hard drive
  34. Token: EndArray
  35. Token: EndObject

三、简单了解JsonTextReader的处理过程

JsonTextReader内部会通过State枚举记录其现在读取JSON字符串所在的位;

  1. /// <summary>
  2. /// Specifies the state of the reader.
  3. /// </summary>
  4. protected internal enum State
  5. {
  6. /// <summary>
  7. /// A <see cref="JsonReader"/> read method has not been called.
  8. /// </summary>
  9. Start,
  10. /// <summary>
  11. /// The end of the file has been reached successfully.
  12. /// </summary>
  13. Complete,
  14. /// <summary>
  15. /// Reader is at a property.
  16. /// </summary>
  17. Property,
  18. /// <summary>
  19. /// Reader is at the start of an object.
  20. /// </summary>
  21. ObjectStart,
  22. /// <summary>
  23. /// Reader is in an object.
  24. /// </summary>
  25. Object,
  26. /// <summary>
  27. /// Reader is at the start of an array.
  28. /// </summary>
  29. ArrayStart,
  30. /// <summary>
  31. /// Reader is in an array.
  32. /// </summary>
  33. Array,
  34. /// <summary>
  35. /// The <see cref="JsonReader.Close()"/> method has been called.
  36. /// </summary>
  37. Closed,
  38. /// <summary>
  39. /// Reader has just read a value.
  40. /// </summary>
  41. PostValue,
  42. /// <summary>
  43. /// Reader is at the start of a constructor.
  44. /// </summary>
  45. ConstructorStart,
  46. /// <summary>
  47. /// Reader is in a constructor.
  48. /// </summary>
  49. Constructor,
  50. /// <summary>
  51. /// An error occurred that prevents the read operation from continuing.
  52. /// </summary>
  53. Error,
  54. /// <summary>
  55. /// The end of the file has been reached successfully.
  56. /// </summary>
  57. Finished
  58. }

JsonTextReader在Read方法内部会根据当前_currentState来决定下一步处理的逻辑;在开始的时候_currentState=State.Start,所以会调用ParseValue方法;

  1. public override bool Read()
  2. {
  3. EnsureBuffer();
  4. MiscellaneousUtils.Assert(_chars != null);
  5. while (true)
  6. {
  7. switch (_currentState)
  8. {
  9. case State.Start:
  10. case State.Property:
  11. case State.Array:
  12. case State.ArrayStart:
  13. case State.Constructor:
  14. case State.ConstructorStart:
  15. return ParseValue();
  16. case State.Object:
  17. case State.ObjectStart:
  18. return ParseObject();
  19. case State.PostValue:
  20. // returns true if it hits
  21. // end of object or array
  22. if (ParsePostValue(false))
  23. {
  24. return true;
  25. }
  26. break;
  27. case State.Finished:
  28. if (EnsureChars(0, false))
  29. {
  30. EatWhitespace();
  31. if (_isEndOfFile)
  32. {
  33. SetToken(JsonToken.None);
  34. return false;
  35. }
  36. if (_chars[_charPos] == '/')
  37. {
  38. ParseComment(true);
  39. return true;
  40. }
  41. throw JsonReaderException.Create(this, "Additional text encountered after finished reading JSON content: {0}.".FormatWith(CultureInfo.InvariantCulture, _chars[_charPos]));
  42. }
  43. SetToken(JsonToken.None);
  44. return false;
  45. default:
  46. throw JsonReaderException.Create(this, "Unexpected state: {0}.".FormatWith(CultureInfo.InvariantCulture, CurrentState));
  47. }
  48. }
  49. }

JsonTextReader的ParseValue方法会根据当前读取的字符决定下一步的处理逻辑;由于_chars数组默认初始化的时候第一个字符是\0,并且_charsUsed和_charPos都为0,所以会调用ReadData方法;

  1. private bool ParseValue()
  2. {
  3. MiscellaneousUtils.Assert(_chars != null);
  4. while (true)
  5. {
  6. char currentChar = _chars[_charPos];
  7. switch (currentChar)
  8. {
  9. case '\0':
  10. if (_charsUsed == _charPos)
  11. {
  12. if (ReadData(false) == 0)
  13. {
  14. return false;
  15. }
  16. }
  17. else
  18. {
  19. _charPos++;
  20. }
  21. break;
  22. case '"':
  23. case '\'':
  24. ParseString(currentChar, ReadType.Read);
  25. return true;
  26. case 't':
  27. ParseTrue();
  28. return true;
  29. case 'f':
  30. ParseFalse();
  31. return true;
  32. case 'n':
  33. if (EnsureChars(1, true))
  34. {
  35. char next = _chars[_charPos + 1];
  36. if (next == 'u')
  37. {
  38. ParseNull();
  39. }
  40. else if (next == 'e')
  41. {
  42. ParseConstructor();
  43. }
  44. else
  45. {
  46. throw CreateUnexpectedCharacterException(_chars[_charPos]);
  47. }
  48. }
  49. else
  50. {
  51. _charPos++;
  52. throw CreateUnexpectedEndException();
  53. }
  54. return true;
  55. case 'N':
  56. ParseNumberNaN(ReadType.Read);
  57. return true;
  58. case 'I':
  59. ParseNumberPositiveInfinity(ReadType.Read);
  60. return true;
  61. case '-':
  62. if (EnsureChars(1, true) && _chars[_charPos + 1] == 'I')
  63. {
  64. ParseNumberNegativeInfinity(ReadType.Read);
  65. }
  66. else
  67. {
  68. ParseNumber(ReadType.Read);
  69. }
  70. return true;
  71. case '/':
  72. ParseComment(true);
  73. return true;
  74. case 'u':
  75. ParseUndefined();
  76. return true;
  77. case '{':
  78. _charPos++;
  79. SetToken(JsonToken.StartObject);
  80. return true;
  81. case '[':
  82. _charPos++;
  83. SetToken(JsonToken.StartArray);
  84. return true;
  85. case ']':
  86. _charPos++;
  87. SetToken(JsonToken.EndArray);
  88. return true;
  89. case ',':
  90. // don't increment position, the next call to read will handle comma
  91. // this is done to handle multiple empty comma values
  92. SetToken(JsonToken.Undefined);
  93. return true;
  94. case ')':
  95. _charPos++;
  96. SetToken(JsonToken.EndConstructor);
  97. return true;
  98. case StringUtils.CarriageReturn:
  99. ProcessCarriageReturn(false);
  100. break;
  101. case StringUtils.LineFeed:
  102. ProcessLineFeed();
  103. break;
  104. case ' ':
  105. case StringUtils.Tab:
  106. // eat
  107. _charPos++;
  108. break;
  109. default:
  110. if (char.IsWhiteSpace(currentChar))
  111. {
  112. // eat
  113. _charPos++;
  114. break;
  115. }
  116. if (char.IsNumber(currentChar) || currentChar == '-' || currentChar == '.')
  117. {
  118. ParseNumber(ReadType.Read);
  119. return true;
  120. }
  121. throw CreateUnexpectedCharacterException(currentChar);
  122. }
  123. }
  124. }

在JsonTextReader内部会通过_chars来读取少量的字符;

  1. private int ReadData(bool append)
  2. {
  3. return ReadData(append, 0);
  4. }
  5. private int ReadData(bool append, int charsRequired)
  6. {
  7. if (_isEndOfFile)
  8. {
  9. return 0;
  10. }
  11. PrepareBufferForReadData(append, charsRequired);
  12. MiscellaneousUtils.Assert(_chars != null);
  13. int attemptCharReadCount = _chars.Length - _charsUsed - 1;
  14. int charsRead = _reader.Read(_chars, _charsUsed, attemptCharReadCount);
  15. _charsUsed += charsRead;
  16. if (charsRead == 0)
  17. {
  18. _isEndOfFile = true;
  19. }
  20. _chars[_charsUsed] = '\0';
  21. return charsRead;
  22. }

四、使用JsonTextReader优化反序列化

通过以上分析,我们可以直接使用二进制的文件流来读取文件,并将它传递给JsonTextReader,这样就可以实现小片段的读取并序列化;

  1. using (FileStream s = File.Open("json.txt", FileMode.Open))
  2. using (StreamReader sr = new StreamReader(s))
  3. using (JsonReader reader = new JsonTextReader(sr))
  4. {
  5. JsonSerializer serializer = new JsonSerializer();
  6. // read the json from a stream
  7. // json size doesn't matter because only a small piece is read at a time
  8. BigTable table = serializer.Deserialize<BigTable>(reader);
  9. }
  1. using (StreamReader sr = File.OpenText("json.txt"))
  2. using (JsonReader reader = new JsonTextReader(sr))
  3. {
  4. JsonSerializer serializer = new JsonSerializer();
  5. // read the json from a stream
  6. // json size doesn't matter because only a small piece is read at a time
  7. BigTable table = serializer.Deserialize<BigTable>(reader);
  8. }

原文链接:https://www.cnblogs.com/wufengtinghai/p/17150078.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号