(495) 925-0049, ITShop интернет-магазин 229-0436, Учебный Центр 925-0049
  Главная страница Карта сайта Контакты
Поиск
Вход
Регистрация
Рассылки сайта
 
 
 
 
 

Быстрая обработка данных Excel в Delphi

Источник: webdelphi
Vlad 7

Праздники ещё не закончились, работать лень, но надо как-то уже прекращать заниматься кишкоблудством и начинать работать в полную силу. Ну, а чтобы как-то себя расшевелить и начать уже работу в блоге, решил первый пост сделать простым - снова сказать несколько слов про Excel. Дело в том, что с момента выхода поста под названием "Работа с Excel в Delphi. Основы основ." прошло практически полтора года и этот пост (почему-то вопреки всем ожиданиям) очень прочно закрепился в выдаче поисковиков. Это, конечно хорошо, но этот пост (читай название) дает лишь небольшое представление о том как работать с Excel в Delphi. Никто ведь не изучает сразу квантовую механику с первого класса? Сначала учимся основам вообще - математика, физика и т.д. Так я решил поступить в начале рассказа про Excel- сначала дать общее представление, а потом по-тихоньку раскрывать тему более подробно и детально. Но поисковики немного спутали карты, подняв пост выше других про Excel. Соответственно, те из посетителей, кто уже имеют представление о работе с Excel, видя представленные в статье примеры, возмущаются по поводу того, что чтение данных в этом случае будет происходить медленно. И я не спорю, да проход по каждой ячейке листа - это жуткие тормоза. А ускорить процесс чтения можно и необходимо. Поэтому можно считать, что эта статья - расширение к основам.

За полтора года мне предлагали кучу вариантов того как ускорить чтение данных с листа Excel - от использования MSXML и других готовых библиотек до самопальных процедур и функций. Что ж, любую задачу можно решить несколькими способами. Рассмотрим несколько вариантов и определимся какой из вариантов окажется наиболее быстрым. Ну, а какой вариант окажется более удобным - это уже каждый решит для себя сам.


Чтение данных из Excel

Вначале рассмотрим вариант чтения данных использованием которого грешат те, кто только начинает свое знакомство с Excel в Delphi - чтение данных из каждой ячейки по отдельности. Тестовая процедура с таким вариантом чтения может выглядеть следующим образом:

procedure TForm16.SlowVariant;
var Rows, Cols, i,j: integer;
    WorkSheet: OLEVariant;
    d: TDateTime;
begin
  //открываем книгу
  ExcelApp.Workbooks.Open(edFile.Text);
  //получаем активный лист
  WorkSheet:=ExcelApp.ActiveWorkbook.ActiveSheet;
  //определяем количество строк и столбцов таблицы
  Rows:=WorkSheet.UsedRange.Rows.Count;
  Cols:=WorkSheet.UsedRange.Columns.Count;
 
  StringGrid1.RowCount:=Rows;
  StringGrid1.ColCount:=Cols;
 
  //засекаем время начала чтения
  d:=Now;
 
  //выводим данные в таблицу
  for I := 0 to Rows-1 do
    for j := 0 to Cols-1 do
        StringGrid1.Cells[J,I]:=WorkSheet.UsedRange.Cells[I+1,J+1].Value;
 
 Label2.Caption:='Время чтения всего листа: '+FormatDateTime('hh:mm:ss:zzz',
    Now()-d);
end;

Счётчик будет в итоге содержать время чтения и вывода в StringGrid данных. Можно было бы сделать счётчик исключительно на чтение данных с листа, но я решил не перегружать исходник лишними переменными. Если будет желание - можете переписать чуть-чуть исходник и получить "чистое" время чтения.

Для теста этого варианта был создан лист Excel, содержащий 143 строки и 142 столбца с данными, т.е. 20306 ячеек с данными. На рисунке ниже представлено значение счётчика после чтения данных:

12 секунд на чтение...а если будет 1000 строк и 1000 столбцов? Так можно и не дождаться окончания операции.

Если внимательно посмотреть на процедуру, представленную выше, то можно видеть, что в цикле мы каждый раз при каждой итерации вначале получаем диапазон, занятый данными, затем в этом диапазоне получаем определенную ячейку и только потом считываем значение в ячейке. На самом деле столько лишних операций для чтения данных с листа не требуется. Тем более, когда данные располагаются непрерывным массивом. Более выгодным в этом случае вариантом чтения будет чтение данных сразу из всего диапазона в массив.

