| "А чё, так можно было, что ли?!?" | |
| © Уральские пельмени |
Недавно, правя баги в чужом проекте, я в методе генерации отчета в шаблон MS Excel обнаружил то, о чем сам никогда не задумывался... Среди сотни строк присвоения значений ячейкам, я заметил, что одна таблица из сотни ячеек копируется на лист MS Excel одной строкой...
Алгоритм очень прост: создаем динамический двумерный вариантный массив, заполняем его значениями и присваиваем диапазону ячеек:
На выходе имеем:var ea: TExcelApplication; v: Variant; iRow, iCol: Integer; begin ... ea := TExcelApplication.Create(nil); try ... // присваиваем значения ячейкам ea.Cells.Item[1, 2] := 'Integer'; ea.Cells.Item[1, 3].Value := 'Float'; ea.Range['D1', 'D1'].Value := 'Text'; ... // создаем двумерный массив // элементы которого могут содержать значения разных типов v := VarArrayCreate([1, 5, 1, 3], varVariant); // 5 строк, 3 столбца try // заполняем двумерный массив значениями for iRow := 1 to 5 do for iCol := 1 to 3 do Case iCol of 1: v[iRow, iCol] := iRow; // целое число 2: v[iRow, iCol] := iCol / iRow; //вещественное число 3: v[iRow, iCol] := 'Row = ' + IntToStr(iRow); // строка End; // экспортируем двумерный массив в Excel ea.Range['B2', 'D6'].Value := v; finally VarClear(v); end; ... finally ea.Free end;
Теоретически это должно работать быстрее. А практически? Сделаем небольшой тест:
program ExcelArray;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils, System.Variants, System.Win.ComObj,
Winapi.Windows, Winapi.ActiveX;
function InitEA: OleVariant;
begin
Result := CreateOleObject('Excel.Application');
Result.DisplayAlerts := False; // отключаем сообщения
Result.EnableEvents := False; // отключаем обработку событий
Result.ScreenUpdating := False; // отключаем перерисовку объектов на экране
Result.DisplayStatusBar := False; // отключаем вывод в строку статуса
Result.Workbooks.Add('');
Result.Calculation := $FFFFEFD9;//xlCalculationManual; // отключаем авторасчет
end;
procedure CloseEA(var ovEA: OleVariant);
begin
ovEA.Quit;
ovEA := Unassigned;
end;
const
ciColCount = 26; //16384;
procedure Test(const iRowCount: Integer);
var
ovEA: OleVariant;
iRow,
iCol: Integer;
v: Variant;
dStart,
dTotal: TDateTime;
procedure WriteTime(const sTitle: String);
var
dDelta: TDateTime;
begin
dDelta := Now - dStart;
dTotal := dTotal + dDelta;
WriteLn(sTitle + ': ', FormatDateTime('nn:ss.zzz', dDelta));
end;
begin
WriteLn('RowCount: ', iRowCount, ' ColCount: ', ciColCount);
try
// Экспорт по ячейкам
ovEA := InitEA;
try
dStart := Now;
for iRow := 1 to iRowCount do
for iCol := 1 to ciColCount do
ovEA.Cells[iRow, iCol] := 'Row = ' + IntToStr(iRow) +
' Col = ' + IntToStr(iCol);
WriteTime('Экспорт по ячейкам');
finally
CloseEA(ovEA);
end;
// Экспорт листа с использованием массива
WriteLn('Экспорт с использованием массива');
dTotal := 0;
dStart := Now;
v := VarArrayCreate([1, iRowCount, 1, ciColCount], varVariant);
try
for iRow := 1 to iRowCount do
for iCol := 1 to ciColCount do
v[iRow, iCol] := 'Row = '+ IntToStr(iRow)+' Col = '+IntToStr(iCol);
WriteTime('1. Создание массива');
ovEA := InitEA;
try
dStart := Now;
ovEA.Range['A1', Chr($40 + ciColCount) + IntToStr(iRowCount)] := v;
WriteTime('2. Экспорт массива');
finally
CloseEA(ovEA);
end;
finally
dStart := Now;
VarClear(v);
WriteTime('3. Удаление массива');
end;
WriteLn('Общее время: ', FormatDateTime('nn:ss.zzz', dTotal));
WriteLn;
except
on E: Exception do
Writeln(E.Message);
end;
end;
begin
Try
CoInitialize(nil);
Test(10);
Test(100);
Test(1000);
Test(10000);
Test(100000);
Finally
CoUninitialize;
End;
end.Результаты:Как видно по результатам теста, разница в скорости экспорта начинает хорошо ощущается только при экспорте 1000 строк, т.е. при экспорте нескольких десятков тысяч ячеек, и резко увеличивается при большом количестве ячеек. А значит небольшие отчеты можно создавать в MS Excel и по ячейкам, а вот для экспорта в MS Excel большого количества данных нужно обязательно использовать присваивание массива диапазону ячеек.RowCount: 10 ColCount: 26 Экспорт по ячейкам: 00:00.060 Экспорт с использованием массива 1. Создание массива: 00:00.000 2. Экспорт массива: 00:00.000 3. Удаление массива: 00:00.000 Общее время: 00:00.000 RowCount: 100 ColCount: 26 Экспорт по ячейкам: 00:00.598 Экспорт с использованием массива 1. Создание массива: 00:00.000 2. Экспорт массива: 00:00.026 3. Удаление массива: 00:00.000 Общее время: 00:00.026 RowCount: 1000 ColCount: 26 Экспорт по ячейкам: 00:06.223 Экспорт с использованием массива 1. Создание массива: 00:00.000 2. Экспорт массива: 00:00.279 3. Удаление массива: 00:00.015 Общее время: 00:00.294 RowCount: 10000 ColCount: 26 Экспорт по ячейкам: 01:04.324 Экспорт с использованием массива 1. Создание массива: 00:00.063 2. Экспорт массива: 00:03.034 3. Удаление массива: 00:00.016 Общее время: 00:03.113 RowCount: 100000 ColCount: 26 Экспорт по ячейкам: 10:21.632 Экспорт с использованием массива 1. Создание массива: 00:00.583 2. Экспорт массива: 00:32.691 3. Удаление массива: 00:00.163 Общее время: 00:33.437
Но есть и "обратная сторона медали" экспорта используя вариантный массив. Во-первых - этот способ требуется больше оперативной памяти. Хотя кого это волнует в современных реалиях? Во-вторых - существуют лимиты на количество элементов в массиве (как со стороны VarArrayCreate, так и со стороны Excel/COM). Поэтому экспортировать все возможные для MS Excel 1048576 строк по 16384 столбцов используя вариантный массив не получится.
P.S. Не знание о возможности присвоить диапазону ячеек MS Excel массива - это позор на мои седины... Но у меня есть оправдание. Прошло много лет с моего последнего использования TExcelApplication/TOleServer и CreateOleObject('Excel.Application'), т.к. если я и работал с файлами MS Excel, то только "напрямую" используя XLSReadWriteII от Axolot Data и TMS FlexCel от TMS Software.

Комментариев нет:
Отправить комментарий