• 15 апреля стартует «Курс «SQL-injection Master» ©» от команды The Codeby

    За 3 месяца вы пройдете путь от начальных навыков работы с SQL-запросами к базам данных до продвинутых техник. Научитесь находить уязвимости связанные с базами данных, и внедрять произвольный SQL-код в уязвимые приложения.

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

    Запись на курс до 25 апреля. Получить промодоступ ...

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

  • Автор темы Darker
  • Дата начала
D

Darker

Наверняка поднимаю "избитую" тему.
Пример задачи, необходимо пройтись по коллекции, достать из каждого документа данные. В конце обработки получить строку с данными из коллекции.
Напрашивается очевидный метод обхода:
Код:
...
Dim s as String
while not doc is nothing
...
s=s+doc.GetitemValue("TestField")(0)
...
wend
...
А есть альтернативный подход: собрать массив строк, затем в конце массив объединить в строку
Код:
...
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)
...
Методом профилирования кода (который был в агенте), мною было замечено, что второй подход отрабатывает намного быстрее. В попытках объяснить сей факт не хочу выглядеть дилетантом, поэтому нуждаюсь в Вашем мнении об этом. (Подозреваю, что загвоздка с работой с памятью).
В связи с этим, написал класс для работы со строками. И перед тем как, его использовать "в производстве", прошу вас выявить недостатки:
Код:
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
 
N

nvyush

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


Только я не совсем согласен с приведённым там графиком. Если нужно собрать относительно короткую строку из подстрок, конкатенация работает всё-таки быстрее.
 
N

nvyush

Можно и такое попробовать:
Код:
'...
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
'...
 
D

Darker

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

Darker

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

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

VladSh

начинающий
Lotus Team
11.12.2009
1 784
157
BIT
57
a) при превышении лимита массива (default 32000) происходит конкатенация(при частом превышении чревато тормозами)
он не превышается, т.к. это контролируется в resetArray

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


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

VladSh

начинающий
Lotus Team
11.12.2009
1 784
157
BIT
57
rinsk
Не сервер. 8.5.2 клиент.

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

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


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

Мне кажется, что это теория...
я с таким сталкивался когда подписывал вложения
 
N

nvyush

По памяти говорил, ошибся.. Только что прогнал, вот такие результаты:
Final string buffer length = 100001. Timer = ,4453125 sec.
Final string length = 100001. Timer = 11,53906 sec.
По тесту, приведённому там, получал аналогичные результаты. Только в том тесте в одну и ту же строку конкатенировали 100000 раз. Если же взять 10 конкатенаций и повторить опыт 10000 раз (число операций конкатенации будет таким же), время выполнения будет совсем другим, не в пользу StringBuffer. Так что я бы не стал везде и всюду вместо конкатенации использовать StringBuffer.
 
D

Darker

Если же взять 10 конкатенаций и повторить опыт 10000 раз (число операций конкатенации будет таким же), время выполнения будет совсем другим, не в пользу StringBuffer.
код в студию, чтобы понять
 
N

nvyush

Искать лень. А суть примерно такая: сравнивать время выполнения не
Код:
s = ""
For i = 1 To 100000
s = s + "a" 'аналогично для StringBuffer
Next
а
Код:
For j = 1 To 10000
s = ""
For i = 1 To 10
s = s + "a" 'аналогично для StringBuffer
Next
Next
 
D

Darker

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

nvyush

Получается в конце StringBuffer нам выдаст ааааааааааааа......аа, а традиционный способ лишь аааааааааа,
Как-то нечестно получается
Я внутренний цикл оформлял функцией. И StringBuffer и конкатенация собирали "аааааааааа", но много раз. Более того, я также пробовал "выкусывать" функционал StringBuffer'а, то есть сразу создавал массив нужного размера и потом join'ил его. Во всех случаях конкатенация выигрывала. Экспериментировал для типично собираемых сообщений msgbox.
 

VladSh

начинающий
Lotus Team
11.12.2009
1 784
157
BIT
57
происходит конкатенация старого буфера с темповым массивом toString = buffer & Join(tempArray, "")
Дык оно так и должно работать.. разве нет? как же в строку собирать данные?
Выигрыш 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">
Код:
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."

Добавлено:
Получается в конце StringBuffer нам выдаст ааааааааааааа......аа, а традиционный способ лишь аааааааааа,
Как-то нечестно получается
Потому что между For'ами (между For и вызовом функции) стоит s = "".
 

VladSh

начинающий
Lotus Team
11.12.2009
1 784
157
BIT
57
StringBuffer тоже сбрасывал:
<div class="sp-wrap"><div class="sp-head-wrap"><div class="sp-head folded clickable">Оригинальный пример вызова StringBuffer'а</div></div><div class="sp-body"><div class="sp-content">
Код:
Dim sb As New StringBuffer(16)
Dim s As String
Dim startTime As Single
Dim i As Long

'** this is a test of the StringBuffer class
'** REMOVE ALL THIS IF YOU USE THE CODE IN PRODUCTION
startTime = Timer()
For i = 0 To 100000
sb.append("a")
Next
Print "Final string buffer length = " & Len(sb.toString) & ". Timer = " & (Timer() - startTime) & " sec."

startTime = Timer()
For i = 0 To 100000
s = s & "a"
Next
Print "Final string length = " & Len(s) & ". Timer = " & (Timer() - startTime) & " sec."
ничего не сбрасывает.
В принтах (Len) выводится одинаковое количество элементов.
 
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!