На деле реализация этого варианты работы окажется даже проще, чем представленного выше. Смотрите сами. Вот вариант чтения данных целым диапазоном:

procedure TForm16.RangeRead;
var Rows, Cols, i,j: integer;
    WorkSheet: OLEVariant;
    FData: OLEVariant;
    d: TDateTime;
begin
  //открываем книгу
  ExcelApp.Workbooks.Open(edFile.Text);
  //получаем активный лист
  WorkSheet:=ExcelApp.ActiveWorkbook.ActiveSheet;
  //определяем количество строк и столбцов таблицы
  Rows:=WorkSheet.UsedRange.Rows.Count;
  Cols:=WorkSheet.UsedRange.Columns.Count;
 
  //считываем данные всего диапазона
  FData:=WorkSheet.UsedRange.Value;
 
  StringGrid1.RowCount:=Rows;
  StringGrid1.ColCount:=Cols;
 
//засекаем время начала чтения
  d:=Now;
 
//выводим данные в таблицу
  for I := 0 to Rows-1 do
    for j := 0 to Cols-1 do
        StringGrid1.Cells[J,I]:=FData[I+1,J+1];
 
    Label2.Caption:='Время чтения всего листа: '+FormatDateTime('hh:mm:ss:zzz',
    Now()-d);
end;

Здесь мы ввели всего одну переменную FData типа Variant. В эту переменную мы прочитали за 1 операцию весь диапазон, занятый данными. После того как диапазон прочитан FData будет содержать матрицу, каждый элемент которой будет типом данных, определенным в Excel.

Смотрим на время выполнения операции:

Как видите, прирост скорости оказался колоссальным, учитывая даже то, что в счётчик попало время обновления StringGrid'а.

Здесь было бы уместно показать и обратный метод работы с Excel, т.е. запись данных на лист Excel с использованием вариантного массива.
Запись данных в Excel

В случае, если нам необходимо записать большой объем данных на лист Excel нам необходимо провести обратную операцию, т.е. вначале создать вариантный массив, затем записать в этот массив данные после чего записать весь массив одной операцией в Excel. Для примера я написал процедуру, которая считывает большой объем данных из StringGrid и записывает эти данные на второй лист открытой книги Excel:

procedure TForm16.WriteData;
var i,j: integer;
    FData: Variant;
    Sheet,Range: Variant;
begin
//создаем вариантный массив
  FData:=VarArrayCreate([1,StringGrid1.RowCount,1,StringGrid1.ColCount],varVariant);
//заполняем массив данными из StringGrid
  for i:=1 to VarArrayHighBound(FData,1) do
    for j:=1 to VarArrayHighBound(FData,2) do
      FData[i,j]:=StringGrid1.Cells[J-1,I-1];
{активируем второй лист книги}
//открываем книгу
ExcelApp.Workbooks.Open(edFile.Text);
//активируем
Sheet:=ExcelApp.ActiveWorkBook.Sheets[2];
Sheet.Activate;
//выделяем диапазон для вставки данных
Range:=Sheet.Range[Sheet.Cells[1,1],Sheet.Cells[VarArrayHighBound(FData,1),VarArrayHighBound(FData,2)]];
//вставляем данные
Range.Value:=FData;
//показываем окно Excel
ExcelApp.Visible:=True;
end;

Здесь мы вначале создаем двумерный вариантный массив, используя метод VarArrayCreate, после чего заполняем массив данным и передаем этот массив в Excel. Обратите внимание, что при записи в Excel не используются никакие циклы - запись происходит в 2 простых действия:
выделяем диапазон, используя в качестве границ диапазона первую и последнюю ячейки
присваиваем диапазону значение из массива.

Для полноты картины ниже на рисунке представлено значение счётчика, который отсчитал время от момента создания массива до активации приложения Excel включительно:

Естественно, что с ростом объема данных будет расти и время выполнения операции. Так, например, лист, содержащий 1000 строк и 256 столбцов с данными заполнялся около 7 секунд. Если для Вас такое время неприемлемо, то представленная выше процедура может быть немного ускорена использованием пары методов VarArrayLock() и VarArrayUnLock(), но при этом следует учитывать, что матрица FData будет транспонирована.

