Я часто работаю с двоичными файлами, которые обрабатываю с помощью C#.
Форматы данных очень специфичны в зависимости от приложения, но в официальном документе точно указано, какие байты кодируют какие значения.
Примером файла или формата данных может быть ASPRS LAS (конечно, этот формат немного сложнее чем пример вопроса ниже).
Предположим, что двоичный файл состоит из множества одинаковых пакетов данных.
До сих пор я бы написал класс, который содержит один файл данных. запись из бинарного файла. В предполагаемом случае я бы обработал весь двоичный файл в массив объектов этого класса.
Предположим, пакет данных содержит следующие данные:
Код: Выделить всё
int
char[32]
float
float
int
double
Код: Выделить всё
public class Packet
{
private int _id;
private char[] _text;
private float _value1;
private float _value2;
private int _value3;
private double _value4;
// properties...
// etc.
// constructor 1 to read packet value by value
public Record(BinaryFile bf)
{
_id = bf.ReadInt();
_text = bf.ReadString(32).ToCharArray();
_value1 = bf.ReadFloat();
_value2 = bf.ReadFloat();
_value3 = bf.ReadInt();
_value4 = bf.ReadDouble();
}
// constructor 2 to create object from previously read byte-array
public Record(ref byte[] data, ref int startindex)
{
_id = BitConverter.ToInt32(data, startindex);
startindex += 4;
_text = new char[32];
for(int i = 0; i < 32; i++)
{
_text[i] = (char)data[startindex+i];
}
startindex += 32;
// etc...
}
// getter/setter methods...
// method 1 to write packet value by value
public void WriteToFile(BinaryFile bf)
{
bf.WriteInt(_id);
for(int i = 0; i < _text.Length; i++)
{
bf.WriteChar(_text[i]);
}
bf.WriteFloat(_value1);
// etc...
}
// method 2 to return bytes of packet to write it as a whole into the file later
public byte[] GetBytes()
{
byte[] bytes = new byte[56];
Array.Copy(BitConverter.GetBytes(_id), 0, bytes, 0, 4);
Array.Copy(BitConverter.GetBytes(_text), 0, bytes, 4, 32);
Array.Copy(BitConverter.GetBytes(_value1), 0, bytes, 36, 4);
// etc.
return bytes;
}
// etc.
}
Код: Выделить всё
public class BinaryFile
{
// ...
public int ReadInt(ByteOrder byteOrder)
{
byte[] ret = ReadBytes(4, byteOrder);
return BitConverter.ToInt32(ret, 0);
}
public byte[] ReadBytes(int numberOfBytes, ByteOrder byteOrder)
{
if (numberOfBytes < 1)
{
throw new ArgumentException("...");
}
// prepare Array
byte[] ret = new byte[numberOfBytes];
// read bytes
this._fileStream.Read(ret, 0, numberOfBytes);
// check byte-order
if (byteOrder == ByteOrder.BigEndian)
{
Array.Reverse(ret);
}
return ret;
}
// etc...
}
Затем я перебираю массив и получаю доступ к отдельным значениям объекта Packet либо через методы получения и установки, либо через свойства. Некоторые данные придется изменить, некоторые нет. После обработки, если были внесены изменения, я бы записал файл обратно на жесткий диск.
На мой взгляд, процедура в первом конструкторе/методе записи проста для понимания, но, естественно, несколько медленнее, так как все значения читаются/записываются индивидуально.
Второй конструктор получает ссылку на весь файл в виде массива байтов и обрабатывает сами значения, что быстрее, но не так просто понимаю, а также немного более подвержен ошибкам (startindex += ...). Кроме того, второй метод, который возвращает массив байтов, кажется несколько неудобным.
Имеет ли этот подход (жестко запрограммированное поле для каждого значения пакета) смысл в принципе или нет?
Было бы лучше, если бы класс содержал только массив байтов, а соответствующие значения преобразовывались только при доступе через метод или свойство? Есть ли какие-либо преимущества или недостатки у этого варианта с точки зрения производительности и использования памяти (или других)? Чтение и запись файла, вероятно, будет намного быстрее, но обработка отдельных значений, вероятно, нет?
Однако затем я прихожу к тому, что могу прочитать весь файл как байт. массив и вычислить смещение в байтах отдельных записей данных при доступе, а затем при необходимости преобразовать соответствующие значения. Таким образом, мне не пришлось бы создавать тысячи (или даже миллионы) объектов этого класса, но мне пришлось бы вычислять гораздо больше. Это было бы еще сложнее, если бы размер пакета данных отличался от пакета к пакету. Другая потенциальная проблема могла бы возникнуть, если бы мне пришлось изменить размер пакета данных. Тогда мне пришлось бы воссоздавать весь массив для каждого измененного пакета. Это было бы еще более неблагоприятно, если бы размер файла был как минимум вдвое меньше доступной оперативной памяти...
Мне часто приходится перебирать каждую запись данных файла в цикле, поэтому наверное, не имело бы смысла (с точки зрения ввода-вывода) вообще не читать файл, а обращаться к нему непосредственно на жестком диске?
Поскольку это простой пример и реальный- мировые файлы часто более сложны, подход сериализации не будет работать (также прокомментировано пользователем555045).
Подробнее здесь: https://stackoverflow.com/questions/789 ... s-and-back