Что нового

Работа с большими файлами

ynbIpb

Скриптер
Сообщения
399
Репутация
109
Задача: есть большой файл (который нельзя прочитать в память целиком).
1 . Необходимо из прозвольного участка файла вырезать фрагмент байтов, зная смещение и количество байтов, после чего файл уменьшится в размерах.
2. Необходимо в прозвольное место файла добавить некторое количество байтов, после чего файл увеличится в размере (тоесть не затирая имеющиеся данные, а какбы сдвигая их)
Что-то я таких функций не нашёл.
 

Yashied

Модератор
Команда форума
Глобальный модератор
Сообщения
5 379
Репутация
2 711
А таких функций нет, облом. Для того, что бы вставить что-то в файл (не в конец) нужно перезаписывать файл. И тоже самое для удалеия произвольного фрагмента.
 
Автор
ynbIpb

ynbIpb

Скриптер
Сообщения
399
Репутация
109
Таки придётся читать файл по кускам в цикле и писать его в новый файл?
Этоже получится 2 огромных файла... жуть
 

Yashied

Модератор
Команда форума
Глобальный модератор
Сообщения
5 379
Репутация
2 711
Причем, чем больше выделишь буфер для чтения, тем быстрее будет происходить запись, намного быстрее.
 
Автор
ynbIpb

ynbIpb

Скриптер
Сообщения
399
Репутация
109
Понятно...
А какой размер буфера рекомендуется? (обрабатываемый файл 312 МБ)
Теория для удаления фрагмента: известно смещение, известен размер удаляемого блока.
Читаем по куску, и пишем его в темп файл, пока не дойдём до нужного смещения. Теперь командой FileSetPos сдвигаемся с текущей позиции читаемого файла на количество равное размеру удаляемого блока. опять продолжаем читать по кускам и писать в темп. Как файл закончился, удаляем оригинал и переносим на его место файл из темпа.
А теперь вопрос по практической реализации: как мне при чтении начального куска точно остановиться на нужном смещении?
 

AZJIO

Меценат
Меценат
Сообщения
2 752
Репутация
1 149
ynbIpb
Можно математически подсчитать. Разделить смещение на величину буфера, это число в количество циклов, остаток от деления Mod( value1, value2 ) прочитать после цикла, далее читать размер блока, и следующий цикл - чтение до конца. Наверно так...
 

Yashied

Модератор
Команда форума
Глобальный модератор
Сообщения
5 379
Репутация
2 711
Это довольно простая задача, вот один из вариантов.

Код:
$Offset = ...

$BuffSize = 64 * 1024 * 1024 ; 64 MB
$Mod = Mod($Offset, $BuffSize)
$Step = Int(($Offset  - $Mod) / $BuffSize)

For $i = 1 To $Step
	; Чтение и запись в файл порции данных размером $BuffSize
	; Смещение указателя в файле на $BuffSize
Next

If $Mod Then
	; Чтение и запись в файл порции данных размером $Mod
	; Смещение указателя в файле на $Mod
EndIf

; Здесь мы остановились на блоке, который необходимо удалить


Чтение и запись в файл я рекомендую делать с помощью _WinAPI_... функций.



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

В принципе AZJIO уже ответил.
 

kaster

Мой Аватар, он лучший самый
Команда форума
Глобальный модератор
Сообщения
4 020
Репутация
622
А зачем какие-то буферы? :smile:
Если все известно - и положение удаляемого блока и его размер, то я бы сделал так:
1. Прочитать файл до удаляемого блока, записать в другой файл
2. Прочитать удаляемый блок
3. Прочитать остаток файла после удаляемого блока, записать в файл
 

AZJIO

Меценат
Меценат
Сообщения
2 752
Репутация
1 149
ynbIpb
Сильно не заморачивался, взял свой скрипт EXE2Bin.au3 и слегка подправил его
Код:
#NoTrayIcon ; скрыть иконку в трее
Opt("GUICloseOnESC", 1) ; выход по ESC

$Gui = GUICreate("EXE2Bin.au3",  300, 94, -1, -1, -1, 0x00000010)

$filemenu = GUICtrlCreateMenu ("Файл")
$folder1 = GUICtrlCreateMenuitem ("Открыть",$filemenu)
$Readme = GUICtrlCreateMenuitem ("О программе",$filemenu)
$Quit = GUICtrlCreateMenuitem ("Выход",$filemenu)

