I thought I would attempt to compare the speed of each of the methods listed here for the hell of it. I based the speed testing code off this.
The result is that BitConverter+String.Replace seems to be faster than most other simple ways. But the speed can be improved with algorithms like Nathan Moinvaziri's ByteArrayToHexString or Kurt's ToHex.
I also found it interesting that string.Concat and string.Join are much slower than StringBuilder implementations for long strings, but similar for shorter arrays. Probably due to expanding the StringBuilder on the longer strings, so setting the initial size should negate this difference.
- Took each bit of code from an answer here:
- BitConvertRep = Answer by Guffa, BitConverter and String.Replace (I'd recommend for most cases)
- StringBuilder = Answer by Quintin Robinson, foreach char StringBuilder.Append
- LinqConcat = Answer by Michael Buen, string.Concat of Linq built array
- LinqJoin = Answer by mloskot, string.Join of Linq built array
- LinqAgg = Answer by Matthew Whited, IEnumerable.Aggregate with StringBuilder
- ToHex = Answer by Kurt, sets chars in an array, using byte values to get hex
- ByteArrayToHexString = Answer by Nathan Moinvaziri, approx same speed as the ToHex above, and is probably easier to read (I'd recommend for speed)
- ToHexFromTable = Linked in answer by Nathan Moinvaziri, for me this is near the same speed as the above 2 but requires an array of 256 strings to always exist
With: LONG_STRING_LENGTH = 1000 * 1024;
- BitConvertRep calculation Time Elapsed 27,202 ms (fastest built in/simple)
- StringBuilder calculation Time Elapsed 75,723 ms (StringBuilder no reallocate)
- LinqConcat calculation Time Elapsed 182,094 ms
- LinqJoin calculation Time Elapsed 181,142 ms
- LinqAgg calculation Time Elapsed 93,087 ms (StringBuilder with reallocating)
- ToHex calculation Time Elapsed 19,167 ms (fastest)
With: LONG_STRING_LENGTH = 100 * 1024;
, Similar results
- BitConvertReplace calculation Time Elapsed 3431 ms
- StringBuilder calculation Time Elapsed 8289 ms
- LinqConcat calculation Time Elapsed 21512 ms
- LinqJoin calculation Time Elapsed 19433 ms
- LinqAgg calculation Time Elapsed 9230 ms
- ToHex calculation Time Elapsed 1976 ms
With: int MANY_STRING_COUNT = 1000;
int MANY_STRING_LENGTH = 1024;
(Same byte count as first test but in different arrays)
- BitConvertReplace calculation Time Elapsed 25,680 ms
- StringBuilder calculation Time Elapsed 78,411 ms
- LinqConcat calculation Time Elapsed 101,233 ms
- LinqJoin calculation Time Elapsed 99,311 ms
- LinqAgg calculation Time Elapsed 84,660 ms
- ToHex calculation Time Elapsed 18,221 ms
With: int MANY_STRING_COUNT = 2000;
int MANY_STRING_LENGTH = 20;
- BitConvertReplace calculation Time Elapsed 1347 ms
- StringBuilder calculation Time Elapsed 3234 ms
- LinqConcat calculation Time Elapsed 5013 ms
- LinqJoin calculation Time Elapsed 4826 ms
- LinqAgg calculation Time Elapsed 3589 ms
- ToHex calculation Time Elapsed 772 ms
Testing code I used:
void Main()
{
int LONG_STRING_LENGTH = 100 * 1024;
int MANY_STRING_COUNT = 1024;
int MANY_STRING_LENGTH = 100;
var source = GetRandomBytes(LONG_STRING_LENGTH);
List<byte[]> manyString = new List<byte[]>(MANY_STRING_COUNT);
for (int i = 0; i < MANY_STRING_COUNT; ++i)
{
manyString.Add(GetRandomBytes(MANY_STRING_LENGTH));
}
var algorithms = new Dictionary<string,Func<byte[], string>>();
algorithms["BitConvertReplace"] = BitConv;
algorithms["StringBuilder"] = StringBuilderTest;
algorithms["LinqConcat"] = LinqConcat;
algorithms["LinqJoin"] = LinqJoin;
algorithms["LinqAgg"] = LinqAgg;
algorithms["ToHex"] = ToHex;
algorithms["ByteArrayToHexString"] = ByteArrayToHexString;
Console.WriteLine(" === Long string test");
foreach (var pair in algorithms) {
TimeAction(pair.Key + " calculation", 500, () =>
{
pair.Value(source);
});
}
Console.WriteLine(" === Many string test");
foreach (var pair in algorithms) {
TimeAction(pair.Key + " calculation", 500, () =>
{
foreach (var str in manyString)
{
pair.Value(str);
}
});
}
}
// Define other methods and classes here
static void TimeAction(string description, int iterations, Action func) {
var watch = new Stopwatch();
watch.Start();
for (int i = 0; i < iterations; i++) {
func();
}
watch.Stop();
Console.Write(description);
Console.WriteLine(" Time Elapsed {0} ms", watch.ElapsedMilliseconds);
}
//static byte[] GetRandomBytes(int count) {
// var bytes = new byte[count];
// (new Random()).NextBytes(bytes);
// return bytes;
//}
static Random rand = new Random();
static byte[] GetRandomBytes(int count) {
var bytes = new byte[count];
rand.NextBytes(bytes);
return bytes;
}
static string BitConv(byte[] data)
{
return BitConverter.ToString(data).Replace("-", string.Empty);
}
static string StringBuilderTest(byte[] data)
{
StringBuilder sb = new StringBuilder(data.Length*2);
foreach (byte b in data)
sb.Append(b.ToString("X2"));
return sb.ToString();
}
static string LinqConcat(byte[] data)
{
return string.Concat(data.Select(b => b.ToString("X2")).ToArray());
}
static string LinqJoin(byte[] data)
{
return string.Join("",
data.Select(
bin => bin.ToString("X2")
).ToArray());
}
static string LinqAgg(byte[] data)
{
return data.Aggregate(new StringBuilder(),
(sb,v)=>sb.Append(v.ToString("X2"))
).ToString();
}
static string ToHex(byte[] bytes)
{
char[] c = new char[bytes.Length * 2];
byte b;
for(int bx = 0, cx = 0; bx < bytes.Length; ++bx, ++cx)
{
b = ((byte)(bytes[bx] >> 4));
c[cx] = (char)(b > 9 ? b - 10 + 'A' : b + '0');
b = ((byte)(bytes[bx] & 0x0F));
c[++cx] = (char)(b > 9 ? b - 10 + 'A' : b + '0');
}
return new string(c);
}
public static string ByteArrayToHexString(byte[] Bytes)
{
StringBuilder Result = new StringBuilder(Bytes.Length*2);
string HexAlphabet = "0123456789ABCDEF";
foreach (byte B in Bytes)
{
Result.Append(HexAlphabet[(int)(B >> 4)]);
Result.Append(HexAlphabet[(int)(B & 0xF)]);
}
return Result.ToString();
}
Also another answer with a similar process, I haven't compared our results yet.