Что нового

Ограничение по работе с ini файлами

gregaz

AutoIT Гуру
Сообщения
1,166
Репутация
299
AZJIO
На мой взгляд не стоит заморачиваться на регулярных выражениях.
Оно будет намного медленнее и время сильно зависит от размера файла и даже размера его элементов.
!!!!!!!! А еще больше от размера секции ; !!!!!!!!!!!!!!!!!!
Я сечас протестировал его на ИНИ с длинными(у меня все такие) элементами и получил время даже больше,чем у INIString
порядка 15 ms,
тогда как у INIString - 13ms,
у IniVirtual 0.15ms.
В то же время на длинном ИНИ с короткими элементами Wincmd.ini (Total Comander) размерос 22 kb 800 эл-тов :
1.2ms RegExp
6.5ms INIString
0.25ms IniVirtual

Честно говоря мне понравилась идея IniVirtual , хотя вначале и не представлял ее возможности.
Быстра, проста для выполнения различных модификаций .
Например мне понадобилась ф-ия RenameKey, так я ее легко получаю.
Для записи легко получить информацию о необходимости обнуления файла или достаточности записи в конец файла, что немаловажно для скорости.
А то, что занимет время на запись, так и INIString -занимала.
Я ее буду встраивать в свой проект вместо INIString. Конечно она еще сыровата. Надо скорректировать выходные данные, ошибки и т.д. Так,что спасибо. :beer:
 

AZJIO

Меценат
Меценат
Сообщения
2,892
Репутация
1,196
gregaz [?]
На мой взгляд не стоит заморачиваться на регулярных выражениях.
Оно будет намного медленнее и время сильно зависит от размера файла и даже размера его элементов.
!!!!!!!! А еще больше от размера секции ; !!!!!!!!!!!!!!!!!!
Я сечас протестировал его на ИНИ с длинными(у меня все такие) элементами и получил время даже больше,чем у INIString
порядка 15 ms,
тогда как у INIString - 13ms,
у IniVirtual 0.15ms.
1. Почему я говорил что IniVirtual с инициализацией в массив будет делать первый запрос дольше чем просто запрос через рег.выр? Потому что просто рег.выр. с ключём 1 идёт до первого совпадения и не читает данные до конца, в то время как инициализация в массив читает с помощью рег.выр. до конца файла и тратит время на формирование массивов. То есть разовый запрос быстрее при условии что надо тут же сохранить. То есть плюс всё же есть и второй плюс - при изменении параметра модифицируется только значение параметра, в то время как сохранение массива в текущем варианте обнулит все комментарии в ini-файле.

2. На счёт INIString, я пока не знаю почему она быстрее, возможно в ней нет игнорирования спецсиволов, но теоритически она не должна быть быстрее. Если судить по понятию получения результата с наименьшим количесвом ходов, то регулярное выражение именно и осуществляет этот метод. То есть чтение файла не избежать, так как оно является критерием совпадения. В данном случае рег. выр. читает файл с начала до первого совпадения, ну какой алгоритм может быть быстрее? Сейчас проверил, в INIString параметр не прочитался при наличии пробелов вокруг "=".
 

madmasles

Модератор
Глобальный модератор
Сообщения
7,790
Репутация
2,322
AZJIO,
Пример с вложенными объектами Scripting.Dictionary.
Код:
;#include <Array.au3>

Opt('MustDeclareVars', 1)

Global $iRand = Random(5, 50, 1), $aTmp[1], $aSections[$iRand][2], $oDict, $aKeys

For $i = 0 To UBound($aSections) - 1
	$aSections[$i][0] = 'Section' & $i + 1
	$iRand = Random(5, 50, 1)
	ReDim $aTmp[$iRand][2]
	For $j = 0 To $iRand - 1
		$aTmp[$j][0] = 'Key' & $j + 1
		$aTmp[$j][1] = 'Value' & Random(10000, 10000000, 1)
	Next
	$aSections[$i][1] = $aTmp
Next
$aTmp = 0
#cs
	_ArrayDisplay($aSections, 'All')
	For $i = 0 To UBound($aSections) - 1
	_ArrayDisplay($aSections[$i][1], $aSections[$i][0] & '(' & UBound($aSections) & ')')
	Next
#ce

$oDict = ObjCreate('Scripting.Dictionary')
$oDict.CompareMode = 0

For $i = 0 To UBound($aSections) - 1
	$oDict.add($aSections[$i][0], ObjCreate('Scripting.Dictionary'))
	$oDict.item($aSections[$i][0] ).CompareMode = 0
	$aTmp = $aSections[$i][1]
	For $j = 0 To UBound($aTmp) - 1
		$oDict.item($aSections[$i][0] ).add($aTmp[$j][0], $aTmp[$j][1])
	Next
