I tested it with 1MB text file, length of string to find was 3 characters, made around 75000 replacements in file.
Result without buffered read (when: buffer length == length of data to find): 4165ms
Result with 4096 bytes buffer: 390ms

10 times faster


Here is the code, i added some unnecessary 'features', you can remove them if you don't like them, and i left some comments:
Code: Select all
EnableExplicit
DisableDebugger
#REPLACEONE = 1 ;make only one replacement, default is to search whole file and make multiple replacements (without this flag)
#STRINGMODE = 2 ;find string, default is hex (without this flag)
#CASESENSITIVE = 4 ;case sensitive string search and replace, default is case-insensitive (without this flag)
#FILLDATA = 8 ;if DataToReplace$ is smaller than DataToFind$ then append data to make it same length as DataToFind$, function returns error (-1) without this flag if DataToFind$ and DataToReplace$ are not same length
#STRING_CASESENSITIVE=#STRINGMODE|#CASESENSITIVE|#FILLDATA
Procedure ReplaceDataInFile(File$, DataToFind$, DataToReplace$, StartPosition=1, Flags=#FILLDATA, BufferLen=4096,*Count=0)
;RETURN: <0 if error {-4=file not found;-3=file open error;-2=not enough free memory on system;-1=wrong parameters}
; =0 if ToFind$ not found
; >0 position of last replacement
Protected _ltf=Len(DataToFind$)
Protected _ltr=Len(DataToReplace$)
Protected length = _ltf
Protected k, result
If Not Flags & #STRINGMODE ; if hex search...
If (length%2)<>0 ; ...then len must be {2,4,6,8,...}
ProcedureReturn -1 ; we don't have valid len for hex search (1,3,5,7,... is not valid)
EndIf
length/2 ; 2 hex characters means 1 byte
EndIf
If Flags & #FILLDATA
If _ltf>_ltr ;if length of DataToReplace$ is smaller than DataToFind$...
DataToReplace$+Space(_ltf-_ltr) ;...then add empty data (space characters: chr(20)) to end of DataToReplace$
Protected __ltr=_ltr
_ltr=Len(DataToReplace$)
If Not Flags & #STRINGMODE ;if we do not search for string (--> hex search)...
For k=__ltr To _ltf-1
PokeS(@DataToReplace$+k,"0") ;...then convert spaces to zeros (or instead of "0" to "F")
Next
EndIf
EndIf
EndIf
If length=0 Or StartPosition=<0 Or _ltf<>_ltr Or BufferLen=<0 Or BufferLen<length Or Flags<0
ProcedureReturn -1
EndIf
Protected *searchdata = AllocateMemory((length*2)+BufferLen)
Protected *replacedata = *searchdata+length
Protected *testdata.Character = *replacedata+length
If *searchdata=0
ProcedureReturn -2
EndIf
If Flags & #STRINGMODE ; if string search...
If Not Flags & #CASESENSITIVE
DataToFind$=UCase(DataToFind$)
EndIf
PokeS(*searchdata ,DataToFind$)
PokeS(*replacedata,DataToReplace$)
Else ; if hex search...
For k=0 To length-1
PokeB(*searchdata +k,Val("$"+PeekS(@DataToFind$ +(k*2),2)))
PokeB(*replacedata+k,Val("$"+PeekS(@DataToReplace$+(k*2),2)))
Next
EndIf
If FileSize(File$)>=0 ;OpenFile() creates file if it doesn't exist --> we don't want that so we check it before if it exists
Protected file = OpenFile(#PB_Any, File$) ;if someone deletes file before this line is executed then we create empty file :( but probability of this is so small that we simply ignore it
If file
Protected readpos = StartPosition-1
Protected thiseof = Lof(file) - length
Protected replacecount = 0
While (Not Eof(file)) And readpos <= thiseof
FileSeek(file, readpos)
Protected DataRead=ReadData(file, *testdata, BufferLen)
If Flags & #STRINGMODE
If Not Flags & #CASESENSITIVE
For k=0 To DataRead-1 ;we can't convert it to uppercase at once because it could have chr(0) in the middle so we have this loop
*testdata\c = Asc(UCase(Chr(*testdata\c))) ;*testdata\c is a little faster than PeekC()
*testdata+1 ;optionally: create structure with chr.c[0] and then use *testdata\chr[k] in previous line instead of this
Next
*testdata-DataRead ;return pointer to first byte
EndIf
EndIf
;Debug "Buffered: "+PeekS(*testdata,DataRead)
For k=0 To DataRead-length
;Debug "> Test: "+PeekS(*testdata+k,length) + " == " + PeekS(*searchdata,length)
If CompareMemory(*testdata+k, *searchdata, length)
FileSeek(file, readpos+k)
;Debug "Replacing data..."
WriteData(file, *replacedata, length)
result=readpos+k+1 ;save position
replacecount+1
If ((DataRead-length)-k)=<length
readpos+(2*length+k-DataRead-1)
EndIf
k+(length-1) ;skip comparing replaced characters in this loop
If Flags & #REPLACEONE
Break 2
EndIf
EndIf
Next
readpos + (DataRead - (length-1))
Wend
CloseFile(file)
Else
result=-3
EndIf
Else
result=-4
EndIf
FreeMemory(*searchdata)
If *Count<>0
PokeI(*Count,replacecount)
EndIf
ProcedureReturn result
EndProcedure
;*************************************************************
;*************************************************************
;*************************************************************
;here is one way of many how to use it:
Define counter.i
Select ReplaceDataInFile("c:\file.txt", "Find This Text", "New text",1,#STRING_CASESENSITIVE,4*1024,@counter)
Case -4,-3,-2,-1
MessageRequester("Error","Procedure failed!",#MB_ICONERROR)
Case 0
MessageRequester("","No replacements made.")
Default
MessageRequester("Success","Number of replacements made: "+Str(counter.i))
EndSelect
;or simpler:
ReplaceDataInFile("c:\file.dat", "Find This Text", "New text",1,#STRING_CASESENSITIVE,4*1024,@counter)
EnableDebugger
If counter>0
Debug "Success, replacements made: "+Str(counter)
EndIf
;or:
If ReplaceDataInFile("c:\file.dat", "Find This Text", "New text",1,#STRING_CASESENSITIVE,4*1024,@counter)>0
Debug "Success, replacements made: "+Str(counter)
EndIf
;more examples for hex data:
ReplaceDataInFile("c:\file.dat", "7062", "5042")
ReplaceDataInFile("c:\file.dat", "FF", "00")
ReplaceDataInFile("c:\file.dat", "5042", "7062",1,#REPLACEONE)
ReplaceDataInFile("c:\file.dat", "5042", "70",1,#REPLACEONE|#FILLDATA)
ReplaceDataInFile("c:\file.dat", "5042", "70",1,#REPLACEONE|#FILLDATA,4*1024,@counter)
;string, case-insensitive:
ReplaceDataInFile("c:\file.dat", "find", "re",1,#STRINGMODE|#FILLDATA)
Edit: fixed #REPLACEONE bug found by wallgod