Skip to content

Commit e3c070e

Browse files
authored
Merge pull request #368 from drewnoakes/spanify-sequential-reader
Spanify SequentialReader
2 parents c8b36ab + f8b1943 commit e3c070e

File tree

7 files changed

+155
-118
lines changed

7 files changed

+155
-118
lines changed

MetadataExtractor.Tests/IO/SequentialReaderTestBase.cs

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -236,15 +236,15 @@ public void GetBytes()
236236
}
237237

238238
[Fact]
239-
public void OverflowBoundsCalculation()
239+
public void GetBytes_OverflowBoundsCalculation()
240240
{
241241
var reader = CreateReader(new byte[10]);
242242
var ex = Assert.Throws<IOException>(() => reader.GetBytes(15));
243243
Assert.Equal("End of data reached.", ex.Message);
244244
}
245245

246246
[Fact]
247-
public void GetBytesEof()
247+
public void GetBytes_Eof()
248248
{
249249
CreateReader(new byte[50]).GetBytes(50);
250250

@@ -255,8 +255,45 @@ public void GetBytesEof()
255255
Assert.Throws<IOException>(() => CreateReader(new byte[50]).GetBytes(51));
256256
}
257257

258+
#if NET
258259
[Fact]
259-
public void GetByteEof()
260+
public void GetBytes_Span()
261+
{
262+
var bytes = new byte[] { 0, 1, 2, 3, 4, 5 };
263+
for (var i = 0; i < bytes.Length; i++)
264+
{
265+
var reader = CreateReader(bytes);
266+
#pragma warning disable CA2014 // Do not use stackalloc in loops
267+
Span<byte> span = stackalloc byte[i];
268+
#pragma warning restore CA2014 // Do not use stackalloc in loops
269+
reader.GetBytes(span);
270+
Assert.Equal(bytes.Take(i).ToArray(), span.ToArray());
271+
}
272+
}
273+
274+
[Fact]
275+
public void GetBytes_OverflowBoundsCalculation_Span()
276+
{
277+
var reader = CreateReader(new byte[10]);
278+
var ex = Assert.Throws<IOException>(() => reader.GetBytes(stackalloc byte[15]));
279+
Assert.Equal("End of data reached.", ex.Message);
280+
}
281+
282+
[Fact]
283+
public void GetBytes_Eof_Span()
284+
{
285+
CreateReader(new byte[50]).GetBytes(stackalloc byte[50]);
286+
287+
var reader = CreateReader(new byte[50]);
288+
reader.GetBytes(stackalloc byte[25]);
289+
reader.GetBytes(stackalloc byte[25]);
290+
291+
Assert.Throws<IOException>(() => CreateReader(new byte[50]).GetBytes(stackalloc byte[51]));
292+
}
293+
#endif
294+
295+
[Fact]
296+
public void GetByte_Eof()
260297
{
261298
CreateReader(new byte[1]).GetByte();
262299

MetadataExtractor/IO/SequentialByteArrayReader.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,15 @@ public override byte[] GetBytes(int count)
3939
return bytes;
4040
}
4141