Next
$aTmp = 0

$aKeys = $oDict.keys
;_ArrayDisplay($aKeys, $oDict.count)

For $i = 0 To $oDict.count - 1 ;UBound($aKeys) - 1
	$aTmp = $oDict.item($aKeys[$i] ).keys
	ConsoleWrite('[' & $aKeys[$i] & ']' & @LF)
	For $j = 0 To $oDict.item($aKeys[$i] ).count - 1 ;UBound($aTmp) - 1
		ConsoleWrite($aTmp[$j] & ' = ' & $oDict.item($aKeys[$i] ).item($aTmp[$j]) & @LF)
	Next
Next
ConsoleWrite('-----' & @LF)

Local $sSect = 'Section' & Random(5, 20, 1), $sKey = 'Key' & Random(5, 20, 1), $iStart = TimerInit()

If $oDict.Exists($sSect) Then
	If $oDict.item($sSect ).Exists($sKey) Then
		ConsoleWrite('[' & $sSect & ']' & @TAB & $sKey & ' = ' & $oDict.item($sSect ).item($sKey) & @LF)
	Else
		ConsoleWrite('No key: ' & $sKey & ' in section: ' & '[' & $sSect & ']' & @LF)
	EndIf
Else
	ConsoleWrite('No section: ' & $sSect & @LF)
EndIf
ConsoleWrite(StringFormat('Time: %.2f msec', TimerDiff($iStart)) & @LF)
Минус - не должно быть в одной секции ключей с одинаковыми названиями.
 

CreatoR

Must AutoIt!
Команда форума
Администратор
Сообщения
8,673
Репутация
2,486
madmasles [?]
Минус - не должно быть в одной секции ключей с одинаковыми названиями.
Это не минус, т.к специфика ini-файла не предусматривает дубли ключей.
 

AZJIO

Меценат
Меценат
Сообщения
2,892
Репутация
1,196
gregaz
Для _IniVirtual с массивами добавил:
Добавил игнорирование обрамляющих кавычек.
_IniVirtual_Write - при остутсвии создаёт теперь.
В общем я решил поведение сделать в точности как у нативных функций, так проще потом с UDF _Setting объединить.

madmasles
Ок, кстати, когда свой пример тестил с объектами, доступ быстрее, а создание объекта дольше в 2 раза.
 

AZJIO

Меценат
Меценат
Сообщения
2,892
Репутация
1,196
gregaz
Форматы вывода сделал, перекачай. Теперь можно критиковать.
Единственное не понял фразу в IniRenameSection
может установить значение @error, если раздел не может быть перезаписан (только при flag = 0).
с flag = 0 по описанию не перезаписывать, поэтому не понятно как он не может быть перезаписан. В англ. варианте тоже самое.
 

gregaz

AutoIT Гуру
Сообщения
1,166
Репутация
299
AZJIO [?]
с flag = 0 по описанию не перезаписывать, поэтому не понятно как он не может быть перезаписан. В англ. варианте тоже самое.
Малопонятно вообще.

По моему они хотели сказать :
возвращает значение @error=1 , если переименование не выполнено , т.к. раздел уже существует (только при flag = 0).
 

AZJIO

Меценат
Меценат
Сообщения
2,892
Репутация
1,196
madmasles
Что-то вложенные объекты у меня не работают, может глаз замылился.

Код:
#include <Array.au3>

$o_Mem_Ini = _IniVirtual_Initial(FileRead(@ScriptDir & "\Test.ini"))
$sKey = _IniVirtual_Read($o_Mem_Ini, "boot loader", "timeout", "NotFound")
MsgBox(4096, 'Результат', $sKey)

Func _IniVirtual_Initial($sText)
	Local $oTmp
	$sText = StringRegExpReplace($sText, '(?s)\h*(\r\n)\h*', '\1') ; Удаление пробельных символов в начале и в конце каждой строки. (упрощает слудующее рег.выр. и в параметрах лишнее)
	Local $aSection = StringRegExp($sText, '(?s)(?:\r\n|\A)\[\h*(.*?)\h*\]\r\n(.*?)(?=\r\n\[\h*.*?\h*\]\r\n|\z)', 3)
	Local $oMainINI= ObjCreate('Scripting.Dictionary')
	$oMainINI.CompareMode = 0

	For $i = 0 To UBound($aSection) - 1 Step 2
		If Not $oMainINI.Exists($aSection[$i]) Then
			$oTmp = ObjCreate('Scripting.Dictionary') ; используем временную переменную для удобства использования в цикле
			$oTmp.CompareMode = 0
			
			$aKeyVal = StringRegExp($aSection[$i], '(?m)^(.+?)\h*=\h*(["'']?)(.*?)\2\r?$', 3) ; учитывает пробелы между элементами и обрамление кавычками
			; Заполняем вложенные объект

			For $j = 0 To UBound($aKeyVal) - 1 Step 3
				If Not $oTmp.Exists($aKeyVal[$j]) Then ; если не существует ключ объекта, тогда
					$oTmp.Add($aKeyVal[$j], $aKeyVal[$j + 2]) ; добавляем ключ объекта
				EndIf
			Next

			$oMainINI.Add($aSection[$i], $oTmp) ; Добавляем объект как значение, где ключ является имя секции
		EndIf
	Next

	Return $oMainINI
