Сегодня - простая задачка на знание справки Delphi. Итак, есть такой код:
var Buf: RawByteString; // AnsiString для старых Delphi Data: RawByteString; FS: TFileStream; begin ... Data := ''; SetLength(Buf, 10240); while FS.Position < FS.Size do begin // SetLength сработает для последнего куска SetLength(Buf, FS.Read(Pointer(Buf)^, Length(Buf))); Data := Data + Buf; end; end;Этот простой код читает файл, загружая его в
Data
поблочно: блоками по 10 Кб, читая каждый блок в Buf
и соединяя их друг за другом в цельное содержимое.Однако в этом коде есть одна проблема. Что с ним не так? И как это исправить?
Ответ.
Строки в Delphi используют Copy On Write, а из-за прямого доступа на запись к данным строки этот механизм не работает. UniqueString поможет.
ОтветитьУдалитьИли же в FS.Read использовать Buf[1] вместо Pointer(Buf)^.
ОтветитьУдалитьAnsiString = array[0.. ] of Char
ОтветитьУдалитьбайт с адресом 0 в этом массиве указывает длину строки. Загрузка текстовой строки Pointer(Buf)^ приведет к тому что в [0] будет записано что угодно, но только не длина. Соответственно операция Data := Data + Buf в итоге даст мусор.
такое тоже помогает:
ОтветитьУдалитьData := Data + Copy(Buf, 1, Length(Buf));
но совет с Buf[1] правильнее и проще :)
Однако код-то работает.
ОтветитьУдалитьЗабавные комментарии.
ОтветитьУдалитьВот Вы подумайте, как в один байт уместить число 10240 (длина строки в примере)? Никак.
Переменные типа string (AnsiString/RawByteString) указывают на начало строки, а не на байт, содержащий размер строки (как в старом-добром Pascal'е, или как в случае с типом ShortString). Поэтому в примере всё корректно в этом плане.
Мне кажется, что проблема заключается в том, что FS.Read в случае ошибки чтения вернёт 0 (фактическое кол-во прочитанных байт), что приведёт к... бесконечному циклу.
Вероятность возникновения такой ошибки - не велика, и потому этот код работает. Но если есть вероятность выше ноля, то такое когда-нибудь да и произойдёт. По закону Мёрфи :)
Исправить код можно добавлением условия в while:
while (FS.Position < FS.Size) and (Length(Buf) > 0) do
Но лично я предпочитаю немного другой, более прозрачный стиль кодирования:
while FS.Position < FS.Size do
begin
Count := FS.Read(Pointer(Buf)^, Length(Buf));
if Count = 0 then
//попытаться понять, в чём ошибка чтения, или просто передать ошибку вверх:
RaiseLastOSError;
if Count < Length(Buf) then
SetLength(Buf, Count);
Data := Data + Buf; // это тоже не очень хорошая строка - за кадром происходит перераспределение памяти
end;
Chaa прав. В строке 14 нужно добавить
ОтветитьУдалитьUniqueString(AnsiString(Data));
Ну или если оптимальнее:
if Data = Buf then
UniqueString(AnsiString(Data));
Действительно, UniqueString тут не хватает.. это интересно. Получается так, что первая порция данных потеряется (вместо неё будет дублироваться вторая порция данных).
ОтветитьУдалитьКак оно будет оптимальнее - здесь нельзя сказать однозначно, т.к. нам неизвестна сама задача. Можно вообще весь файл считал одним вызовом FS.Read(... FS.Size - FS.Position). Или переменную Buf объявить как указатель и вручную выделять/освобождать под него память, плюс под строку Data сначала выделить память вызовом SetLength(Data, FS.Size - FS.Position) перед циклом, а потом в цикле копировать порцию из Buf.
>Действительно, UniqueString тут не хватает.. это интересно
ОтветитьУдалитьПри использовании в FS.Read Buf[1] вместо Pointer(Buf)^ вызов UniqueString генерируется компилятором.
Конструкция вида Buf[1] хорошо работает и во многих других случаях. А вот интересно, есть ли у нее недостатки или ее можно слепо лепить всюду, где она применима?
ОтветитьУдалитьЕсть небольшой недостаток, но об этом - в ответе... :)
ОтветитьУдалитьУ меня попутный вопрос. Если такая "засада" со строками, то что будет при использовании динамическим массивов (type tarr = array of Integer)? Какие "подводные камни"?
ОтветитьУдалитьВыглядит адекватно. Еще бы вначале
ОтветитьУдалитьFS.Position := 0;
На первый взгляд, UniqueString() не нужен, т.к. SetLength() гарантирует уникальность строки. Однако функция, реализующая конкатенацию, в случае пустого первого аргумента вызывает функцию присваивания и получается, что Data указывает на то же, что и Buf. Т.е., по сути, вызов UniqueString() нужен ровно один раз после конкатенации. Причём, хоть UniqueString(Buf), хоть UniqueString(Data).
ОтветитьУдалить