Вопросы по работе со строками

Тема в разделе "Lotus - Программирование", создана пользователем Darker, 26 авг 2010.

  1. Darker

    Darker Гость

    Наверняка поднимаю "избитую" тему.
    Пример задачи, необходимо пройтись по коллекции, достать из каждого документа данные. В конце обработки получить строку с данными из коллекции.
    Напрашивается очевидный метод обхода:
    Код (LotusScript):
    ...
    Dim s as String
    while not doc is nothing
    ...
    s=s+doc.GetitemValue("TestField")(0)
    ...
    wend
    ...
    А есть альтернативный подход: собрать массив строк, затем в конце массив объединить в строку
    Код (LotusScript):
    ...
    Dim i as long
    i=0
    while not doc is nothing
    ...
    Redim preserve s(i) as string
    s(i)=doc.GetitemValue("TestField")(0)
    i=i+1
    ...
    wend
    s1=join(s)
    ...
    Методом профилирования кода (который был в агенте), мною было замечено, что второй подход отрабатывает намного быстрее. В попытках объяснить сей факт не хочу выглядеть дилетантом, поэтому нуждаюсь в Вашем мнении об этом. (Подозреваю, что загвоздка с работой с памятью).
    В связи с этим, написал класс для работы со строками. И перед тем как, его использовать "в производстве", прошу вас выявить недостатки:
    Код (LotusScript):
    Public Class PROTOTYPE_STRING
    Private stringArray() As String                                                 'Массив строк
    Private extStringArray() As String                                              'Вспомогательный массив, на случай если превысит лимит массива или лимит строки в байтах(если включен игнор лимита)
    Private i As Integer                                                                    'Счетчик для основного массива
    Private j As Integer                                                                    'Счетчик для вспомогательного массива
    Private stringLength As Double                                              'Счетчик размера строки в байтах
    Private isToCheckLength As Boolean                                      'Флаг о необходимости проверки превышения лимита строки в байтах
    Private isToIgnoreLimit As Boolean                                          'Флаг об игнорировании превышения лимита строки в байтах
    Private isOutOfLimit As Boolean                                             'Флаг о превышении лимита строки в байтах

    Public Sub New                                                                      'Конструктор
    Truncate                                                                            'Зануляем все переменные
    Me.isToCheckLength=True                                                 'Делаем необходимым проверку превышения лимита строки в байтах
    Me.isToIgnoreLimit=False                                                    'Отказываемся об игнорирования превышения лимита строки в байтах                                                           
    End Sub

    Public Property Get ToCheckLength As Boolean
    ToCheckLength=Me.isToCheckLength
    End Property

    Public Property Set ToCheckLength As Boolean
    Me.isToCheckLength=ToCheckLength
    End Property

    Public Property Get ToIgnoreLimit As Boolean
    ToIgnoreLimit=Me.isToIgnoreLimit
    End Property

    Public Property Set ToIgnoreLimit As Boolean
    Me.isToIgnoreLimit=ToIgnoreLimit
    If Me.isToIgnoreLimit Then Me.isToCheckLength=True          'Если игнорируем превышения лимита строки в байтах, то делаем необходимым проверку превышения лимита строки в байтах
    End Property

    Public Sub Truncate
    Erase Me.stringArray                                                            'Зачищаем массив строк
    Erase Me.extStringArray                                                     'Зачищаем вспомогательный массив строк
    Me.i=0                                                                              'Зануляем счетчик массива строк
    Me.j=0                                                                              'Занулям счетчик вспомогательного массива
    Me.stringLength=0                                                               'Зануляем счетчик размера строки в байтах
    Me.isOutOfLimit=False                                                       'Предполагаем, что лимит строки в байтах не превышен
    End Sub

    Public Function Add(stringToAdd As String) As Boolean
    On Error Goto erH
    Add=True
    If Me.isToCheckLength Then                                              'Если нуждается в проверке превышения лимита строки
    Dim currentStringLength As Long
    currentStringLength=Lenb(stringToAdd)                           'Берем размер текущей строки в байтах
    Me.stringLength=Me.stringLength+currentStringLength 'Прибавляем его к основному счетчику размера строки в байтах
    If (Me.stringLength)>2147483647 Then                            'Если превысил лимит строки
    If Me.isToIgnoreLimit Then                                          'Если игнорируем ограничения,
    me.isOutOfLimit=True                   
    ThrowToExt                                                          'то перекидываем содержимое массива, в вспомогательный массив, объединяя в строку
    Me.stringLength=0                                                   'зануляем счетчик размера строки в байтах
    Else                                                                            'иначе
    Msgbox "Превышен лимит строки в 2G!"                'показываем сообщение о превышении,
    Add=False                                                              
    Exit Function                                                          
    End If
    End If
    End If
    Redim Preserve Me.stringArray(Me.i) As String                      
    Me.stringArray(Me.i)=stringToAdd                                        'Даем новому элементу массива значение текущей строки
    Me.i=Me.i+1                                                                    
    If Me.i=32767 Then ThrowToExt                                           'Если превысит лимит массива, то перекидываем содержимое массива строк в вспомогательный массив, объединяя в строку
    exF:    Exit Function
    erH:    Msgbox Error + " in "+Getthreadinfo(1)+" on line "+Cstr(Erl)
    Resume exF
    End Function

    Private Sub ThrowToExt
    Redim Preserve extStringArray(Me.j) As String
    extStringArray(Me.j)=Join(Me.stringArray,Chr(10))                   'Перекидываем содержимое массива строк в вспомогательный массив, объединяя в строку
    Me.j=Me.j+1
    Erase Me.stringArray                                                            'Зачищаем массив строк
    Me.i=0
    End Sub

    Public Function Value As Variant
    On Error Goto erH
    ThrowToExt                                                                      'Перекидываем содержимое массива строк в вспомогательный массив, объединяя в строку
    If Me.isOutOfLimit Then                                                     'Если превысил лимит строки в байтах,
    Value=Me.extStringArray                                                 'то возвращем вспомогательный(объединенный) массив строк      
    Else                                                                                    'иначе
    Value=Join(Me.extStringArray,Chr(10))                               'Возвращаем вспомогательный массив строк, объедененный в строку
    End If
    exF:    Exit Function
    erH:    Msgbox Error + " in "+Getthreadinfo(1)+" on line "+Cstr(Erl)
    Value=""       
    Resume exF
    End Function

    Sub Delete
    Truncate
    End Sub
    End Class
     
  2. nvyush

    nvyush Lotus team
    Lotus team

    Регистрация:
    22 апр 2009
    Сообщения:
    2.317
    Симпатии:
    0
    В java класс, реализующий такой подход работы со строками, называется StringBuffer. Где-то на форуме натыкался на реализацию на LS. КМК там было проще.

    http://www.nsftools.com/blog/blog-10-2007.htm#10-12-07
    Только я не совсем согласен с приведённым там графиком. Если нужно собрать относительно короткую строку из подстрок, конкатенация работает всё-таки быстрее.
     
  3. Medevic

    Medevic Что это ? :)
    Lotus team

    Регистрация:
    10 дек 2004
    Сообщения:
    3.346
    Симпатии:
    2
  4. Omh

    Omh Lotus team
    Lotus team

    Регистрация:
    4 июл 2007
    Сообщения:
    2.210
    Симпатии:
    0
    А если ты ещё не будешь редимить массив каждый раз в цикле, а объявишь сразу по количеству документов в коллекции, будет ещё быстрее :)
     
  5. nvyush

    nvyush Lotus team
    Lotus team

    Регистрация:
    22 апр 2009
    Сообщения:
    2.317
    Симпатии:
    0
    Можно и такое попробовать:
    Код (LotusScript):
    '...
    Dim session As NotesSession
    Dim stream As NotesStream
    Set stream = session.CreateStream
    Do Until doc Is Nothing
    '...
    stream.WriteText(doc.GetitemValue("TestField")(0))     
    '...
    Loop
    stream.Position = 0
    s1 = stream.ReadText
    '...
     
  6. Darker

    Darker Гость

    Omh
    Попробовал твой вариант, такой же результат по времени
    nvy
    приходило в голову, но если быть дотошным, то этот способ медленнее на 20-40%(без учета создания сессии и потока)
     
  7. VladSh

    VladSh начинающий
    Lotus team

    Регистрация:
    11 дек 2009
    Сообщения:
    1.251
    Симпатии:
    2
    Для быстрой конкатенации на больших строках очень помогает StringBuffer, в остальном - то да...
     
  8. ToxaRat

    ToxaRat Чёрный маг
    Lotus team

    Регистрация:
    6 ноя 2007
    Сообщения:
    3.047
    Симпатии:
    18
    а результаты производительности опубликуй тут ;)
     
  9. Darker

    Darker Гость

    2 минуса класса StringBuffer:
    a) при превышении лимита массива (default 32000) происходит конкатенация(при частом превышении чревато тормозами)
    б) лимит выдачи строки в 2GB

    я опубликовывал свой вариант, там это все учтено PROTOTYPE_STRING
    Если size>2Gb, возвращается массив, иначе строка
     
  10. VladSh

    VladSh начинающий
    Lotus team

    Регистрация:
    11 дек 2009
    Сообщения:
    1.251
    Симпатии:
    2
    он не превышается, т.к. это контролируется в resetArray

    Мне кажется, что это теория... Попробуйте Stream'ом открыть 50-100-метровый файл и добавить его раз 7... как раз здесь упираемся в ограничение массива 65536, а если файл больше 100 метров, то и того меньше.


    ToxaRat
    Результаты производительности давались здесь. Тестовый пример, который идёт с классом, у меня показал 10-тикратное превышение скорости.
     
  11. rinsk

    rinsk Lotus team
    Lotus team

    Регистрация:
    12 ноя 2009
    Сообщения:
    799
    Симпатии:
    78
    на какой версии?
    тут интересная инфа:
    http://blog.vinceschuurman.com/home/ndt4.n...nk=201005212259
     
  12. VladSh

    VladSh начинающий
    Lotus team

    Регистрация:
    11 дек 2009
    Сообщения:
    1.251
    Симпатии:
    2
    rinsk
    Не сервер. 8.5.2 клиент.

    По памяти говорил, ошибся.. Только что прогнал, вот такие результаты:
    Final string buffer length = 100001. Timer = ,4453125 sec.
    Final string length = 100001. Timer = 11,53906 sec.
     
  13. Darker

    Darker Гость

    VladSh

    Public Function toString () As String
    '** if this has to be compatible with Notes R5 or lower, use:
    'toString = buffer & r5Join(tempArray, "")

    '** for Notes 6 and higher, you can use:
    toString = buffer & Join(tempArray, "")
    End Function


    '** append any kind of variable that can be converted to a String
    Public Sub append (v As Variant)
    If (pos > upperB) Then
    buffer = Me.toString
    Me.resetArray(Clng(upperB) * 2)
    End If

    tempArray(pos) = Cstr(v)
    pos = pos + 1
    End Sub


    в случае если
    Код (LotusScript):
    (pos > upperB)
    , вызывается
    Код (LotusScript):
    buffer = Me.toString
    , где происходит конкатенация старого буфера с темповым массивом toString = buffer & Join(tempArray, "")

    я с таким сталкивался когда подписывал вложения
     
  14. nvyush

    nvyush Lotus team
    Lotus team

    Регистрация:
    22 апр 2009
    Сообщения:
    2.317
    Симпатии:
    0
    По тесту, приведённому там, получал аналогичные результаты. Только в том тесте в одну и ту же строку конкатенировали 100000 раз. Если же взять 10 конкатенаций и повторить опыт 10000 раз (число операций конкатенации будет таким же), время выполнения будет совсем другим, не в пользу StringBuffer. Так что я бы не стал везде и всюду вместо конкатенации использовать StringBuffer.
     
  15. Darker

    Darker Гость

    код в студию, чтобы понять
     
  16. nvyush

    nvyush Lotus team
    Lotus team

    Регистрация:
    22 апр 2009
    Сообщения:
    2.317
    Симпатии:
    0
    Искать лень. А суть примерно такая: сравнивать время выполнения не
    Код (Text):
    s = ""
    For i = 1 To 100000
    s = s + "a" 'аналогично для StringBuffer
    Next
    а
    Код (Text):
    For j = 1 To 10000
    s = ""
    For i = 1 To 10
    s = s + "a" 'аналогично для StringBuffer
    Next
    Next
     
  17. Darker

    Darker Гость

    Получается в конце StringBuffer нам выдаст ааааааааааааа......аа, а традиционный способ лишь аааааааааа,
    Как-то нечестно получается
     
  18. nvyush

    nvyush Lotus team
    Lotus team

    Регистрация:
    22 апр 2009
    Сообщения:
    2.317
    Симпатии:
    0
    Я внутренний цикл оформлял функцией. И StringBuffer и конкатенация собирали "аааааааааа", но много раз. Более того, я также пробовал "выкусывать" функционал StringBuffer'а, то есть сразу создавал массив нужного размера и потом join'ил его. Во всех случаях конкатенация выигрывала. Экспериментировал для типично собираемых сообщений msgbox.
     
  19. VladSh

    VladSh начинающий
    Lotus team

    Регистрация:
    11 дек 2009
    Сообщения:
    1.251
    Симпатии:
    2
    Дык оно так и должно работать.. разве нет? как же в строку собирать данные?
    Выигрыш StringBuffer'а в том, что Join временного массива делается быстрее, чем последовательные конкатенации. Т.о. количество конкатенаций сильно уменьшается, но они всё равно есть для сборки строки.. по другому как слить данные в строку?

    тогда почему Ваш класс заваливается с ошибкой 14 (Out of String space), а если её забить, то вываливается в Overflow?
    Вот пример вызова (только в pathname запишите путь к большому файлу):
    <div class="sp-wrap"><div class="sp-head-wrap"><div class="sp-head folded clickable">Код</div></div><div class="sp-body"><div class="sp-content">
    Код (Text):
    Dim session As New NotesSession
    Dim stream As NotesStream
    Dim pathname As String
    Dim startTime As Single
    Dim i As Long

    Set stream = session.CreateStream

    pathname = ""       'Здесь я тестил на файлах 53 и 110 Mb

    If Not stream.Open(pathname) Then
    Messagebox pathname,, "Open failed"
    Exit Sub
    End If
    fContent$ = stream.ReadText()
    stream.Close

    Dim ps As New PROTOTYPE_STRING

    startTime = Timer()
    For i = 1 To 7
    Print i
    ps.Add(fContent$)
    Next
    v = ps.Value()
    Print "Final PROTOTYPE_STRING: Time = " & (Timer() - startTime) & " sec."
    Добавлено:
    Потому что между For'ами (между For и вызовом функции) стоит s = "".
     
  20. nvyush

    nvyush Lotus team
    Lotus team

    Регистрация:
    22 апр 2009
    Сообщения:
    2.317
    Симпатии:
    0
    StringBuffer тоже сбрасывал:
     
Загрузка...

Поделиться этой страницей