EndFunc   ;==>_IniVirtual_Initial

Func _IniVirtual_Read($oMainINI, $sSection, $sKey, $sDefault = '')
	If $oMainINI.Exists($sSection) Then ; если существует ключ $sSection в объекте, тогда
		$oValue = $oMainINI.Item($sSection) ; читаем значение ключа $sSection, которое является вложенным обектом
	Else ; иначе
		Return $sDefault ;значение по умолчанию
	EndIf
	; MsgBox(0, 'Сообщение', VarGetType($oValue))
	$Array = $oValue.Keys() ; тест - извлекаем все ключи из объекта $oValue
	MsgBox(0, 'Сообщение', UBound($Array))
	_ArrayDisplay($Array, 'Array')
	If $oValue.Exists($sKey) Then
		Return $oValue.Item($sKey) ; читает значение
	Else
		Return $sDefault
	EndIf
EndFunc   ;==>_IniVirtual_Read

Func _IniVirtual_ReadSectionNames($oMainINI) ; извлечение корневых ключей без проблем
	$aSection = $oMainINI.Keys()
	Return $aSection
EndFunc


gregaz
По моему они хотели сказать :
да я уже понял, перечитав несколько раз.
 

gregaz

AutoIT Гуру
Сообщения
1,166
Репутация
299
AZJIO [?]
В общем я решил поведение сделать в точности как у нативных функций

По поводу ф-ии : _IniVirtual_RenameSection
Имеются различия (флаг=1) при существующей секции :
Здесь местонахождение раздела остается соответствующим месту старого раздела
В штатной ф-ии его местонахождение соответствует месту найденного раздела.
 

AZJIO

Меценат
Меценат
Сообщения
2,892
Репутация
1,196
gregaz
Обновил.
Объявил локальными переменные, проверив все.
 

gregaz

AutoIT Гуру
Сообщения
1,166
Репутация
299
Во всех ф-иях использовано
Код:
SetError(1)

вместо
Код:
SetError(1, 0, 0)


возвращаемое значение будет тоже 1 , а не 0 ???
 

AZJIO

Меценат
Меценат
Сообщения
2,892
Репутация
1,196
gregaz
Обновил, незнал, о таком поведении и справке это не документировано.
И пару переменных объявил локальными.
 

gregaz

AutoIT Гуру
Сообщения
1,166
Репутация
299
Кажется так быстрее :
Код:
Func _IniVirtual_RenameSection_1(ByRef $aSection2D, $sSection, $sNewName, $flag = 0)
		Local $i = _ArraySearch($aSection2D, $sSection, 1, 0, 0, 0, 1, 0)
        If @error Then Return SetError(1) ; если секции для переименования не существует, то выход с @error
		Local  $j = _ArraySearch($aSection2D, $sNewName, 1, 0, 0, 0, 1, 0)
        If @error Then
			$aSection2D[$i][0] = $sNewName ; если новый раздел не существует, то просто меняем имя раздела
        Else
			If $flag Then
				$aSection2D[$j][1] = $aSection2D[$i][1] ; если перезапись, то копируем старый массив в позицию нового
				;_IniVirtual_Delete($aSection2D, $sSection) ; а старый удаляем
				_ArrayDelete($aSection2D, $i)
				$aSection2D[0][0] -=1
			Else
				
				Return SetError(1, 0, 0)
			EndIf
        EndIf
        Return 1
EndFunc   ;==>_IniVirtual_RenameSection


Если подходит , то и _IniVirtual_Delete можно упростить Здесь выгоды во времени нет
 

AZJIO

Меценат
Меценат
Сообщения
2,892
Репутация
1,196
gregaz
Да, использовал _ArrayDelete, в том числе и в _IniVirtual_Delete, хотя в ней уже прироста скорости не будет так как проверок больше.
 

gregaz

AutoIT Гуру
Сообщения
1,166
Репутация
299
А эта еще быстрее :