$Input1 = GUICtrlCreateLabel('', 0, 0, 300, 94)
GUICtrlSetState(-1, 136) ; скрыть поле
GUICtrlCreateLabel ("Кинь сюда файл для конвертации в тело скрипта", 10,2,280,17)
$StatusBar=GUICtrlCreateLabel (@CRLF&@CRLF&'Строка состояния', 10,23,280,57)
$offset=3240000

GUISetState ()

	While 1
		$msg = GUIGetMsg()
		Select
			Case $msg = -13
				$filename=StringRegExp(@GUI_DRAGFILE,'(^.*)\\(.*)\.(.*)$',3)
				GUICtrlSetData($StatusBar, 'Файл '&$filename[1]&'.'&$filename[2]&' принят'&@CRLF&'чтение...')
				$ScrBin='$sData  = ''0x'''&@CRLF
				$file = FileOpen(@GUI_DRAGFILE, 16)
				$size = FileGetSize(@GUI_DRAGFILE)
				$ostatok=Mod( $offset, 1024*1024 )
				$n=($offset-$ostatok)/(1024*1024)
				
MsgBox(0, 'Сообщение', $n)
				
$file1 = FileOpen(@ScriptDir&'\file.txt',18)
				
				For $i = 1 to $n
					$Bin = FileRead($file, 1024*1024)
					If @error = -1 Then ExitLoop
					FileWrite($file1, $Bin)
					Sleep(1)
				Next
					$Bin = FileRead($file, $ostatok)
					If @error = -1 Then Exit
					FileWrite($file1, $Bin)
					Sleep(1)
					$Bin = FileRead($file, 4)
					If @error = -1 Then Exit
					FileWrite($file1, '0x00000000')
					Sleep(1)
				
				While 1
					$Bin = FileRead($file, 1024*1024)
					If @error = -1 Then ExitLoop
					FileWrite($file1, $Bin)
					Sleep(1)
				WEnd
FileClose($file1)
				FileClose($file)
				
			Case $msg = $Readme
				MsgBox(0, 'Readme', '     тест.')
			Case $msg = -3 Or $msg = $Quit
				Exit
		EndSelect
	WEnd


кинь в него 5-ти мегабайтный файл и со смещением где-то на третьем мегабайте вместо 4-х байт вставлены нули. Проверил в "Beyond Compare 3", замена выполнена
 

Yashied

Модератор
Команда форума
Глобальный модератор
Сообщения
5 379
Репутация
2 711
Kaster сказал(а):
А зачем какие-то буферы? :smile:
Затем, что

ynbIpb сказал(а):
Задача: есть большой файл (который нельзя прочитать в память целиком).
Если размер всегда 312 МБ, то естественно заморачиваться нет смысла. Но ведь это не так...
 

AZJIO

Меценат
Меценат
Сообщения
2 752
Репутация
1 149
Чуть позже подправлю этот скрипт, самому идея понравилсь


Kaster
ну а если память 256 Мб, а файл 1Гб, получается как он его прочитает? Как я понял он же в памяти держит то что выполнено как FileRead.
 
Автор
ynbIpb

ynbIpb

Скриптер
Сообщения
399
Репутация
109
Ооо.. как много ответов.
Спасибо, теперь всё ясно. Буду колдовать.
Действительно я боюсь читать в память такое количество, файл может разростись и поболее, а скрипт должен работать на любой машине.
Чтоб стало немного понятнее над чем я работаю: _http://gtamodding.ru/wiki/IMG_архив
Пишу редактор игрового архива версии 1. Уже реализовал чтение архива, построение списка файлов, извлечение, добавление.
В существующих программах удаление из архива реализовано затиранием информации о нём в dir файле, а сам файл так и оставался в архиве и просто игнорировался, а я хочу сделать всё корректно.
 

SyDr

Сидра
Сообщения
651
Репутация
158
1) Переписываете данные, идущие после удаляемого блока на его место, после чего обрезаете размер файла до нужного.
2) Увеличиваете размер файла до необходимого, переносите данные, идущие после вставляемого блока в конец файла, на освободившееся место записываете необходимые данные.
_WinAPI_SetEndOfFile
 
Автор
ynbIpb

ynbIpb

Скриптер
Сообщения
399
Репутация
109
хм... интересное решение.
Но!
В этом случае всёравно нужно читать в буфер завершительную часть?
если не сложно премирчик для наглядности.
 

SyDr

Сидра
Сообщения
651
Репутация
158
OffTopic:
Честно говоря, сейчас не могу. У меня зачётная неделя идёт. А это пострашнее экзаменов...