Что ещё стоит сказать по поводу чтения/записи данных в Excel? Наверное то, что предложенные выше методы работы в обязательном порядке требуют наличия установленного Excel на том компьютере где запускается Ваша программа. В связи с этим обстоятельством может потребоваться более универсальный способ работы с Excel. Здесь, опять же, может быть несколько вариантов работы, но я покажу, а точнее укажу только на один из них - с использованием библиотека XLSReadWrite.

Про эту библиотеку мне поведал один из читателей блога в комментарии как раз-таки к посту ""Работа с Excel в Delphi. Основы основ". Чтобы лишний раз Вас не переправлять на комментарий с примером использования этой библиотеки, я с разрешения GS (ник автора кода) просто опубликую здесь уже готовые примеры использования библиотеки XLSReadWrite:

Упрощенный пример для Delphi 7
var
  IntlXls: TXLSReadWriteII2;
  I, J: Integer;
 
begin
  // создаем объект
  IntlXls := TXLSReadWriteII2.Create(nil);
 
  // название книги
  IntlXls.Sheets[0].Name := " Название моего отчета ";
  // добавляем необходимое количество строк и колонок
  IntlXls.Sheets[0].Rows.AddIfNone(0, 10000);
  IntlXls.Sheets[0].Columns.AddIfNone(0, 100);
 
  // добавляем и заносим ширины ячеек (значение в пикселях)
  for I := 0 to 99 do
    IntlXls.Sheets[0].Columns[I].PixelWidth := 150;
  // заносим высоты строк (значение здесь не в пикселях, поэтому нужно корректировать)
  for I := 0 to 9999 do
    IntlXls.Sheets[0].Rows[I].Height := 20 * 14;
 
  // настраиваем
  for J := 0 to 9999 do
    for I := 0 to 99 do
    begin
      // заносим числовое значение
      // если нужно например занести строку, то использовать AsString
      IntlXls.Sheets[0].AsFloat[I, J] := J + I / 100;
 
      // выравнивание по горизонтали (доступно chaLeft, chaCenter, chaRight)
      IntlXls.Sheets[0].Cell[I, J].HorizAlignment := chaLeft;
 
      // выравнивание по вертикали (доступно cvaTop, cvaCenter, cvaBottom)
      IntlXls.Sheets[0].Cell[I, J].VertAlignment := cvaTop;
 
      // шрифт
      IntlXls.Sheets[0].Cell[I, J].FontName := " Arial ";
      IntlXls.Sheets[0].Cell[I, J].FontSize := 12;
      IntlXls.Sheets[0].Cell[I, J].FontStyle := [];
      IntlXls.Sheets[0].Cell[I, J].FontColor := TColorToClosestXColor(clBlue);
      IntlXls.Sheets[0].Cell[I, J].Rotation := 0;
      // жирное начертание
      with IntlXls.Sheets[0].Cell[I, J] do
        FontStyle := FontStyle + [xfsBold];
      // наклонное начертание
      with IntlXls.Sheets[0].Cell[I, J] do
        FontStyle := FontStyle + [xfsItalic];
      // цвет фона
      IntlXls.Sheets[0].Cell[I, J].FillPatternForeColor :=
        TColorToClosestXColor(clYellow);
 
      // бордюр слева (аналогично и остальные бордюры)
      IntlXls.Sheets[0].Cell[I, J].BorderLeftColor :=
        TColorToClosestXColor(clBlack);
      IntlXls.Sheets[0].Cell[I, J].BorderLeftStyle := cbsThin;
 
      // объединение ячеек (здесь объединяются две ячейки по горизонтали)
      if I = 49 then
        IntlXls.Sheets[0].MergedCells.Add(I, J, I + 1, J);
    end;
 
  IntlXls.SaveToFile(" c: \ demo.xls ");
  IntlXls.Free;
end;

Полный пример работы с библиотекой:

function ExportToExcelXls(var AFileName: string): Integer;
var
  IntlXls: TXLSReadWriteII2;
  IntlCol: Integer;
  IntlRow: Integer;
  IntlMainCol: Integer;
  IntlMainRow: Integer;