Код:
Func _IniVirtual_RenameSection_2(ByRef $aSection2D, $sSection, $sNewName, $flag = 0)
        Local $i = _ArraySearch($aSection2D, $sSection, 1, 0, 0, 0, 1, 0)
        If @error Then Return SetError(1) ; если секции для переименования не существует, то выход с @error
        Local  $j = _ArraySearch($aSection2D, $sNewName, 1, 0, 0, 0, 1, 0)
        If @error Then
            $aSection2D[$i][0] = $sNewName ; если новый раздел не существует, то просто меняем имя раздела
        Else
            If $flag Then
                $aSection2D[$j][1] = $aSection2D[$i][1] ; если перезапись, то копируем старый массив в позицию нового
                ;_IniVirtual_Delete($aSection2D, $sSection) ; а старый удаляем
                ;_ArrayDelete($aSection2D, $i)
                ;$aSection2D[0][0] -=1
                For $j = $i To $aSection2D[0][0] - 1
                    $aSection2D[$j][0] = $aSection2D[$j + 1][0]
                    $aSection2D[$j][1] = $aSection2D[$j + 1][1]
                Next
                ReDim $aSection2D[$aSection2D[0][0]][2]
                $aSection2D[0][0] -= 1
            Else
                
                Return SetError(1, 0, 0)
            EndIf
        EndIf
        Return 1
EndFunc   ;==>_IniVirtual_RenameSection



Исключается лишний поиск в массиве ф-ии _IniVirtual_Delete
 

AZJIO

Меценат
Меценат
Сообщения
2,892
Репутация
1,196
gregaz
Ещё нестандартное поведение: в IniVirtual при пустых данных что ключей в секции, что секций в ini-файле, всегда возвращается пустой массив с первым элементом = 0, что упрощает работу, не требуется при каждом обращении делать проверку, является ли это массивом. Циклы тоже не вызывают ошибку, так как с 1 к 0 не делается ни одного шага цикла. В нативных ini-функциях возвращается 0, и не является массивом.


Добавлено:
Сообщение автоматически объединено:

gregaz
gregaz сказал(а):
А эта еще быстрее :
Так может просто _ArrayDelete2D сделать, одну на всех? Принцип тот же только не будет проверок, что это массив и проверки что он двумерный и проверки что индекс не выходит за пределы диапазона и т.д. Потому что в теории поиск по массиву 2D и _ArraySearch за пределы диапазона не выйдет.
 

gregaz

AutoIT Гуру
Сообщения
1,166
Репутация
299
А эта измененная ф-ия работает быстрее до 2-храз :
Максимальная экономия : при удалении секций, находящихся в начале файла и удалении параметра в начале секции. Особенно заметно при большом кол-ве секций или параметров.

Func _IniVirtual_DeleteEx(ByRef $aSection2D, $sSection, $sKey = Default)
Local $iEnd, $iStep
Local $i = _ArraySearch($aSection2D, $sSection, 1, 0, 0, 0, 1, 0)
If @error Then Return SetError(0, 1, 1) ; отсутствие раздела/ключа не является ошибкой, но устанавливаем @extended
If $sKey = Default Then
If $i < ($aSection2D[0][0] - 1)/2 Then
Dim $iEnd=1, $iStep=-1
Else
Dim $iEnd=$aSection2D[0][0] - 1, $iStep=1
EndIf
For $j = $i To $iEnd Step $iStep
$aSection2D[$j][0] = $aSection2D[$j + 1][0]
$aSection2D[$j][1] = $aSection2D[$j + 1][1]
Next
ReDim $aSection2D[$aSection2D[0][0]][2]
$aSection2D[0][0] -= 1
Else
Local $aKey = $aSection2D[$i][1]
Local $j = _ArraySearch($aKey, $sKey, 1, 0, 0, 0, 1, 0)
If @error Then Return SetError(2, 0 0)
If $i < ($aKey[0][0] - 1)/2 Then
Dim $iEnd=1, $iStep=-1
Else
Dim $iEnd= $aKey[0][0] - 1, $iStep=1
EndIf
For $z = $j To $iEnd Step $iStep
$aKey[$z][0] = $aKey[$z + 1][0]
$aKey[$z][1] = $aKey[$z + 1][1]
Next
ReDim $aKey[$aKey[0][0]][2]
$aKey[0][0] -= 1
$aSection2D[$i][1] = $aKey

EndIf
Return 1
EndFunc




Добавлено:
Сообщение автоматически объединено:

AZJIO [?]
Так может просто _ArrayDelete2D сделать, одну на всех?

Можно и так. Будет как внутренняя ф-ия .
 
Верх