А буфер вы в любом случае используете. Но если использовать дополнительный файл - будет ещё дольше (читаем в переменную, записываем в файл, перемещаем его). И простой примерчик здесь не получится написать.
 

Yashied

Модератор
Команда форума
Глобальный модератор
Сообщения
5 379
Репутация
2 711
Если файл жизненно важный, то я бы рекомендовал все-таки испоьзовать временный файл, а не писать в один и тот же.
 

AZJIO

Меценат
Меценат
Сообщения
2 752
Репутация
1 149
Вот что временно сделал без глубокого тестирования и проверки вариантов, когда патч-файл больше, когда смещение в конце файла, и прочие крайности.

Код:
#NoTrayIcon ; скрыть иконку в трее
Opt("GUICloseOnESC", 1) ; выход по ESC

$Gui = GUICreate("Патч файла",  420, 194, -1, -1, -1, 0x00000010)

GUICtrlCreateLabel ("используйте drag-and-drop", 220,2,160,17)
;$StatusBar=GUICtrlCreateLabel (@CRLF&@CRLF&'Строка состояния', 10,173,280,57)

$Label1 = GUICtrlCreateLabel("Файл изменяемый", 24, 10, 155, 17)
$Input1 = GUICtrlCreateInput("", 24, 27, 305, 21)
GUICtrlSetState(-1, 8)
$folder1 = GUICtrlCreateButton("Обзор...", 344, 26, 57, 23)

$Label2 = GUICtrlCreateLabel("Файл, который внедряется в файл изменяемый", 24, 60, 285, 17)
$Input2 = GUICtrlCreateInput("", 24, 77, 305, 21)
GUICtrlSetState(-1, 8)
$folder2 = GUICtrlCreateButton("Обзор...", 344, 76, 57, 23)

GUICtrlCreateLabel("Смещение:", 24, 126, 66, 17)
GUICtrlCreateLabel("байт", 250, 126, 66, 17)
$offset = GUICtrlCreateInput("3240000", 140, 123, 105, 21)

GUICtrlCreateLabel("Размер блока замены:", 24, 156, 118, 17)
GUICtrlCreateLabel("байт", 250, 156, 66, 17)
$blok = GUICtrlCreateInput("", 140, 153, 105, 21)


GUICtrlCreateLabel("Кэш:", 286, 126, 36, 17)
GUICtrlCreateLabel("Мб", 375, 126, 36, 17)
$SizeCache = GUICtrlCreateCombo ("", 316, 123, 55, 21)
GUICtrlSetData(-1,'1|10|50|150|300', '10')

$Patch = GUICtrlCreateButton("Патч", 304, 157, 77, 30)


GUISetState ()

While 1
	$msg = GUIGetMsg()
	Select
		Case $msg = -13
			If @GUI_DropID=$Input1 Then GUICtrlSetData($Input1, @GUI_DRAGFILE)
			If @GUI_DropID=$Input2 Then GUICtrlSetData($Input2, @GUI_DRAGFILE)
		Case $msg = $Patch
			$Input01=GUICtrlRead ($Input1)
			$Input02=GUICtrlRead ($Input2)
			$offset0=GUICtrlRead ($offset)
			$SizeCache0=GUICtrlRead ($SizeCache)
			$blok0=GUICtrlRead ($blok)
			$filename=StringRegExp($Input01,'(^.*)\\(.*)\.(.*)$',3)
			$file = FileOpen($Input01, 16)
			;$size = FileGetSize($Input01)
			$ostatok=Mod( $offset0, $SizeCache0*1024*1024 )
			$n=($offset0-$ostatok)/($SizeCache0*1024*1024)
			
			$fileNew = FileOpen($filename[0]&'\New_'&$filename[1]&'.'&$filename[2],18)
			
			$filePatch = FileOpen($Input02, 16)
			$textPatch = FileRead($filePatch)
			FileClose($filePatch)
			
			For $i = 1 to $n
				$Bin = FileRead($file, $SizeCache0*1024*1024)
				If @error = -1 Then ContinueLoop 2
				FileWrite($fileNew, $Bin)
				Sleep(1)
			Next
				$Bin = FileRead($file, $ostatok)
				If @error = -1 Then ContinueLoop
				FileWrite($fileNew, $Bin)
				Sleep(1)
				If $blok0<>'' And $blok0>0 And IsInt(Int($blok0)) Then $Bin = FileRead($file, $blok0)
				If @error = -1 Then ContinueLoop
				FileWrite($fileNew, $textPatch)
				Sleep(1)
			
			While 1
				$Bin = FileRead($file, $SizeCache0*1024*1024)
				If @error = -1 Then ExitLoop
				FileWrite($fileNew, $Bin)
				Sleep(1)
			WEnd
			FileClose($fileNew)
			FileClose($file)
			
				; кнопки обзор
			Case $msg = $folder1
				$folder01 = FileOpenDialog("Указать файл", @WorkingDir & "", "Любой (*.*)", 1 + 4 )
				If @error Then ContinueLoop
				GUICtrlSetData($Input1, $folder01)
			Case $msg = $folder2
				$folder02 = FileOpenDialog("Указать файл", @WorkingDir & "", "Любой (*.*)", 1 + 4 )
				If @error Then ContinueLoop
				GUICtrlSetData($Input2, $folder02)
			
		;Case $msg = $Readme
			;MsgBox(0, 'Readme', '     Утилита для патча одного файла другим.')
		Case $msg = -3
			Exit
	EndSelect
