Удаление завершающих нулей из байтового массива в C#
Хорошо, я читаю в dat-файлах в байтовый массив. По какой-то причине люди, которые генерируют эти файлы, помещают в конец файла бесполезные нулевые байты размером около половины мегабайта. Кто-нибудь знает быстрый способ обрезать их до конца?
Первой мыслью было начать с конца массива и выполнять итерацию в обратном направлении до тех пор, пока я не найду что-то, кроме нуля, а затем скопировать все до этой точки, но мне интересно, если нет лучшего способа.
Чтобы ответить на некоторые вопросы: Вы уверены, что 0 байтов определенно находятся в файле, а не в баге в коде чтения файла? Да, я в этом уверен.
Можете ли вы определенно обрезать все конечные 0? Да.
Может ли быть 0 в остальной части файла? Да, могут быть другие места 0, так что нет, я не могу начать с начала и остановиться на первых 0.
11 ответов
Учитывая ответы на дополнительные вопросы, похоже, вы в основном делаете правильные вещи. В частности, вам нужно коснуться каждого байта файла, начиная с последних 0 и далее, чтобы убедиться, что он имеет только 0.
Теперь, нужно ли вам копировать все или нет, зависит от того, что вы тогда делаете с данными.
- Возможно, вы могли бы запомнить индекс и сохранить его с данными или именем файла.
- Вы можете скопировать данные в новый байтовый массив
- Если вы хотите "исправить" файл, вы можете вызвать FileStream.SetLength, чтобы обрезать файл
"Вы должны прочитать каждый байт между точкой усечения и концом файла", хотя это важная часть.
Я согласен с Джоном. Критический бит состоит в том, что вы должны "касаться" каждого байта от последнего до первого ненулевого байта. Что-то вроде этого:
byte[] foo;
// populate foo
int i = foo.Length - 1;
while(foo[i] == 0)
--i;
// now foo[i] is the last non-zero byte
byte[] bar = new byte[i+1];
Array.Copy(foo, bar, i+1);
Я почти уверен, что это настолько эффективно, насколько вы сможете это сделать.
@ Фактор Мистик,
Я думаю, что есть кратчайший путь:
var data = new byte[] { 0x01, 0x02, 0x00, 0x03, 0x04, 0x00, 0x00, 0x00, 0x00 };
var new_data = data.TakeWhile((v, index) => data.Skip(index).Any(w => w != 0x00)).ToArray();
Как насчет этого:
[Test]
public void Test()
{
var chars = new [] {'a', 'b', '\0', 'c', '\0', '\0'};
File.WriteAllBytes("test.dat", Encoding.ASCII.GetBytes(chars));
var content = File.ReadAllText("test.dat");
Assert.AreEqual(6, content.Length); // includes the null bytes at the end
content = content.Trim('\0');
Assert.AreEqual(4, content.Length); // no more null bytes at the end
// but still has the one in the middle
}
Предполагая, что 0= ноль, это, вероятно, ваша лучшая ставка... в качестве незначительной настройки, вы можете использовать Buffer.BlockCopy
когда вы, наконец, скопировать полезные данные..
Проверить это:
private byte[] trimByte(byte[] input)
{
if (input.Length > 1)
{
int byteCounter = input.Length - 1;
while (input[byteCounter] == 0x00)
{
byteCounter--;
}
byte[] rv = new byte[(byteCounter + 1)];
for (int byteCounter1 = 0; byteCounter1 < (byteCounter + 1); byteCounter1++)
{
rv[byteCounter1] = input[byteCounter1];
}
return rv;
}
Когда файл большой (намного больше моей оперативной памяти), я использую его для удаления конечных нулей:
static void RemoveTrailingNulls(string inputFilename, string outputFilename)
{
int bufferSize = 100 * 1024 * 1024;
long totalTrailingNulls = 0;
byte[] emptyArray = new byte[bufferSize];
using (var inputFile = File.OpenRead(inputFilename))
using (var inputFileReversed = new ReverseStream(inputFile))
{
var buffer = new byte[bufferSize];
while (true)
{
var start = DateTime.Now;
var bytesRead = inputFileReversed.Read(buffer, 0, buffer.Length);
if (bytesRead == emptyArray.Length && Enumerable.SequenceEqual(emptyArray, buffer))
{
totalTrailingNulls += buffer.Length;
}
else
{
var nulls = buffer.Take(bytesRead).TakeWhile(b => b == 0).Count();
totalTrailingNulls += nulls;
if (nulls < bytesRead)
{
//found the last non-null byte
break;
}
}
var duration = DateTime.Now - start;
var mbPerSec = (bytesRead / (1024 * 1024D)) / duration.TotalSeconds;
Console.WriteLine($"{mbPerSec:N2} MB/seconds");
}
var lastNonNull = inputFile.Length - totalTrailingNulls;
using (var outputFile = File.Open(outputFilename, FileMode.Create, FileAccess.Write))
{
inputFile.Seek(0, SeekOrigin.Begin);
inputFile.CopyTo(outputFile, lastNonNull, bufferSize);
}
}
}
Он использует класс ReverseStream, который можно найти здесь.
И этот метод расширения:
public static class Extensions
{
public static long CopyTo(this Stream input, Stream output, long count, int bufferSize)
{
byte[] buffer = new byte[bufferSize];
long totalRead = 0;
while (true)
{
if (count == 0) break;
int read = input.Read(buffer, 0, (int)Math.Min(bufferSize, count));
if (read == 0) break;
totalRead += read;
output.Write(buffer, 0, read);
count -= read;
}
return totalRead;
}
}
Всегда есть ответ LINQ
byte[] data = new byte[] { 0x01, 0x02, 0x00, 0x03, 0x04, 0x00, 0x00, 0x00, 0x00 };
bool data_found = false;
byte[] new_data = data.Reverse().SkipWhile(point =>
{
if (data_found) return false;
if (point == 0x00) return true; else { data_found = true; return false; }
}).Reverse().ToArray();
Если в файле нулевые байты могут быть допустимыми значениями, знаете ли вы, что последний байт в файле не может быть нулевым. Если это так, то лучше всего выполнить итерацию в обратном направлении и найти первую ненулевую запись, если нет, то нет способа определить, где находится фактический конец файла.
Если вы знаете больше о формате данных, например, не может быть последовательности нулевых байтов длиннее двух байтов (или какого-либо подобного ограничения). Тогда вы сможете выполнить бинарный поиск "точки перехода". Это должно быть намного быстрее, чем линейный поиск (при условии, что вы можете прочитать весь файл).
Основная идея (используя мое предыдущее предположение об отсутствии последовательных нулевых байтов) будет:
var data = (byte array of file data...);
var index = data.length / 2;
var jmpsize = data.length/2;
while(true)
{
jmpsize /= 2;//integer division
if( jmpsize == 0) break;
byte b1 = data[index];
byte b2 = data[index + 1];
if(b1 == 0 && b2 == 0) //too close to the end, go left
index -=jmpsize;
else
index += jmpsize;
}
if(index == data.length - 1) return data.length;
byte b1 = data[index];
byte b2 = data[index + 1];
if(b2 == 0)
{
if(b1 == 0) return index;
else return index + 1;
}
else return index + 2;
Вы можете просто посчитать число ноль в конце массива и использовать его вместо.Length при последующей итерации массива. Вы можете заключить это в капсулу так, как вам нравится. Главное, вам не нужно копировать это в новую структуру. Если они большие, это может стоить того.
В моем случае подход LINQ никогда не заканчивался ^))) Это медленная работа с байтовыми массивами!
Ребята, почему вы не будете использовать метод Array.Copy()?
/// <summary>
/// Gets array of bytes from memory stream.
/// </summary>
/// <param name="stream">Memory stream.</param>
public static byte[] GetAllBytes(this MemoryStream stream)
{
byte[] result = new byte[stream.Length];
Array.Copy(stream.GetBuffer(), result, stream.Length);
return result;
}