Skulle vilja att min VB applikation bytte ut de två sista raderna i en textfil mot två nya rader. Hur kommer man åt just de två sista raderna? Tja du.... Som vanligt finns det skitmånga sätt att fixa problemet på.... Hej Hej igen SvenPon.... Undrar om du noterade följande text i min lösning... Igen Igen Får väl anta utmaningen... :O) Hej Tack så mycket. Försökte mitt bästa. Jaha Andreas hann före denna gång.... Använder API anropet för att minska filen om de infogade raderna är mindre än de tidigare. Klarar din funktion det? Tja... Det blir ju inte så noga mätt med gettickcount(), vi säger att det är dött lopp. Hej Tack för upplysningen. Kände inte till det. Jag var inte ute efter att optimera något i exemplet.... Apropå SvenPon & det där med GetTickCount() IgenByta ut textrader i textfil?
Sv: Byta ut textrader i textfil?
Är det en stor fil är inte följande sätt det bästa..... Men för mindre filer duger det så himla bra så.....
============================================
Private Sub Command1_Click()
Dim path As String
Dim file As String
Dim fil As String
Dim filRad() As String
Dim fNr As Integer
path = "c:\minmapp\"
file = "minfil.txt"
If Dir(path & file) <> "" Then
fNr = FreeFile()
Open path & file For Binary Access Read As #fNr
fil = Space$(LOF(fNr))
Get #fNr, , fil
Close #fNr
filRad() = Split(fil, vbCrLf, -1, vbTextCompare)
filRad(UBound(filRad()) - 2) = "Ersättningsrad 1" 'Näst sista raden
filRad(UBound(filRad()) - 1) = "Ersättningsrad 2" 'Sista raden
Open "c:\test.txt" For Binary Access Write As #fNr
Put #fNr, , Join(filRad(), vbCrLf)
Close #fNr
End If
End Sub
============================================
Lycka till...
/peterh <=> hpeterSv: Byta ut textrader i textfil?
>Hur kommer man åt just de två sista raderna?
Som sagt det finns olika sätt att lösa problemmet
Så här löser jag det
Option Explicit
Private Sub Command1_Click()
Call SistaRader
End Sub
Private Sub SistaRader()
Dim dummy As String * 1
Dim rader As Long, i As Long, tmpStr As String
Dim FileNum As Long, FileNum2 As Long
FileNum = FreeFile
Open "Test.txt" For Input As FileNum
FileNum2 = FreeFile
Open "Test.###" For Append As FileNum2
Do Until EOF(FileNum)
'Ta reda på antal rader
Line Input #FileNum, dummy
rader = rader + 1
Loop
Close #FileNum
FileNum = FreeFile
Open "Test.txt" For Input As FileNum
For i = 1 To rader - 2
Line Input #FileNum, tmpStr
Print #FileNum2, tmpStr
Next ' i
Print #FileNum2, "Min utbytes rad 1"
Print #FileNum2, "Min utbytes rad 2"
Close #FileNum
Close #FileNum2
Kill "Test.txt"
FileCopy "Test.###", "Test.txt"
Kill "Test.###"
End Sub
Testa och kommentera gärna
Mvh
SvenSv: Byta ut textrader i textfil?
Eftersom vi har disskuterat optimering så roade
jag mig med att göra en tidskontroll på "hpeters" lösning
och min
Resultat : SvenPon : s 3.18 sek
hpeter : s 5.60 sek
dvs skillnad på 45 %
Båda löste problemmet men Obs !
om textfilen:ens två sista rader är blankrader
kan resultat se förvirrande ut
mvh
SvenSv: Byta ut textrader i textfil?
> Är det en stor fil är inte följande sätt det bästa..... Men för mindre filer duger det så himla bra så.....
Det är inte så smart att allokera minne (sträng, dynamiskt för att lösa problemet)
Men som sagt för små filer funkar det finfint.
/peterhSv: Byta ut textrader i textfil?
> Är det en stor fil är inte följande sätt det bästa.....
Hur vet du att jag kollat mot stor fil ???
DSSv: Byta ut textrader i textfil?
> Dim fNr As Integer
En liten detalj
I 32 bits system skall du i princip aldrig deklarera Integer
det bytyder att processorn får göra några extra onödiga
runder för att vaska fram en Integer
DSSv: Byta ut textrader i textfil?
Liten beskrivning:
Fuskar ju lite med api anrop.
Söker enbart på tecknet cr (13). Har sina för och nackdelar.
Tar även med tomma rader från slutet.
Men den är betydligt mycket snabbare än tidigare två exempel i vilket hela filen läses och skrivs om. Vilket inte gör så mycket med små filer.
Men om filerna är över 1MB går det inte att jämföra funktionernas hastighet.
Desutom är min funktion lite mer dynamisk efter som man kan ang en eller flera rader.
Ex:
<code>
ReplaceLinesRev "c:\Test.txt","Sista raden"
ReplaceLinesRev "c:\Test.txt","Näst sista raden","Sista raden"
ReplaceLinesRev "c:\Test.txt","Raden innan dess...", "Näst sista raden","Sista raden"
</code>
osv...
Som sagt om man kan acceptera bristerna i min funktion så duger den nog...
Mitt bidrag:
<code>
Private Const FILE_BEGIN = 0
Private Const OPEN_EXISTING = 3
Private Const INVALID_HANDLE_VALUE = -1
Private Const GENERIC_WRITE = &H40000000
Private Declare Function CreateFile Lib "kernel32" Alias "CreateFileA" (ByVal lpFileName As String, ByVal dwDesiredAccess As Long, ByVal dwShareMode As Long, lpSecurityAttributes As Any, ByVal dwCreationDisposition As Long, ByVal dwFlagsAndAttributes As Long, ByVal hTemplateFile As Long) As Long
Private Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
Private Declare Function SetFilePointer Lib "kernel32" (ByVal hFile As Long, ByVal lDistanceToMove As Long, lpDistanceToMoveHigh As Long, ByVal dwMoveMethod As Long) As Long
Private Declare Function SetEndOfFile Lib "kernel32" (ByVal hFile As Long) As Long
Public Function SetEOF(FileName As String, EOF As Long) As Boolean
Dim hFile As Long
Dim lReturn As Long
hFile = CreateFile(FileName, GENERIC_WRITE, 0&, ByVal 0&, OPEN_EXISTING, 0&, 0&)
If hFile = INVALID_HANDLE_VALUE Then
SetEOF = False
Else
lReturn = SetFilePointer(hFile, EOF, 0&, FILE_BEGIN)
If lReturn = -1& Then
SetEOF = False
Else
SetEOF = SetEndOfFile(hFile)
End If
CloseHandle hFile
End If
End Function
Public Function ReplaceLinesRev(FileName As String, ParamArray Lines() As Variant)
Dim FileNo As Integer
Dim Location As Long
Dim Char As Byte
Dim Index As Integer
Dim Count As Integer
FileNo = FreeFile()
Open FileName For Binary Access Read Write As #FileNo
Location = LOF(FileNo)
Count = UBound(Lines) + 1
Do While Location And Index < Count
Get #FileNo, Location, Char
If Char = 13 Then
Index = Index + 1
End If
Location = Location - 1
Loop
Put #FileNo, Location + 1, vbCrLf & Join(Lines, vbCrLf)
Location = Seek(FileNo)
Close #FileNo
SetEOF FileName, Location - 1
End Function
</code>Sv: Byta ut textrader i textfil?
Proffsig lösning förutom detta
Dim Index As Integer 'skall vara Long
Dim Count As Integer 'skall vara Long
Sven ;-)Sv: Byta ut textrader i textfil?
Skulle ju kunna gjort allt med API'er för att slippa att öppna och stänga filen två gånger. Men Det var enklare att använda de interna funktionerna för att läsa och skriva till filen.
Jag är tveksam på att VB klarar Parameter Arrayer med mer än 32 tusen poster. Tvecksam om det är mer än hundra. Så då räcker det väl med en integer. Skulle kansk ändra storleken till en byte.
Om det inte finns nån begränsning, lär det ju inte heller vara roligt att skriva den kod raden med 32000 parametrar... :O)
Om man skulle vilja ha mer rader än Parameter Arrayen tillåter, eller kunna ange dem dynamiskt. Så är det ju bara att göra om den till en sträng array och om nödvändigt konvertera till Long.Sv: Byta ut textrader i textfil?
Men min är ändock snabbast.... Om vi nu skall snacka optimering >>>> SvenPon.
Följande tider upopmättes på min PIII-800 Mhz då två sista raderna byts ut i en textfil. Textfilens storlek 1.89 MB
SvenPon: 622 ms
Andreas Hillqvist: 2-3 ms
peterh: 0-1 ms
SvenPon, min rutin klarar det på 1/622 del av din tid och drygt halva Andreas tid.
När jag exekverar min variant får jag oftast 0 ms, ibland 1 ms.
Min rutin har dock en svaghet. Textfilen måste sluta med en vbCrLF. Men detta borde den göra då man förmodligen lägger till vbCrLf efter alla rader man skriver till fil.
Observera att jag dessutom inte använder API förutom att mäta exekveringstiden.
Slå detta ni....... (Äntligen blev jag störst snabbast och bäst)
Här är mitt final bidrag......
=============================================
Option Explicit
Private Declare Function GetTickCount Lib "kernel32" () As Long
Private Sub Form_Load()
Dim startT As Single
startT = GetTickCount()
Dim CRLF As Byte
Dim filNamn As String
Dim filPekare As Long
Dim filLangd As Long
Dim filNummer As Integer
Dim position As Byte
Dim str As String
Dim notFound As Boolean
Const step = 3
filNummer = FreeFile()
filNamn = "C:\test.txt"
filLangd = fileLen(filNamn)
filPekare = filLangd
notFound = True
str = Space$(step)
Open filNamn For Binary As #filNummer
Do While notFound
filPekare = filPekare - step
Get #filNummer, filPekare, str
position = InStr(1, str, vbCr)
If position <> 0 Then
CRLF = CRLF + 1
ElseIf CRLF = 3 Then
filPekare = filPekare + position + step + 2
Exit Do
End If
Loop
Seek #filNummer, filPekare
Put #filNummer, , "ersättningsrad nummer 1" & vbCrLf
Put #filNummer, , "ersättningsrad nummer 2" & vbCrLf
Close #filNummer
Debug.Print GetTickCount() - startT
End Sub
=============================================
/peterh
emotser kommentarer.......Sv: Byta ut textrader i textfil?
Tänkte lägga in en koll så att API anropet enbart utfördes vid behov. Men struntade i det.
Du använder ju dig egentligen av samma princip som jag använder. Bara en snabbara implementerning genom att söka större stycken. Så jag vet inte om det är något att skryta med. Dessutom är det ingen gennerel funktion utan en explicit lösning på problemet.
Om man jämför min hastighetsförändring mot ditt först förslag så är ju din hastighets förändring försummbar... :O)
Men jag tackar dig för ditt bidrag, du slog mig vad det gäller tid. Grattis...Sv: Byta ut textrader i textfil?
Däremot säger SvenPon att man inte skall deklarera integertyper i VB om man kör 32-bitars system....
Jag är beredd att hålla med men förklara då resultatet av följande körning:
=============================================
Option Explicit
Private Declare Function GetTickCount Lib "kernel32" () As Long
Private Sub Form_Load()
Dim a As Single
Dim i1 As Long, j1 As Long
Dim i2 As Integer, j2 As Integer
a = GetTickCount()
For i1 = 0 To 8192
For j1 = -32768 To 0
Next j1
Next i1
Debug.Print GetTickCount() - a
a = GetTickCount()
For i2 = 0 To 8192
For j2 = -32768 To 0
Next j2
Next i2
Debug.Print GetTickCount() - a
End Sub
============================================
/peterhSv: Byta ut textrader i textfil?
Skall analyser lite senare Men ytterligare ett
optimeringsfel.
For i1 = 0 To 8192
For j1 = -32768 To 0
Next j1
Next i1
Den här slingan blir onödigt långsam pga
att du skriver Next j1 Och Next i1
Skall vara Next 'j1 Next 'i1
Kolla får du se
Sen blir förmodligen din mätning inkorrekt därför
att du blandar in GetTickCount
Mät med Timer det duger bra
Tror jag
SvenSv: Byta ut textrader i textfil?
Två gånger på en dag har du "överlistat" mig.
Hmm, jag håller nog på att tappa stinget... :O)Sv: Byta ut textrader i textfil?
Jag gjorde de nästlade loparna på detta sätt därför att jag naturligtvis fick en overflow för denna typ av loop.
for i = 0 to 32767
next i
då i=32767 exekveras ändå next i och genererar en overflow. Därför satte jag looparna att loopa från -32768 till 0 istället.
För övrigt så är loparnas längder anpassat så körningen inte tar alltför lång tid.
Vad beträffar
next i
next j
Så såg jag ingen skillnad i exekveringstid jämfört med
next i,j
Men kolla in exemplet nedan då om du tycker att det verkar vara mer rättvist..
===========================================
Option Explicit
Private Sub Form_Load()
Dim q As Integer
For q = 1 To 5
loopInt q
loopLong q
Next q
For q = 6 To 10
loopLong q
loopInt q
Next q
For q = 11 To 15
loopLong q
Next q
For q = 11 To 15
loopInt q
Next q
For q = 16 To 20
loopInt q
Next q
For q = 16 To 20
loopLong q
Next q
End Sub
Private Sub loopInt(q As Integer)
Dim i As Integer, j As Integer
Dim t As Single
t = Timer: For i = 0 To 10000: For j = 0 To 10000: Next j, i: Debug.Print "Integer: "; q, Timer - t
End Sub
Private Sub loopLong(q As Integer)
Dim k As Long, l As Long
Dim u As Single
u = Timer: For k = 0 To 10000: For l = 0 To 10000: Next l, k: Debug.Print "Long : "; q, Timer - u
End Sub
=============================================
/peterhSv: Byta ut textrader i textfil?
======================================
Option Explicit
Private Declare Function GetTickCount Lib "kernel32" () As Long
Private Sub withTimer()
Dim t1 As Single
Dim t2 As Single
t1 = GetTickCount()
t2 = Timer
While Timer < (t2 + 5): Wend
Debug.Print (GetTickCount() - t1) * (Timer - t2) / 5000
End Sub
Private Sub withTickCount()
Dim t1 As Single
Dim t2 As Single
t1 = GetTickCount()
t2 = Timer
While GetTickCount() < (t1 + 5000): Wend
Debug.Print (GetTickCount() - t1) * (Timer - t2) / 5000
End Sub
Private Sub Form_Load()
withTickCount
withTimer
End Sub
=======================================
SÅvitt kan jag se det är det hugget som stucket, vilken som är bäst
att använda... Därför duger det med Timer.
Man får ju för sig att API är bättre i alla lägen... Men så är det ej...
Några kommentarer angående koden ovan....
Tiden att exekvera enskilda kommandon är försummbar mot tiden att exekvera hela snurran...
/peterhSv: Byta ut textrader i textfil?
Igen
> Vad beträffar
next i
next j
Nej jag kan inte heller se nån skillnad .
På den gamle kungens tid sa man att om man skrev " Next i "
så slöade detta ner därför att i varje loop så behövde
kontroll ske mot "For i "
Man menade att man bara skulle skriva "Next"
Med dagens snabba processorer verkar det inte
ge någon mätbar skillnad.
mvh
Sven