WEnd


Интересно, что вставка текста из одного файла в другой корректно работает.
 
Автор
ynbIpb

ynbIpb

Скриптер
Сообщения
399
Репутация
109
Итак, воссоздал предложение SyDr, Правильно ли я понял идею?
Создал в HEX редакторе простейший бинарный файлик размером 100 байт и разбил его на 10 блоков по 10 байт и присволил им значения. Этот образец показывает как вклинить в середину файла фрагмент байтов. Результат работы на скриншоте:


Код:
#Include <WinAPI.au3>
$binfile = @ScriptDir & "\binfile" ; тестовый бинарный файл (100 байт)
$tempfile = @ScriptDir & "\binfile_temp" ; временный файл
$offset = 30
$newdata = "0x5A5A5A5A5A5A5A5A5A5A" ; бинарные данные, которые будут вставляться
$size1 = FileGetSize ($binfile)
$o_binfile = FileOpen ($binfile, 16); открыть для чтения, бинарный режим
$o_tempfile = FileOpen ($tempfile, 2+16); открыть для записи с затиранием содержимого + бинарный режим
FileSetPos ($o_binfile, $offset, 0); встаём на офсет относительно начала файла
While 1
	$Buff = FileRead ($o_binfile, 10); читаем по 10 байт
	If @error = -1 Then ExitLoop
	FileWrite ($o_tempfile, $Buff); записываем по куску во временный файл
WEnd
FileClose ($o_binfile); закрываем оба фала
FileClose ($o_tempfile)
$o_binfile = FileOpen ($binfile, 1+16); теперь открываем основной файл для записи в конец + бинарный режим
$o_tempfile = FileOpen ($tempfile, 16); и открываем временный файл для чтения, бинарный режим
FileSetPos ($o_binfile, $offset, 0); встаём на офсет относительно начала файла
_WinAPI_SetEndOfFile($o_binfile); ставим конец файла на текущей позиции
FileWrite ($o_binfile, $newdata); записываем кусок в середину файла
While 1
	$Buff = FileRead ($o_tempfile, 10); читаем по 10 байт
	If @error = -1 Then ExitLoop
	FileWrite ($o_binfile, $Buff); записываем по куску в основной файл
WEnd
FileClose ($o_binfile); закрываем оба файла
FileClose ($o_tempfile)
$size2 = FileGetSize ($binfile)
Msgbox (0, "Отладка", "Старый размер: "&$size1&@CRLF&"Новый размер: "&$size2)


------------------
Теперь вопрос в плане ситуации удаления фрагмента
SyDr [?]
Переписываете данные, идущие после удаляемого блока на его место, после чего обрезаете размер файла до нужного.
А как отрезать лишний хвост у файла?

--------------- updated -------------
Всё дошло! Теперь отрезается хвост:
Код:
$o_binfile = _WinAPI_CreateFile($binfile, 2, 4) ; открываем файл чтоб отрезать хвост
_WinAPI_SetFilePointer($o_binfile, $size1 - $size); перемещаемся на позицию исходный размер минус количество удалённых байт
_WinAPI_SetEndOfFile($o_binfile); отмечаем, что это конец файла
_WinAPI_CloseHandle($o_binfile); закрываем файл


Что меня отталкивает перейти на WinAPI для работы с файлами, так это непонимание работы функций _WinAPI_WriteFile и _WinAPI_ReadFile они пишут\читают не чисто байты, что-то производимое DllStruct...
да и остальные параметры тоже не понимаю...
Если не затруднит, покажите реализацию на WinAPI из моего примера выше.
 
Верх