как получить реальное содержимое файла с помощью TFilestream?

я пытаюсь получить содержимое файла с помощью TFilestream:

procedure ShowFileCont(myfile : string);
var
tr : string;
fs : TFileStream;
Begin
   Fs   := TFileStream.Create(myfile, fmOpenRead or fmShareDenyNone); 
   SetLength(tr, Fs.Size);
   Fs.Read(tr[1], Fs.Size);
   Showmessage(tr); 
   Fs.Free;
end;

Делаю небольшой текстовый файл только с содержимым: aaaaaaaJ“њРЉTщЂ®8ЈЏVд"Ј¦AИaaaaaaa

  1. И сохраните этот файл (используя AkelPad) с кодом 1251 (ansi).
  2. Сохранить с кодовой страницей 65001 (UTF8).

эти файлы имеют разный размер, но содержимое одинаковое - я открыл их оба в блокноте, и они оба имеют одинаковое содержимое

Но когда я запускаю процедуру ShowFileCont, она показывает мне разные результаты:

  1. аааааааJ?ЊТ?8?В?"?А?ааааааа
  2. аааааааJ“њРЉТщЂ®8ЈЏВд"Ј¦АИааааааа

Вопросы:

  1. как получить реальное содержимое файла с помощью TFilestream?
  2. Как объяснить, что эти 2 файла имеют разный размер, но содержимое (в блокноте) одинаковое?

Дополнение: Извините, я не сказал, что использую Lazarus FPC и string = utf8string


person user2154246    schedule 21.05.2013    source источник
comment
Вы должны ознакомиться с кодировками и кодировками.   -  person OnTheFly    schedule 21.05.2013
comment
Для файла в кодировке ANSI (строка): ShowMessage(AnsiToUTF8(tr)); Подробнее о кодировках читайте здесь: Поддержка LCL Unicode   -  person Abelisto    schedule 21.05.2013
comment
TFileStream показывает вам реальный контент. Блокнот показывает интерпретированное представление этого содержимого. Ваш вопрос должен заключаться в том, как получить это интерпретируемое содержимое; o)   -  person Sir Rufo    schedule 21.05.2013
comment
спасибо за ответы.   -  person user2154246    schedule 22.05.2013
comment
Абелисто. Да, вы правы, это работает, но в некоторых случаях: для файла 1251 ShowMessage(AnsiToUTF8(tr)) - показывать правильно, но для файла UTF8 ShowMessage(AnsiToUTF8(tr)) - показывать false, но ShowMessage(tr) показывать правильно.   -  person user2154246    schedule 22.05.2013
comment
Вот почему я должен как-то определить кодовую страницу файла. А если файл будет ex. какая-нибудь кодовая страница не 1251 или utf8?   -  person user2154246    schedule 22.05.2013
comment
Вы не можете определить кодовую страницу из файла. Вам нужно знать, что такое кодовая страница.   -  person David Heffernan    schedule 22.05.2013


Ответы (2)


Почему файлы имеют разный размер?

Потому что они используют разные кодировки. Кодировка 1251 отображает каждый символ в один байт. Но UTF-8 использует переменное количество байтов для каждого символа.

Как получить истинное содержимое файла?

Вам нужно использовать тип строки, который соответствует кодировке, используемой в файле. Так, например, если содержимое закодировано в кодировке UTF-8, что является лучшим выбором, вы загружаете содержимое в строку UTF-8. Вы используете FPC в режиме, где string имеет кодировку UTF-8. В этом случае код в вопросе - это то, что вам нужно.

Загрузка файла в кодировке MBCS с кодовой страницей 1251, скажем, более сложна. Вы можете загрузить это в переменную AnsiString, и если языковой стандарт вашей системы равен 1251, любые преобразования будут выполняться правильно.

Но код будет вести себя по-другому при запуске на машине с другой локалью. И если вы хотели загрузить текст с использованием разных кодировок MBCS, например 1252, то вы не можете использовать этот подход. Вам нужно будет загрузить в массив байтов, а затем преобразовать из 1252, скажем, в UTF-8, чтобы затем сохранить этот UTF-8 в переменной string.

Для этого вы можете использовать модуль LConvEncoding. из ЛКЛ. Например, вы можете использовать CP1251ToUTF8, CP1252ToUTF8 и т. д. для преобразования MBCS в UTF-8.

Как я могу определить из файла, какая кодировка используется?

Вы не можете. Вы можете сделать предположение, которое будет точным во многих случаях. Но в общем случае определить кодировку массива байтов, который предназначен для представления текста, просто невозможно.

Иногда можно взять файл и исключить определенные кодировки. Например, не все потоки байтов являются допустимым текстом UTF-8 или UTF-16. И поэтому вы можете исключить такие файлы. Но для таких кодировок, как 1251, 1252 и т. д., допустим любой поток байтов. У вас просто нет возможности отличить 1251 закодированный поток от 1252 закодированного потока со 100% точностью.

Блок LConvEncoding имеет GuessEncoding, что звучит так, как будто это какой-то использовать.