begin
  // инициализируем статус
  prgrbrStatus.Max := FLinkReport.RowCount;
  prgrbrStatus.Position := 0;
  pnlStatus.Visible := TRUE;
  pnlStatus.Refresh;
  // добавлено в конце имени файла расширение ".XLS"?
  if Length(AFileName) < 5 then
    // добавляем
    AFileName := AFileName + ".xls "
  else if AnsiCompareText(Copy(AFileName, Length(AFileName)- 3, 4), ".xls ") <> 0
  then
    // добавляем
    AFileName := AFileName + ".xls ";
  // файл уже существует?
  if FileExists(AFileName) then
    // спросим
    if Application.MessageBox
      (PChar(" Файл " " + AFileName + " " уже существует.Перезаписать ? "),
      " Внимание ", MB_TASKMODAL + MB_ICONQUESTION + MB_YESNO + MB_DEFBUTTON2)
      <> IDYES then
    // выходим
    begin
      // код ошибки
      Result := UNIRPT_GENERATE_ABORT;
 
      // выходим
      Exit;
    end; // if
  // создаем объект
  IntlXls := TXLSReadWriteII2.Create(nil);
  // все делаем защищаясь
  try
    // название книги
    IntlXls.Sheets[0].Name := FLinkReport.Caption;
    // добавляем необходимое количество строк и колонок
    IntlXls.Sheets[0].Rows.AddIfNone(0, FLinkReport.Cells.RowCount + 1);
    IntlXls.Sheets[0].Columns.AddIfNone(0, FLinkReport.Cells.ColCount + 1);
    // добавляем и заносим ширины ячеек
    for IntlCol := 0 to FLinkReport.Cells.ColCount - 1 do
      IntlXls.Sheets[0].Columns[IntlCol].PixelWidth :=
        FLinkReport.ColWidths[IntlCol];
    // заносим высоты строк
    for IntlRow := 0 to FLinkReport.Cells.RowCount - 1 do
      IntlXls.Sheets[0].Rows[IntlRow].Height := FLinkReport.RowHeights
        [IntlRow] * 14;
    // проходим по всем строкам
    for IntlRow := 0 to FLinkReport.Cells.RowCount - 1 do
    begin
      // проходим по всем колонкам
      for IntlCol := 0 to FLinkReport.Cells.ColCount - 1 do
      begin
        // определяем главную ячейку
        IntlMainCol := IntlCol + FLinkReport.Cells[IntlCol, IntlRow].Range.Left;
        IntlMainRow := IntlRow + FLinkReport.Cells[IntlCol, IntlRow].Range.Top;
        // заносим оформление
        with FLinkReport.Cells[IntlMainCol, IntlMainRow] do
        begin
          // главная ячейка?
          if (IntlMainCol = IntlCol) and (IntlMainRow = IntlRow) then
          // да, заносим текст и его оформление
          begin
            // значение
            try
              // если значение - число то заносим его как число
              IntlXls.Sheets[0].AsFloat[IntlCol, IntlRow] := StrToFloat(Value);
            except
              // иначе заносим его как строку
              IntlXls.Sheets[0].AsString[IntlCol, IntlRow] := Value;
            end;
 
            // выравнивание по горизонтали
            case HorizAlign of
              haLeft:
                // выравнивание слева
                IntlXls.Sheets[0].Cell[IntlCol, IntlRow].HorizAlignment
                  := chaLeft;
              haCenter:
                // выравнивание по центру
                IntlXls.Sheets[0].Cell[IntlCol, IntlRow].HorizAlignment :=
                  chaCenter;
              haRight:
                // выравнивание справа
                IntlXls.Sheets[0].Cell[IntlCol, IntlRow].HorizAlignment
                  := chaRight;
            end; // case
            // выравнивание по вертикали
            case VertAlign of
              vaTop:
                // выравнивание сверху
                IntlXls.Sheets[0].Cell[IntlCol, IntlRow].VertAlignment
                  := cvaTop;
              vaCenter:
                // выравнивание в центре
                IntlXls.Sheets[0].Cell[IntlCol, IntlRow].VertAlignment :=
                  cvaCenter;
              vaBottom:
                // выравнивание снизу
                IntlXls.Sheets[0].Cell[IntlCol, IntlRow].VertAlignment :=
                  cvaBottom;
            end; // case
            // шрифт
            IntlXls.Sheets[0].Cell[IntlCol, IntlRow].FontName := Font.Name;
            IntlXls.Sheets[0].Cell[IntlCol, IntlRow].FontSize := Font.Size;
            IntlXls.Sheets[0].Cell[IntlCol, IntlRow].FontCharset :=
              Font.Charset;
            IntlXls.Sheets[0].Cell[IntlCol, IntlRow].FontStyle := [];
            IntlXls.Sheets[0].Cell[IntlCol, IntlRow].FontColor :=
              TColorToClosestXColor(Font.Color);
            IntlXls.Sheets[0].Cell[IntlCol, IntlRow].Rotation := Font.Angle;
            // есть жирное начертание?
            if Font.IsBold then
              // есть
              with IntlXls.Sheets[0].Cell[IntlCol, IntlRow] do
                FontStyle := FontStyle + [xfsBold];
            // есть наклонное начертание?
            if Font.IsItalic then
              // есть
              with IntlXls.Sheets[0].Cell[IntlCol, IntlRow] do
                FontStyle := FontStyle + [xfsItalic];
            // цвет фона
            if Color <> clWindow then
              // цвет задан
              IntlXls.Sheets[0].Cell[IntlCol, IntlRow].FillPatternForeColor :=
                TColorToClosestXColor(Color);
          end // if
          else
            // просто активизируем ячейку (иначе ниже невозможно добавить бордюры)
            IntlXls.Sheets[0].AsString[IntlCol, IntlRow] := ";
 
          // бордюр слева есть?
          with Borders.Left do
            if LineHeight > 0 then
            // настраиваем
            begin
              // цвет
              IntlXls.Sheets[0].Cell[IntlCol, IntlRow].BorderLeftColor :=
                TColorToClosestXColor(Color);
              // толщина
              if LineHeight = 1 then
                // тонка
                IntlXls.Sheets[0].Cell[IntlCol, IntlRow].BorderLeftStyle
                  := cbsThin
              else if LineHeight in [1, 2] then
                // средняя толщина
                IntlXls.Sheets[0].Cell[IntlCol, IntlRow].BorderLeftStyle :=
                  cbsMedium
              else
                // толстая
                IntlXls.Sheets[0].Cell[IntlCol, IntlRow].BorderLeftStyle
                  := cbsHair;
            end; // if, with
          // бордюр сверху есть?
          with Borders.Top do
            if LineHeight > 0 then
            // настраиваем
            begin
              // цвет
              IntlXls.Sheets[0].Cell[IntlCol, IntlRow].BorderTopColor :=
                TColorToClosestXColor(Color);
              // толщина
              if LineHeight = 1 then
                // тонка
                IntlXls.Sheets[0].Cell[IntlCol, IntlRow].BorderTopStyle
                  := cbsThin
              else if LineHeight in [1, 2] then
                // средняя толщина
                IntlXls.Sheets[0].Cell[IntlCol, IntlRow].BorderTopStyle :=
                  cbsMedium
              else
                // толстая
                IntlXls.Sheets[0].Cell[IntlCol, IntlRow].BorderTopStyle
                  := cbsHair;
            end; // if, with
          // бордюр справа есть?
          with Borders.Right do
            if LineHeight > 0 then
            // настраиваем
            begin
              // цвет
              IntlXls.Sheets[0].Cell[IntlCol, IntlRow].BorderRightColor :=
                TColorToClosestXColor(Color);
              // толщина
              if LineHeight = 1 then
                // тонка
                IntlXls.Sheets[0].Cell[IntlCol, IntlRow].BorderRightStyle
                  := cbsThin
              else if LineHeight in [1, 2] then
                // средняя толщина
                IntlXls.Sheets[0].Cell[IntlCol, IntlRow].BorderRightStyle :=
                  cbsMedium
              else
                // толстая
                IntlXls.Sheets[0].Cell[IntlCol, IntlRow].BorderRightStyle
                  := cbsHair;
            end; // if, with
          // бордюр снизу есть?
          with Borders.Bottom do
            if LineHeight > 0 then
            // настраиваем
            begin
              // цвет
              IntlXls.Sheets[0].Cell[IntlCol, IntlRow].BorderBottomColor :=
                TColorToClosestXColor(Color);
              // толщина
              if LineHeight = 1 then
                // тонка
                IntlXls.Sheets[0].Cell[IntlCol, IntlRow].BorderBottomStyle
                  := cbsThin
              else if LineHeight in [1, 2] then
                // средняя толщина
                IntlXls.Sheets[0].Cell[IntlCol, IntlRow].BorderBottomStyle :=
                  cbsMedium
              else
                // толстая
                IntlXls.Sheets[0].Cell[IntlCol, IntlRow].BorderBottomStyle
                  := cbsHair;
            end; // if, with
          // объединение нужно?
          if ((Range.Width > 1) or (Range.Height > 1)) and
            ((IntlMainCol = IntlCol) and (IntlMainRow = IntlRow)) then
            // объединяем
            IntlXls.Sheets[0].MergedCells.Add(IntlCol, IntlRow,
              IntlCol + Range.Width - 1, IntlRow + Range.Height - 1);
          // пользователь нажал кнопку прерывания экспорта?
          if btnCancel.Tag = 2 then
            // да, выходим
            Break;
        end; // with
      end; // for
      // обновляем статус
      prgrbrStatus.Position := prgrbrStatus.Position + 1;
      Application.ProcessMessages;
      // пользователь нажал кнопку прерывания экспорта?
      if btnCancel.Tag = 2 then
        // да, выходим
        Break;
    end; // for
    // пользователь нажал кнопку прерывания экспорта?
    if btnCancel.Tag <> 2 then
    // нет
    begin
      // на левый верхний угол
      IntlXls.Sheet[0].TopRow := 0;
      IntlXls.Sheet[0].LeftCol := 0;
      IntlXls.Sheet[0].Selection.ActiveRow := 0;
      IntlXls.Sheet[0].Selection.ActiveCol := 0;
 
      // статус
      prgrbrStatus.Position := prgrbrStatus.Max;
      Application.ProcessMessages;
      // записываем в файл
      IntlXls.FileName := AFileName;
      IntlXls.Write;
      // все успешно
      Result := UNIRPT_OK;
 
    end // if
    else
      // да
      Result := UNIRPT_GENERATE_ABORT;
 
  finally
    // освобождаем память
    IntlXls.Free;
  end; // try..finally