42+
public override void GetBytes(Span<byte> bytes)
43+
{
44+
if (_index + bytes.Length > _bytes.Length)
45+
throw new IOException("End of data reached.");
46+
47+
_bytes.AsSpan(_index, bytes.Length).CopyTo(bytes);
48+
_index += bytes.Length;
49+
}
50+
4251
public override void GetBytes(byte[] buffer, int offset, int count)
4352
{
4453
if (_index + count > _bytes.Length)

MetadataExtractor/IO/SequentialReader.cs

Lines changed: 52 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
// Copyright (c) Drew Noakes and contributors. All Rights Reserved. Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
22

3+
using System.Buffers.Binary;
4+
35
namespace MetadataExtractor.IO
46
{
57
/// <summary>Base class for reading sequentially through a sequence of data encoded in a byte stream.</summary>
@@ -13,13 +15,8 @@ namespace MetadataExtractor.IO
1315
/// <see cref="IsMotorolaByteOrder"/>.
1416
/// </remarks>
1517
/// <author>Drew Noakes https://drewnoakes.com</author>
16-
public abstract class SequentialReader
18+
public abstract class SequentialReader(bool isMotorolaByteOrder)
1719
{
18-
protected SequentialReader(bool isMotorolaByteOrder)
19-
{
20-
IsMotorolaByteOrder = isMotorolaByteOrder;
21-
}
22-
2320
/// <summary>Get and set the byte order of this reader. <c>true</c> by default.</summary>
2421
/// <remarks>
2522
/// <list type="bullet">
@@ -28,7 +25,7 @@ protected SequentialReader(bool isMotorolaByteOrder)
2825
/// </list>
2926
/// </remarks>
3027
/// <value><c>true</c> for Motorola/big endian, <c>false</c> for Intel/little endian</value>
31-
public bool IsMotorolaByteOrder { get; }
28+
public bool IsMotorolaByteOrder { get; } = isMotorolaByteOrder;
3229

3330
public abstract long Position { get; }
3431

@@ -40,6 +37,8 @@ protected SequentialReader(bool isMotorolaByteOrder)
4037
/// <exception cref="IOException"/>
4138
public abstract byte[] GetBytes(int count);
4239

40+
public abstract void GetBytes(Span<byte> bytes);
41+
4342
/// <summary>Retrieves bytes, writing them into a caller-provided buffer.</summary>
4443
/// <param name="buffer">The array to write bytes to.</param>
4544
/// <param name="offset">The starting position within <paramref name="buffer"/> to write to.</param>
@@ -100,139 +99,81 @@ protected SequentialReader(bool isMotorolaByteOrder)
10099
/// <exception cref="IOException"/>
101100
public ushort GetUInt16()
102101
{
103-
if (IsMotorolaByteOrder)
104-
{
105-
// Motorola - MSB first
106-
return (ushort)
107-
(GetByte() << 8 |
108-
GetByte());
109-
}
110-
// Intel ordering - LSB first
111-
return (ushort)
112-
(GetByte() |
113-
GetByte() << 8);
102+
Span<byte> bytes = stackalloc byte[2];
103+
104+
GetBytes(bytes);
105+
106+
return IsMotorolaByteOrder
107+
? BinaryPrimitives.ReadUInt16BigEndian(bytes)
108+
: BinaryPrimitives.ReadUInt16LittleEndian(bytes);
114109
}
115110

116111
/// <summary>Returns a signed 16-bit int calculated from two bytes of data (MSB, LSB).</summary>
117112
/// <returns>the 16 bit int value, between 0x0000 and 0xFFFF</returns>
118113
/// <exception cref="IOException">the buffer does not contain enough bytes to service the request</exception>
119114
public short GetInt16()
120115
{
121-
if (IsMotorolaByteOrder)
122-
{
123-
// Motorola - MSB first
124-
return (short)
125-
(GetByte() << 8 |
126-
GetByte());
127-
}
128-
// Intel ordering - LSB first
129-
return (short)
130-
(GetByte() |
131-
GetByte() << 8);
116+
Span<byte> bytes = stackalloc byte[2];
117+
118+
GetBytes(bytes);
119+
120+
return IsMotorolaByteOrder
121+
? BinaryPrimitives.ReadInt16BigEndian(bytes)
122+
: BinaryPrimitives.ReadInt16LittleEndian(bytes);
132123
}
133124

134125
/// <summary>Get a 32-bit unsigned integer from the buffer, returning it as a long.</summary>
135126
/// <returns>the unsigned 32-bit int value as a long, between 0x00000000 and 0xFFFFFFFF</returns>
136127
/// <exception cref="IOException">the buffer does not contain enough bytes to service the request</exception>
137128
public uint GetUInt32()
138129
{
139-
if (IsMotorolaByteOrder)
140-
{
141-
// Motorola - MSB first (big endian)
142-
return (uint)
143-
(GetByte() << 24 |
144-
GetByte() << 16 |
145-
GetByte() << 8 |
146-
GetByte());
147-
}
148-
// Intel ordering - LSB first (little endian)
149-
return (uint)
150-
(GetByte() |
151-
GetByte() << 8 |
152-
GetByte() << 16 |
153-
GetByte() << 24);
130+
Span<byte> bytes = stackalloc byte[4];
131+
132+
GetBytes(bytes);
133+
134+
return IsMotorolaByteOrder
135+
? BinaryPrimitives.ReadUInt32BigEndian(bytes)
136+
: BinaryPrimitives.ReadUInt32LittleEndian(bytes);
154137
}
155138

156139
/// <summary>Returns a signed 32-bit integer from four bytes of data.</summary>
157140
/// <returns>the signed 32 bit int value, between 0x00000000 and 0xFFFFFFFF</returns>
158141
/// <exception cref="IOException">the buffer does not contain enough bytes to service the request</exception>
159142
public int GetInt32()
160143
{
161-
if (IsMotorolaByteOrder)
162-
{
163-
// Motorola - MSB first (big endian)
164-
return
165-
GetByte() << 24 |
166-
GetByte() << 16 |
167-
GetByte() << 8 |
168-
GetByte();
169-
}
170-
// Intel ordering - LSB first (little endian)
171-
return
172-
GetByte() |
173-
GetByte() << 8 |
174-
GetByte() << 16 |
175-
GetByte() << 24;
144+
Span<byte> bytes = stackalloc byte[4];
145+
146+
GetBytes(bytes);
147+
148+
return IsMotorolaByteOrder
149+
? BinaryPrimitives.ReadInt32BigEndian(bytes)
150+
: BinaryPrimitives.ReadInt32LittleEndian(bytes);
176151
}
177152

178153
/// <summary>Get a signed 64-bit integer from the buffer.</summary>
179154
/// <returns>the 64 bit int value, between 0x0000000000000000 and 0xFFFFFFFFFFFFFFFF</returns>
180155
/// <exception cref="IOException">the buffer does not contain enough bytes to service the request</exception>
181156
public long GetInt64()
182157
{
183-
if (IsMotorolaByteOrder)
184-
{
185-
// Motorola - MSB first
186-
return
187-
(long)GetByte() << 56 |
188-
(long)GetByte() << 48 |
189-
(long)GetByte() << 40 |
190-
(long)GetByte() << 32 |
191-
(long)GetByte() << 24 |
192-
(long)GetByte() << 16 |
193-
(long)GetByte() << 8 |
194-
GetByte();
195-
}
196-
// Intel ordering - LSB first
197-
return
198-
GetByte() |
199-
(long)GetByte() << 8 |
200-
(long)GetByte() << 16 |
201-
(long)GetByte() << 24 |
202-
(long)GetByte() << 32 |
203-
(long)GetByte() << 40 |
204-
(long)GetByte() << 48 |
205-
(long)GetByte() << 56;
158+
Span<byte> bytes = stackalloc byte[8];
159+
GetBytes(bytes);
160+
161+
return IsMotorolaByteOrder
162+
? BinaryPrimitives.ReadInt64BigEndian(bytes)
163+
: BinaryPrimitives.ReadInt64LittleEndian(bytes);
206164
}
207165

208166
/// <summary>Get an unsigned 64-bit integer from the buffer.</summary>
209167
/// <returns>the unsigned 64 bit int value, between 0x0000000000000000 and 0xFFFFFFFFFFFFFFFF</returns>
210168
/// <exception cref="IOException">the buffer does not contain enough bytes to service the request</exception>
211169
public ulong GetUInt64()
212170
{
213-
if (IsMotorolaByteOrder)
214-
{
215-
// Motorola - MSB first
216-
return
217-
(ulong)GetByte() << 56 |
218-
(ulong)GetByte() << 48 |
219-
(ulong)GetByte() << 40 |
220-
(ulong)GetByte() << 32 |
221-
(ulong)GetByte() << 24 |
222-
(ulong)GetByte() << 16 |
223-
(ulong)GetByte() << 8 |
224-
GetByte();
225-
}
226-
// Intel ordering - LSB first
227-
return
228-
GetByte() |
229-
(ulong)GetByte() << 8 |
230-
(ulong)GetByte() << 16 |
231-
(ulong)GetByte() << 24 |
232-
(ulong)GetByte() << 32 |
233-
(ulong)GetByte() << 40 |
234-
(ulong)GetByte() << 48 |
235-
(ulong)GetByte() << 56;
171+
Span<byte> bytes = stackalloc byte[8];
172+
GetBytes(bytes);
173+
174+
return IsMotorolaByteOrder
175+
? BinaryPrimitives.ReadUInt64BigEndian(bytes)
176+
: BinaryPrimitives.ReadUInt64LittleEndian(bytes);
236177
}
237178

238179
#pragma warning restore format
@@ -271,8 +212,14 @@ public float GetS15Fixed16()
271212
/// <exception cref="IOException"/>
272213
public string GetString(int bytesRequested, Encoding encoding)
273214
{
215+
#if NETSTANDARD2_1
216+
Span<byte> bytes = bytesRequested > 2048 ? new byte[bytesRequested] : stackalloc byte[bytesRequested];
217+
GetBytes(bytes);
218+
return encoding.GetString(bytes);
219+
#else
274220
var bytes = GetBytes(bytesRequested);
275221
return encoding.GetString(bytes, 0, bytes.Length);
222+
#endif
276223
}
277224

278225
public StringValue GetStringValue(int bytesRequested, Encoding? encoding = null)

0 commit comments

Comments
 (0)