我正在查看一个旧的帮助器方法,我已经使用了一段时间来跟踪字节数组到输出。我很久以前就写过了它并且工作正常,但我想知道是否有更好的方法(少用代码)。 Linq浮现在我的脑海中,但我所拥有的解决方案非常低效。我需要的是“foreach16”,或者一些枚举器,它不是一次返回1个元素,而是返回一组可枚举的元素。除了我创建自己的枚举器类之外,还有内置的方法吗?
以下示例提供了有关我正在努力实现的更多信息。
原始代码
static void PrintBytes(byte[] bytes)
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < bytes.Length; i++)
{
if (i > 0 && ((i % 16) == 0))
{
// end of line, flushes bytes and resets buffer
Console.WriteLine(" {0}", sb.ToString());
sb.Length = 0;
}
else if (i > 0 && ((i % 8) == 0))
{
Console.Write(" ");
sb.Append(' ');
}
Console.Write(" {0:X2}", (int)bytes[i]);
if (' ' <= bytes[i] && bytes[i] <= '~')
{
sb.Append((char)bytes[i]);
}
else
{
// non-ASCII or control chars are printed as '.'
sb.Append('.');
}
}
// flushes the last few bytes
if ((bytes.Length % 16) > 0)
{
// prints spaces where the missing bytes would be
int spacesToPrint = 3 * (16 - (bytes.Length % 16));
if ((bytes.Length % 16) <= 8)
{
spacesToPrint++;
}
Console.Write(new string(' ', spacesToPrint));
}
Console.WriteLine(" {0}", sb.ToString());
}
我现在拥有 - 这就是我尝试简化代码的原因。但我正在做很多Skip / Take,这会将代码的复杂性从线性增加到二次。
static void PrintBytesV2(byte[] bytes)
{
for (int i = 0; i < bytes.Length; i += 16)
{
PrintLineV2(bytes, i, Math.Min(16, bytes.Length - i));
}
}
static void PrintLineV2(byte[] array, int offset, int count)
{
Console.Write(
string.Join(
" ",
array
.Skip(offset)
.Take(count)
.Select((b, i) =>
((i == 8) ? " " : "") +
string.Format("{0:X2}", (int)b))));
Console.Write(
new string(
' ',
(16 - count) * 3 +
(count <= 8 ? 1 : 0)) +
" ");
Console.WriteLine(
string.Join(
"",
array
.Skip(offset)
.Take(count)
.Select(b => (' ' <= b && b <= '~') ? (char)b : '.')));
}
请注意,即使新代码是线性的,我也可能会坚持使用原始代码,因为1)它的工作原理; 2)我认为它更清晰。但我不禁想知道是否有某种方法可以迭代群体。
答案 0 :(得分:1)
LINQ使代码更具可读性,并与您使用的IEnumerable的类型分离......但是由于它是抽象的本质,它的效率低于手工制作低级代码以满足您的特定需求
答案 1 :(得分:1)
好吧我不确定这是否更具可读性,但这是一个使用类似于Reactive Extensions的Buffer
扩展方法的解决方案。
public static IEnumerable<IList<T>> Buffer<T>(this IEnumerable<T> orig, int count)
{
return orig.Select((o,i) => new { o, i })
.GroupBy(x => x.i / count, x => x.o)
.Select(g => g.ToList());
}
给定一个16字节的块,将它们变成一个字符串(在每一行的末尾):
static string FormatAsString(IList<byte> bytes)
{
return String.Join(" ",
bytes.Buffer(8).Select(
bs => new String(bs.Select(b => ' ' <= b && b <= '~' ? (char)b : '.').ToArray())
)
);
}
给定一个字节块(通常是16个宽),将它们转换为这些字节的字符串表示(在每行的开头):
static string FormatAsBytes(IList<byte> bytes)
{
var blocks =
bytes.Buffer(8)
.Select(bs => String.Join(" ",
bs.Select(b => String.Format("{0:X2}", (int)b)))
);
return String.Join(" ", blocks);
}
现在,如果我们将输入字节转换为块,那么我们可以在输出上运行以上两个:
static void PrintBytesWide(byte[] bytes, int width)
{
foreach (var line in bytes.Buffer(width))
{
Console.WriteLine("{0} {1}", FormatAsBytes(line).PadRight((width + 1) * 3, ' '), FormatAsString(line));
}
}
使用16字节块运行:
var bytes = Encoding.UTF8.GetBytes("the quick brown fox");
PrintBytesWide(bytes, 16);
对我而言,这与原始输出的输出基本相同;
原件:
74 68 65 20 71 75 69 63 6B 20 62 72 6F 77 6E 20 the quic k brown
66 6F 78 fox
新:
74 68 65 20 71 75 69 63 6B 20 62 72 6F 77 6E 20 the quic k brown
66 6F 78 fox
但当然美丽的是你可以做不同的宽度!
PrintBytesWide(bytes, 8);
74 68 65 20 71 75 69 63 the quic
6B 20 62 72 6F 77 6E 20 k brown
66 6F 78 fox
PrintBytesWide(bytes, 24);
74 68 65 20 71 75 69 63 6B 20 62 72 6F 77 6E 20 66 6F 78 the quic k brown fox
答案 2 :(得分:1)
对于foreach16()类型的实现,这个怎么样?
var sampleSet = Enumerable.Range(0, 200);
sampleSet.ForEachBlock(16, x => Console.WriteLine(string.Join(",", x)));
...
使用此扩展方法:
public static void ForEachBlock<T>(this IEnumerable<T> source, int blockSize, Action<IEnumerable<T>> action)
{
foreach (var group in source.Select((x, index) => new { x, index }).GroupBy(x => x.index / blockSize, y => y.x))
action(group);
}
答案 3 :(得分:1)
以下是您的需求:
var result =
String.Join("\n",
bytes
.Select((b, i) => new { b, i })
.GroupBy(x => x.i / 16, x => x.b)
.Select(bs =>
String.Join(" ",
bs.Select(b =>
String
.Format("{0:X2}", b)))
.PadRight(16 * 3, ' ')));
我用“快速的棕色狐狸”测试了上面的代码。 (使用UTF8)得到了这个输出:
54 68 65 20 71 75 69 63 6B 20 62 72 6F 77 6E 20
66 6F 78 2E
我的第一个版本显然有点仓促。这可能会更完整。
Func<string, IEnumerable<byte>> toBytes =
x => System.Text.UTF8Encoding.UTF8.GetBytes(x);
Func<IEnumerable<byte>, string> toString =
x => System.Text.UTF8Encoding.UTF8.GetString(x.ToArray());
Func<IEnumerable<byte>, string> toHexBlock =
xs => String.Join(" ", xs.Select(x => String.Format("{0:X2}", x)));
Func<IEnumerable<byte>, string> toHexLine =
xs =>
String
.Format("{0} {1}",
toHexBlock(xs.Take(8)),
toHexBlock(xs.Skip(8).Take(8)))
.PadRight(16 * 3 + 1, ' ')
+ String
.Format("{0} {1}",
toString(xs.Take(8)),
toString(xs.Skip(8).Take(8)));
var result =
String.Join("\n",
toBytes("The even quicker brown fox.")
.Select((b, i) => new { b, i })
.GroupBy(x => x.i / 16, x => x.b)
.Select(bs => toHexLine(bs)));