end; // function ExportToExcelXls

Вот такой подробный пример предоставил нам GS в своем комментарии. Спасибо ему за это. Мне же в заключении остается только добавить и подчеркнуть, что самые правильные ответы и примеры к вопросам, касающимся работы с Excel содержаться в Справке для разработчиков в самом Excel и надо только воспользоваться поиском. Например, если вам довольно часто приходится перетаскивать данные из базы данных в Excel и в работе используется ADO, то специально для таких случаев в справке рассказывается про интересный метод объекта Range под названием CopyFromRecordset, а если вам надо разукрасить свою таблицу Excel в разные цвета и установить разные виды границ ячеек, то специально для таких случаев в справке приводится подробные перечень всех перечислителей Excel'я.  В общем много чего есть - надо только этим воспользоваться и все получится. Ну, а если не получится, то милости прошу - задавайте вопросы здесь или на нашем форуме.

Ссылки по теме


 Распечатать »
 Правила публикации »
  Написать редактору 
 Рекомендовать » Дата публикации: 20.01.2012 
 

Магазин программного обеспечения   WWW.ITSHOP.RU
Delphi Professional Named User
Enterprise Connectors (1 Year term)
Quest Software. SQL Navigator for Oracle
TeeChart for .NET with source code single license
IBM RATIONAL Rose Enterprise Floating User License + Sw Subscription & Support 12 Months
 
Другие предложения...
 
Курсы обучения   WWW.ITSHOP.RU
 
Другие предложения...
 
Магазин сертификационных экзаменов   WWW.ITSHOP.RU
 
Другие предложения...
 
3D Принтеры | 3D Печать   WWW.ITSHOP.RU
 
Другие предложения...
 
Новости по теме
 
Рассылки Subscribe.ru
Информационные технологии: CASE, RAD, ERP, OLAP
Новости ITShop.ru - ПО, книги, документация, курсы обучения
Программирование на Microsoft Access
CASE-технологии
СУБД Oracle "с нуля"
Работа в Windows и новости компании Microsoft
ЕRP-Форум. Творческие дискуссии о системах автоматизации
 
Статьи по теме
 
Новинки каталога Download
 
Исходники
 
Документация
 
 



    
rambler's top100 Rambler's Top100