-
в вызываемой MultiByteToWideChar указано параметром L-1, то есть длинна без нуля в конце, а должно быть длинна с нулем, либо -1, иначе возвращает буфер без нуля в конце function UTF8_2KOLWideString( const s: AnsiString ): KOLWideString; var Buffer: PWideChar;
L: Integer;
begin
L := Length( s ) + 1;
GetMem( Buffer, L * 2 );
MultiByteToWideChar( CP_UTF8, 0, PAnsiChar( s ), L, Buffer, L );
Result := Buffer;
FreeMem( Buffer );
end;
-
думаю такой вариант будет идеальным, никаких лишних телодвижений, работа чисто по длинам function UTF8_2KOLWideString( const s: AnsiString ): KOLWideString; var Buffer: PWideChar;
L: Integer;
begin
L := Length( s );
GetMem( Buffer, L * 2 );
L := MultiByteToWideChar( CP_UTF8, 0, PAnsiChar( s ), L , Buffer, L );
SetString(Result,Buffer,L);
FreeMem( Buffer );
end;
-
Два раза перемещение данных - сначала в Buffer (по MultiByteToWideChar), потом в Result (по SetString). И да, а с чего вы взяли что размер буфера будет Length(s)*2? Правильное преобразование: function UTF8_2KOLWideString(const S: AnsiString): KOLWideString;
var
L, DestL: DWord;
begin
Result := '';
if S = '' then Exit;
L := Length(S);
DestL := MultiByteToWideChar(CP_UTF8, 0, Pointer(S), L, nil, 0);
if DestL = 0 then Exit;
SetLength(Result, DestL); MultiByteToWideChar(CP_UTF8, 0, Pointer(S), L, Pointer(Result), DestL);
end;
(Result будет null-терминированным)
-
> Netspirit
размер буфера максимальный, что очевидно не уверен что двойное перемещение и одна конвертация хуже, чем двойная конвертация и одно перемещение + лишние If и присвоение ведь получение размера эта та же конвертация только впустую
-
> размер буфера максимальный, что очевидно
Да нет, в случае UTF-8 - не очевидно. Так как один символ может иметь размер и 3 и 4 байта. А в результирующей WideString один символ может быть представлен 2-мя WideChar (4 байта). В исходном решении буфер рассчитывается максимум только на 2-байтные символы UTF-8.
> хуже, чем двойная конвертация
Производительность можно сравнить, конечно, но не думаю что при подсчете длины там идёт "полная конвертация" (получение кода Unicode символа и запись его в поданный нами буфер - потому что мы никакого буфера не подали). Идёт перебор байтов, проверка начальных битов для определения длины символа и перескок через нужное количество байт.
-
Хотя, да, не так понял. Даже если будут символы по 3-4 байта это просто повлияет на то, что будет выделен буфер в 2 раза больше чем нужно. Тогда, вероятно, можно и не делать Length(S)*2, достаточно и Length(S) - расчёт на максимальное коичество символов в исходной строке - когда все символы 1-байтные.
-
> можно и не делать Length(S)*2
... если в качестве буфера использовать тот же Result и SetLength() перед и после конвертации. function UTF8_2KOLWideString(const S: AnsiString): KOLWideString;
var
L: DWord;
begin
Result := '';
if S = '' then Exit;
L := Length(S);
SetLength(Result, L); L := MultiByteToWideChar(CP_UTF8, 0, Pointer(S), L, Pointer(Result), L);
SetLength(Result, L); end;
-
> Netspirit (29.05.18 12:04) [6]
второй SetLength это не тупо замена циферок в заголовке строки, а создание новой строки и копирование туда текущей до заданной длинны :)
-
Конечно. 2 перемещения, как не крути.
-
Вот пример теста производительности, если кому интересно:
program ConvBench;
{$APPTYPE CONSOLE}
uses Windows, SysUtils;
function UTF8ToWStr1(const S: AnsiString): WideString; var Buffer: PWideChar; L: Integer; begin L := Length(S); GetMem(Buffer, L * 2); L := MultiByteToWideChar(CP_UTF8, 0, PAnsiChar(S), L, Buffer, L); SetString(Result, Buffer, L); FreeMem(Buffer); end;
function UTF8ToWStr2(const S: AnsiString): WideString; var L: DWord; begin Result := ''; if S = '' then Exit; L := Length(S);
SetLength(Result, L); L := MultiByteToWideChar(CP_UTF8, 0, Pointer(S), L, Pointer(Result), L); SetLength(Result, L); end;
function UTF8ToWStr3(const S: AnsiString): WideString; var L, DestL: DWord; begin Result := ''; if S = '' then Exit; L := Length(S);
DestL := MultiByteToWideChar(CP_UTF8, 0, Pointer(S), L, nil, 0); if DestL = 0 then Exit;
SetLength(Result, DestL); MultiByteToWideChar(CP_UTF8, 0, Pointer(S), L, Pointer(Result), DestL); end;
const SAMPLE_SIZE = 30*1024*1024; CYCLES = 100;
procedure RunBenchmark; var AStr: AnsiString; WStr: WideString; T1, T2, T3: DWORD; I: Integer; begin //Заполняем образец символом 'Э' в UTF-8 ($D0 $AD) SetLength(AStr, SAMPLE_SIZE);
I := 1; while I < SAMPLE_SIZE do begin AStr[I] := #$D0; Inc(I); AStr[I] := #$AD; Inc(I); end;
Writeln('Benchmark started. Please, wait...');
T1 := GetTickCount; for I := 1 to CYCLES do begin WStr := UTF8ToWStr1(AStr); end;
T2 := GetTickCount; T1 := T2 - T1;
for I := 1 to CYCLES do begin WStr := UTF8ToWStr2(AStr); end; T3 := GetTickCount; T2 := T3 - T2;
for I := 1 to CYCLES do begin WStr := UTF8ToWStr3(AStr); end; T3 := GetTickCount - T3;
Writeln('Benchmark finished.'); Writeln('Bytes processed: ', FloatToStr(SAMPLE_SIZE / 1024 / 1024 * CYCLES), ' MBytes'); Writeln(''); Writeln('Results'); Writeln('UTF8ToWStr1: '+ IntToStr(T1)); Writeln('UTF8ToWStr2: '+ IntToStr(T2)); Writeln('UTF8ToWStr3: '+ IntToStr(T3));
Writeln(''); Writeln('Press "Enter" to close.');
Readln(AStr); end;
begin RunBenchmark; end.
-
> Netspirit:= красавчик!
-
Спасибо ;-)
По результатам теста - мое первое предложение с предварительным определением длины результата (дважды вызов MultiByteToWideChar, функция UTF8ToWStr3) на Windows XP где-то в 2 раза медленнее, чем существующие варианты. На Windows 7 результаты практически идентичны. Может, зависит не от ОС, а от оборудования, но на Windows XP было тоже достаточно мощное железо, хоть и устаревшее. Функция UTF8ToWStr2() немного обгоняет UTF8ToWStr1(), так что можно остановиться на ней. Если есть необходимость экономить память при работе с большими строками UTF-8, тогда есть смысл использовать UTF8ToWStr3().
-
Тогда ввести в КОЛ варианты под условную компиляцию - $UTF8Convert_LOWMEM для UTF8ToWStr (для 3) и UTF8ToWStr (для 2) по дефолту.
|