BinaryReader.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686
  1. // Licensed to the .NET Foundation under one or more agreements.
  2. // The .NET Foundation licenses this file to you under the MIT license.
  3. // See the LICENSE file in the project root for more information.
  4. /*============================================================
  5. **
  6. **
  7. **
  8. **
  9. **
  10. ** Purpose: Wraps a stream and provides convenient read functionality
  11. ** for strings and primitive types.
  12. **
  13. **
  14. ============================================================*/
  15. using System.Buffers.Binary;
  16. using System.Diagnostics;
  17. using System.Runtime.CompilerServices;
  18. using System.Runtime.InteropServices;
  19. using System.Text;
  20. namespace System.IO
  21. {
  22. public class BinaryReader : IDisposable
  23. {
  24. private const int MaxCharBytesSize = 128;
  25. private Stream _stream;
  26. private byte[] _buffer;
  27. private Decoder _decoder;
  28. private byte[] _charBytes;
  29. private char[] _singleChar;
  30. private char[] _charBuffer;
  31. private int _maxCharsSize; // From MaxCharBytesSize & Encoding
  32. // Performance optimization for Read() w/ Unicode. Speeds us up by ~40%
  33. private bool _2BytesPerChar;
  34. private bool _isMemoryStream; // "do we sit on MemoryStream?" for Read/ReadInt32 perf
  35. private bool _leaveOpen;
  36. public BinaryReader(Stream input) : this(input, Encoding.UTF8, false)
  37. {
  38. }
  39. public BinaryReader(Stream input, Encoding encoding) : this(input, encoding, false)
  40. {
  41. }
  42. public BinaryReader(Stream input, Encoding encoding, bool leaveOpen)
  43. {
  44. if (input == null)
  45. {
  46. throw new ArgumentNullException(nameof(input));
  47. }
  48. if (encoding == null)
  49. {
  50. throw new ArgumentNullException(nameof(encoding));
  51. }
  52. if (!input.CanRead)
  53. {
  54. throw new ArgumentException(SR.Argument_StreamNotReadable);
  55. }
  56. _stream = input;
  57. _decoder = encoding.GetDecoder();
  58. _maxCharsSize = encoding.GetMaxCharCount(MaxCharBytesSize);
  59. int minBufferSize = encoding.GetMaxByteCount(1); // max bytes per one char
  60. if (minBufferSize < 16)
  61. {
  62. minBufferSize = 16;
  63. }
  64. _buffer = new byte[minBufferSize];
  65. // _charBuffer and _charBytes will be left null.
  66. // For Encodings that always use 2 bytes per char (or more),
  67. // special case them here to make Read() & Peek() faster.
  68. _2BytesPerChar = encoding is UnicodeEncoding;
  69. // check if BinaryReader is based on MemoryStream, and keep this for it's life
  70. // we cannot use "as" operator, since derived classes are not allowed
  71. _isMemoryStream = (_stream.GetType() == typeof(MemoryStream));
  72. _leaveOpen = leaveOpen;
  73. Debug.Assert(_decoder != null, "[BinaryReader.ctor]_decoder!=null");
  74. }
  75. public virtual Stream BaseStream
  76. {
  77. get
  78. {
  79. return _stream;
  80. }
  81. }
  82. protected virtual void Dispose(bool disposing)
  83. {
  84. if (disposing)
  85. {
  86. Stream copyOfStream = _stream;
  87. _stream = null;
  88. if (copyOfStream != null && !_leaveOpen)
  89. {
  90. copyOfStream.Close();
  91. }
  92. }
  93. _isMemoryStream = false;
  94. _stream = null;
  95. _buffer = null;
  96. _decoder = null;
  97. _charBytes = null;
  98. _singleChar = null;
  99. _charBuffer = null;
  100. }
  101. public void Dispose()
  102. {
  103. Dispose(true);
  104. }
  105. /// <remarks>
  106. /// Override Dispose(bool) instead of Close(). This API exists for compatibility purposes.
  107. /// </remarks>
  108. public virtual void Close()
  109. {
  110. Dispose(true);
  111. }
  112. public virtual int PeekChar()
  113. {
  114. if (_stream == null)
  115. {
  116. throw Error.GetFileNotOpen();
  117. }
  118. if (!_stream.CanSeek)
  119. {
  120. return -1;
  121. }
  122. long origPos = _stream.Position;
  123. int ch = Read();
  124. _stream.Position = origPos;
  125. return ch;
  126. }
  127. public virtual int Read()
  128. {
  129. if (_stream == null)
  130. {
  131. throw Error.GetFileNotOpen();
  132. }
  133. int charsRead = 0;
  134. int numBytes = 0;
  135. long posSav = 0;
  136. if (_stream.CanSeek)
  137. {
  138. posSav = _stream.Position;
  139. }
  140. if (_charBytes == null)
  141. {
  142. _charBytes = new byte[MaxCharBytesSize]; //REVIEW: We need at most 2 bytes/char here?
  143. }
  144. if (_singleChar == null)
  145. {
  146. _singleChar = new char[1];
  147. }
  148. while (charsRead == 0)
  149. {
  150. // We really want to know what the minimum number of bytes per char
  151. // is for our encoding. Otherwise for UnicodeEncoding we'd have to
  152. // do ~1+log(n) reads to read n characters.
  153. // Assume 1 byte can be 1 char unless _2BytesPerChar is true.
  154. numBytes = _2BytesPerChar ? 2 : 1;
  155. int r = _stream.ReadByte();
  156. _charBytes[0] = (byte)r;
  157. if (r == -1)
  158. {
  159. numBytes = 0;
  160. }
  161. if (numBytes == 2)
  162. {
  163. r = _stream.ReadByte();
  164. _charBytes[1] = (byte)r;
  165. if (r == -1)
  166. {
  167. numBytes = 1;
  168. }
  169. }
  170. if (numBytes == 0)
  171. {
  172. return -1;
  173. }
  174. Debug.Assert(numBytes == 1 || numBytes == 2, "BinaryReader::ReadOneChar assumes it's reading one or 2 bytes only.");
  175. try
  176. {
  177. charsRead = _decoder.GetChars(_charBytes, 0, numBytes, _singleChar, 0);
  178. }
  179. catch
  180. {
  181. // Handle surrogate char
  182. if (_stream.CanSeek)
  183. {
  184. _stream.Seek((posSav - _stream.Position), SeekOrigin.Current);
  185. }
  186. // else - we can't do much here
  187. throw;
  188. }
  189. Debug.Assert(charsRead < 2, "BinaryReader::ReadOneChar - assuming we only got 0 or 1 char, not 2!");
  190. }
  191. Debug.Assert(charsRead > 0);
  192. return _singleChar[0];
  193. }
  194. public virtual byte ReadByte() => InternalReadByte();
  195. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  196. private byte InternalReadByte()
  197. {
  198. // Inlined to avoid some method call overhead with InternalRead.
  199. if (_stream == null)
  200. {
  201. throw Error.GetFileNotOpen();
  202. }
  203. int b = _stream.ReadByte();
  204. if (b == -1)
  205. {
  206. throw Error.GetEndOfFile();
  207. }
  208. return (byte)b;
  209. }
  210. [CLSCompliant(false)]
  211. public virtual sbyte ReadSByte() => (sbyte)InternalReadByte();
  212. public virtual bool ReadBoolean() => InternalReadByte() != 0;
  213. public virtual char ReadChar()
  214. {
  215. int value = Read();
  216. if (value == -1)
  217. {
  218. throw Error.GetEndOfFile();
  219. }
  220. return (char)value;
  221. }
  222. public virtual short ReadInt16() => BinaryPrimitives.ReadInt16LittleEndian(InternalRead(2));
  223. [CLSCompliant(false)]
  224. public virtual ushort ReadUInt16() => BinaryPrimitives.ReadUInt16LittleEndian(InternalRead(2));
  225. public virtual int ReadInt32() => BinaryPrimitives.ReadInt32LittleEndian(InternalRead(4));
  226. [CLSCompliant(false)]
  227. public virtual uint ReadUInt32() => BinaryPrimitives.ReadUInt32LittleEndian(InternalRead(4));
  228. public virtual long ReadInt64() => BinaryPrimitives.ReadInt64LittleEndian(InternalRead(8));
  229. [CLSCompliant(false)]
  230. public virtual ulong ReadUInt64() => BinaryPrimitives.ReadUInt64LittleEndian(InternalRead(8));
  231. public virtual unsafe float ReadSingle() => BitConverter.Int32BitsToSingle(BinaryPrimitives.ReadInt32LittleEndian(InternalRead(4)));
  232. public virtual unsafe double ReadDouble() => BitConverter.Int64BitsToDouble(BinaryPrimitives.ReadInt64LittleEndian(InternalRead(8)));
  233. public virtual decimal ReadDecimal()
  234. {
  235. ReadOnlySpan<byte> span = InternalRead(16);
  236. try
  237. {
  238. return decimal.ToDecimal(span);
  239. }
  240. catch (ArgumentException e)
  241. {
  242. // ReadDecimal cannot leak out ArgumentException
  243. throw new IOException(SR.Arg_DecBitCtor, e);
  244. }
  245. }
  246. public virtual string ReadString()
  247. {
  248. if (_stream == null)
  249. {
  250. throw Error.GetFileNotOpen();
  251. }
  252. int currPos = 0;
  253. int n;
  254. int stringLength;
  255. int readLength;
  256. int charsRead;
  257. // Length of the string in bytes, not chars
  258. stringLength = Read7BitEncodedInt();
  259. if (stringLength < 0)
  260. {
  261. throw new IOException(SR.Format(SR.IO_InvalidStringLen_Len, stringLength));
  262. }
  263. if (stringLength == 0)
  264. {
  265. return string.Empty;
  266. }
  267. if (_charBytes == null)
  268. {
  269. _charBytes = new byte[MaxCharBytesSize];
  270. }
  271. if (_charBuffer == null)
  272. {
  273. _charBuffer = new char[_maxCharsSize];
  274. }
  275. StringBuilder sb = null;
  276. do
  277. {
  278. readLength = ((stringLength - currPos) > MaxCharBytesSize) ? MaxCharBytesSize : (stringLength - currPos);
  279. n = _stream.Read(_charBytes, 0, readLength);
  280. if (n == 0)
  281. {
  282. throw Error.GetEndOfFile();
  283. }
  284. charsRead = _decoder.GetChars(_charBytes, 0, n, _charBuffer, 0);
  285. if (currPos == 0 && n == stringLength)
  286. {
  287. return new string(_charBuffer, 0, charsRead);
  288. }
  289. if (sb == null)
  290. {
  291. sb = StringBuilderCache.Acquire(stringLength); // Actual string length in chars may be smaller.
  292. }
  293. sb.Append(_charBuffer, 0, charsRead);
  294. currPos += n;
  295. } while (currPos < stringLength);
  296. return StringBuilderCache.GetStringAndRelease(sb);
  297. }
  298. public virtual int Read(char[] buffer, int index, int count)
  299. {
  300. if (buffer == null)
  301. {
  302. throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
  303. }
  304. if (index < 0)
  305. {
  306. throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_NeedNonNegNum);
  307. }
  308. if (count < 0)
  309. {
  310. throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
  311. }
  312. if (buffer.Length - index < count)
  313. {
  314. throw new ArgumentException(SR.Argument_InvalidOffLen);
  315. }
  316. if (_stream == null)
  317. {
  318. throw Error.GetFileNotOpen();
  319. }
  320. // SafeCritical: index and count have already been verified to be a valid range for the buffer
  321. return InternalReadChars(new Span<char>(buffer, index, count));
  322. }
  323. public virtual int Read(Span<char> buffer)
  324. {
  325. if (_stream == null)
  326. {
  327. throw Error.GetFileNotOpen();
  328. }
  329. return InternalReadChars(buffer);
  330. }
  331. private int InternalReadChars(Span<char> buffer)
  332. {
  333. Debug.Assert(_stream != null);
  334. int numBytes = 0;
  335. int index = 0;
  336. int charsRemaining = buffer.Length;
  337. if (_charBytes == null)
  338. {
  339. _charBytes = new byte[MaxCharBytesSize];
  340. }
  341. while (charsRemaining > 0)
  342. {
  343. int charsRead = 0;
  344. // We really want to know what the minimum number of bytes per char
  345. // is for our encoding. Otherwise for UnicodeEncoding we'd have to
  346. // do ~1+log(n) reads to read n characters.
  347. numBytes = charsRemaining;
  348. if (_2BytesPerChar)
  349. {
  350. numBytes <<= 1;
  351. }
  352. if (numBytes > MaxCharBytesSize)
  353. {
  354. numBytes = MaxCharBytesSize;
  355. }
  356. int position = 0;
  357. byte[] byteBuffer = null;
  358. if (_isMemoryStream)
  359. {
  360. Debug.Assert(_stream is MemoryStream);
  361. MemoryStream mStream = (MemoryStream)_stream;
  362. position = mStream.InternalGetPosition();
  363. numBytes = mStream.InternalEmulateRead(numBytes);
  364. byteBuffer = mStream.InternalGetBuffer();
  365. }
  366. else
  367. {
  368. numBytes = _stream.Read(_charBytes, 0, numBytes);
  369. byteBuffer = _charBytes;
  370. }
  371. if (numBytes == 0)
  372. {
  373. return (buffer.Length - charsRemaining);
  374. }
  375. Debug.Assert(byteBuffer != null, "expected byteBuffer to be non-null");
  376. checked
  377. {
  378. if (position < 0 || numBytes < 0 || position > byteBuffer.Length - numBytes)
  379. {
  380. throw new ArgumentOutOfRangeException(nameof(numBytes));
  381. }
  382. if (index < 0 || charsRemaining < 0 || index > buffer.Length - charsRemaining)
  383. {
  384. throw new ArgumentOutOfRangeException(nameof(charsRemaining));
  385. }
  386. unsafe
  387. {
  388. fixed (byte* pBytes = byteBuffer)
  389. fixed (char* pChars = &MemoryMarshal.GetReference(buffer))
  390. {
  391. charsRead = _decoder.GetChars(pBytes + position, numBytes, pChars + index, charsRemaining, flush: false);
  392. }
  393. }
  394. }
  395. charsRemaining -= charsRead;
  396. index += charsRead;
  397. }
  398. // this should never fail
  399. Debug.Assert(charsRemaining >= 0, "We read too many characters.");
  400. // we may have read fewer than the number of characters requested if end of stream reached
  401. // or if the encoding makes the char count too big for the buffer (e.g. fallback sequence)
  402. return (buffer.Length - charsRemaining);
  403. }
  404. public virtual char[] ReadChars(int count)
  405. {
  406. if (count < 0)
  407. {
  408. throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
  409. }
  410. if (_stream == null)
  411. {
  412. throw Error.GetFileNotOpen();
  413. }
  414. if (count == 0)
  415. {
  416. return Array.Empty<char>();
  417. }
  418. // SafeCritical: we own the chars buffer, and therefore can guarantee that the index and count are valid
  419. char[] chars = new char[count];
  420. int n = InternalReadChars(new Span<char>(chars));
  421. if (n != count)
  422. {
  423. char[] copy = new char[n];
  424. Buffer.BlockCopy(chars, 0, copy, 0, 2 * n); // sizeof(char)
  425. chars = copy;
  426. }
  427. return chars;
  428. }
  429. public virtual int Read(byte[] buffer, int index, int count)
  430. {
  431. if (buffer == null)
  432. {
  433. throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
  434. }
  435. if (index < 0)
  436. {
  437. throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_NeedNonNegNum);
  438. }
  439. if (count < 0)
  440. {
  441. throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
  442. }
  443. if (buffer.Length - index < count)
  444. {
  445. throw new ArgumentException(SR.Argument_InvalidOffLen);
  446. }
  447. if (_stream == null)
  448. {
  449. throw Error.GetFileNotOpen();
  450. }
  451. return _stream.Read(buffer, index, count);
  452. }
  453. public virtual int Read(Span<byte> buffer)
  454. {
  455. if (_stream == null)
  456. {
  457. throw Error.GetFileNotOpen();
  458. }
  459. return _stream.Read(buffer);
  460. }
  461. public virtual byte[] ReadBytes(int count)
  462. {
  463. if (count < 0)
  464. {
  465. throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
  466. }
  467. if (_stream == null)
  468. {
  469. throw new ObjectDisposedException(null, SR.ObjectDisposed_FileClosed);
  470. }
  471. if (count == 0)
  472. {
  473. return Array.Empty<byte>();
  474. }
  475. byte[] result = new byte[count];
  476. int numRead = 0;
  477. do
  478. {
  479. int n = _stream.Read(result, numRead, count);
  480. if (n == 0)
  481. {
  482. break;
  483. }
  484. numRead += n;
  485. count -= n;
  486. } while (count > 0);
  487. if (numRead != result.Length)
  488. {
  489. // Trim array. This should happen on EOF & possibly net streams.
  490. byte[] copy = new byte[numRead];
  491. Buffer.BlockCopy(result, 0, copy, 0, numRead);
  492. result = copy;
  493. }
  494. return result;
  495. }
  496. private ReadOnlySpan<byte> InternalRead(int numBytes)
  497. {
  498. Debug.Assert(numBytes >= 2 && numBytes <= 16, "value of 1 should use ReadByte. value > 16 requires to change the minimal _buffer size");
  499. if (_isMemoryStream)
  500. {
  501. // no need to check if _stream == null as we will never have null _stream when _isMemoryStream = true
  502. // read directly from MemoryStream buffer
  503. Debug.Assert(_stream is MemoryStream);
  504. return ((MemoryStream)_stream).InternalReadSpan(numBytes);
  505. }
  506. else
  507. {
  508. if (_stream == null)
  509. {
  510. throw Error.GetFileNotOpen();
  511. }
  512. int bytesRead = 0;
  513. int n = 0;
  514. do
  515. {
  516. n = _stream.Read(_buffer, bytesRead, numBytes - bytesRead);
  517. if (n == 0)
  518. {
  519. throw Error.GetEndOfFile();
  520. }
  521. bytesRead += n;
  522. } while (bytesRead < numBytes);
  523. return _buffer;
  524. }
  525. }
  526. // FillBuffer is not performing well when reading from MemoryStreams as it is using the public Stream interface.
  527. // We introduced new function InternalRead which can work directly on the MemoryStream internal buffer or using the public Stream
  528. // interface when working with all other streams. This function is not needed anymore but we decided not to delete it for compatibility
  529. // reasons. More about the subject in: https://github.com/dotnet/coreclr/pull/22102
  530. protected virtual void FillBuffer(int numBytes)
  531. {
  532. if (_buffer != null && (numBytes < 0 || numBytes > _buffer.Length))
  533. {
  534. throw new ArgumentOutOfRangeException(nameof(numBytes), SR.ArgumentOutOfRange_BinaryReaderFillBuffer);
  535. }
  536. int bytesRead = 0;
  537. int n = 0;
  538. if (_stream == null)
  539. {
  540. throw Error.GetFileNotOpen();
  541. }
  542. // Need to find a good threshold for calling ReadByte() repeatedly
  543. // vs. calling Read(byte[], int, int) for both buffered & unbuffered
  544. // streams.
  545. if (numBytes == 1)
  546. {
  547. n = _stream.ReadByte();
  548. if (n == -1)
  549. {
  550. throw Error.GetEndOfFile();
  551. }
  552. _buffer[0] = (byte)n;
  553. return;
  554. }
  555. do
  556. {
  557. n = _stream.Read(_buffer, bytesRead, numBytes - bytesRead);
  558. if (n == 0)
  559. {
  560. throw Error.GetEndOfFile();
  561. }
  562. bytesRead += n;
  563. } while (bytesRead < numBytes);
  564. }
  565. protected internal int Read7BitEncodedInt()
  566. {
  567. // Read out an Int32 7 bits at a time. The high bit
  568. // of the byte when on means to continue reading more bytes.
  569. int count = 0;
  570. int shift = 0;
  571. byte b;
  572. do
  573. {
  574. // Check for a corrupted stream. Read a max of 5 bytes.
  575. // In a future version, add a DataFormatException.
  576. if (shift == 5 * 7) // 5 bytes max per Int32, shift += 7
  577. {
  578. throw new FormatException(SR.Format_Bad7BitInt32);
  579. }
  580. // ReadByte handles end of stream cases for us.
  581. b = ReadByte();
  582. count |= (b & 0x7F) << shift;
  583. shift += 7;
  584. } while ((b & 0x80) != 0);
  585. return count;
  586. }
  587. }
  588. }