person David Heffernan    schedule 21.05.2013
comment
Спасибо, Дэвид Хеффернан, сейчас я не проверяю ваш код, но мне кажется, что в моем случае это правильно. Но как я могу получить кодовую страницу файла? Потому что мне нужно какое-то универсальное решение, не зависящее от кодовой страницы файла. Бывший. как текстовые редакторы, блокнот, акелпад. - person user2154246; 22.05.2013
comment
Я видел - В Lazarus я не нашел класс TEncoding. - person user2154246; 22.05.2013
comment
Текстовые файлы не обязательно содержат кодовую страницу. Предполагается, что вы знаете, как кодируется текст. Универсального решения нет. - person David Heffernan; 22.05.2013
comment
Я не думаю, что в FPC есть класс TEncoding, и, насколько мне известно, в FPC сложнее преобразовать кодовую страницу ANSI MBCS в кодировку Unicode. В какой-то степени вы страдаете от того, что помечаете свой вопрос как Delphi и не можете объяснить, что на самом деле используете FPC. Я пришел к выводу, что вы использовали FPC, потому что представленные вами факты указывают на то, что string, который вы используете, - это UTF8, и поэтому вы не можете использовать Delphi. - person David Heffernan; 22.05.2013
comment
Да, я только что удалил тег Delphi. Если текстовые файлы не обязательно содержат кодовую страницу. Но как AkelPad всегда показывает кодовую страницу для любых, а не только текстовых файлов (например, doc, xls и т.д.)? - person user2154246; 22.05.2013
comment
Кодовую страницу можно угадать с достаточной точностью. Но это не защита от дурака. Если выбор сводится к UTF-8 или MBCS, вы можете сначала проверить, является ли файл допустимым UTF-8. Если нет, предположим, что это MBCS с использованием языкового стандарта системы. Но как вы могли ожидать, скажем, увидеть разницу между 1251 и 1252 годами? Ни одна программа не может этого сделать. - person David Heffernan; 22.05.2013
comment
Большое спасибо Дэвиду Хеффернану. Я тоже нашел этот модуль в директории Lazarus. да, в Lazarus есть такие функции, как systoutf8, и если GuessEncoding вернет мне cp1251, я могу применить systoutf8. Есть ли разница между utf8 и utf8bom (я нашел файлы с ними в своем каталоге)? - person user2154246; 22.05.2013
comment
UTF8ToUTF8BOM добавит спецификацию UTF-8 в начало строки. А UTF8BOMToUTF8 удаляет спецификацию. Спецификация — это метка порядка байтов. Поиск в Интернете, чтобы узнать больше. Я не уверен, что могу добавить что-то еще. Я думаю, что ответил на вопрос максимально полно. - person David Heffernan; 22.05.2013
comment
Да, я думаю, теперь я понимаю и получаю ответы на свои вопросы, спасибо! - person user2154246; 22.05.2013
comment
Интересно, как AkelPad определяет кодовую страницу... Потому что я пытаюсь сохранить файл как кодовую страницу консоли (866 для меня), а GuessEncoding определяет ее как 1251, но AkelPad показывает правильную 866.. - person user2154246; 22.05.2013

Их содержимое явно не равно. Вы сами видите, что размеры файлов разные. Вещи разного размера никогда не бывают одинаковыми.

Ваши файлы могут показаться одинаковыми в Блокноте, потому что Блокнот умеет распознавать определенные кодировки символов. Вы сохранили файл двумя разными способами. В одном из способов использовалась кодировка, присваивающая по одному байту каждому из 256 возможных значений. Другой способ использует кодировку, которая назначает от одного до шести байтов каждому из более чем 10 000 возможных значений. Для некоторых сохраненных вами символов требуется более одного байта, что объясняет, почему одна версия файла больше другой.

TFileStream не обращает на это внимания. Он просто работает с байтами. В зависимости от вашей версии Delphi ваша переменная string может учитывать или не учитывать кодировку. До Delphi 2009 string хранил один байт на символ. Начиная с Delphi 2009, string использует два байта на символ, поэтому ваш вызов SetLength неверен, и все, что после этого, бессмысленно исследовать дальше.

С одним байтом на символ ваш вызов ShowMessage не будет интерпретировать строку как кодировку UTF-8. Вместо этого он будет интерпретировать вашу строку, используя кодовую страницу вашей системы. Если вы знаете, что строка, которую вы прочитали, закодирована в UTF-8, то перед отображением вы захотите преобразовать ее в UTF-16, вызвав UTF8Decode. Это вернет WideString, и вы можете использовать любое количество функций для его отображения, например MessageBoxW. Если у вас Delphi 2009 или более поздняя версия, компилятор автоматически вставит код преобразования, если вы использовали Utf8String вместо string.

person Rob Kennedy    schedule 21.05.2013
comment
Я думаю, что в данном случае string это UTF-8, потому что это FPC - person David Heffernan; 21.05.2013
comment
Извините, я не сказал, что использую Lazarus FPC и string = utf8string - person user2154246; 22.05.2013
comment
Дэвид Хефферман: потому что это Lazarus, а showmessage — это функция GUI. Строка FPC = системная кодовая страница по умолчанию. - person Marco van de Voort; 24.05.2013