2009-08-30 12 views
6

Sto usando Delphi7 (VCL non Unicode), ho bisogno di memorizzare un sacco di WideStrings all'interno di un TFileStream. Non riesco a utilizzare TStringStream poiché le stringhe (larghe) sono mescolate con dati binari, il formato è progettato per accelerare il caricamento e la scrittura dei dati ... Tuttavia, credo che il modo corrente in cui sto caricando/scrivendo le stringhe possa essere un collo di bottiglia del mio codice ...(Wide) String - memorizzazione in TFileStream, Delphi 7. Qual è il modo più veloce?

attualmente sto scrivendo la lunghezza di una stringa, quindi scrivendo char in char ... durante il caricamento, prima sto caricando la lunghezza, quindi caricando char per char ...

Quindi, qual è il modo più veloce per salvare e caricare WideString su TFileStream?

Grazie in anticipo

+0

Modifica di un'area particolare del codice perché si * crede Eve * potrebbe essere il collo di bottiglia può essere un enorme spreco di tempo. Dovresti misurare prima, ci sono molti strumenti per aiutarti, alcuni gratuiti, altri commerciali. Prova questi primi per alcuni link: http://stackoverflow.com/questions/291631/profiler-and-memory-analysis-tools-for-delphi e http://stackoverflow.com/questions/368938/delphi-profiling-tools – mghie

+0

Grazie, ma stavo usando QueryPerformanceCounter per rilevarlo;) comunque quello era sicuramente il collo di bottiglia, dato che leggere char in char è molto lento ... tutte le altre operazioni stavano solo salvando alcuni dati binari brevi. – migajek

+0

Ah, OK.Stavo solo reagendo al tuo uso delle parole "credo" e "potrebbe", mi dispiace quindi per la predica ;-) – mghie

risposta

6

Invece di leggere e scrivere un carattere alla volta, leggere e scrivere tutti in una volta:

procedure WriteWideString(const ws: WideString; stream: TStream); 
var 
    nChars: LongInt; 
begin 
    nChars := Length(ws); 
    stream.WriteBuffer(nChars, SizeOf(nChars); 
    if nChars > 0 then 
    stream.WriteBuffer(ws[1], nChars * SizeOf(ws[1])); 
end; 

function ReadWideString(stream: TStream): WideString; 
var 
    nChars: LongInt; 
begin 
    stream.ReadBuffer(nChars, SizeOf(nChars)); 
    SetLength(Result, nChars); 
    if nChars > 0 then 
    stream.ReadBuffer(Result[1], nChars * SizeOf(Result[1])); 
end; 

Ora, tecnicamente, dal momento che WideString è un Windows BSTR, si può contenere un numero di byte pari a dispari. La funzione Length legge il numero di byte e divide per due, quindi è possibile (anche se non è probabile) che il codice sopra taglierà l'ultimo byte. Si potrebbe utilizzare questo codice invece:

procedure WriteWideString(const ws: WideString; stream: TStream); 
var 
    nBytes: LongInt; 
begin 
    nBytes := SysStringByteLen(Pointer(ws)); 
    stream.WriteBuffer(nBytes, SizeOf(nBytes)); 
    if nBytes > 0 then 
    stream.WriteBuffer(Pointer(ws)^, nBytes); 
end; 

function ReadWideString(stream: TStream): WideString; 
var 
    nBytes: LongInt; 
    buffer: PAnsiChar; 
begin 
    stream.ReadBuffer(nBytes, SizeOf(nBytes)); 
    if nBytes > 0 then begin 
    GetMem(buffer, nBytes); 
    try 
     stream.ReadBuffer(buffer^, nBytes); 
     Result := SysAllocStringByteLen(buffer, nBytes) 
    finally 
     FreeMem(buffer); 
    end; 
    end else 
    Result := ''; 
end; 

Ispirato Mghie's answer, hanno sostituito i miei Read e Write chiamate con ReadBuffer e WriteBuffer. Quest'ultimo genererà eccezioni se non sono in grado di leggere o scrivere il numero richiesto di byte.

+0

Il tuo secondo La versione 'WriteWideString()' non viene compilata (manca typecast a PWideChar, manca il paren), ma soprattutto fallisce per le stringhe vuote. Il tuo secondo 'ReadWideString()' dovrebbe anche controllare la lunghezza 0 e semplicemente restituire una stringa vuota in quel caso. – mghie

+0

Non vedo alcun motivo per cui non funzioni per le stringhe vuote; 'SysStringByteLen' restituisce zero per i puntatori nulli. Il requisito di digitare cast a 'PWideChar' è perché" SysStringByteLen' è erroneamente dichiarato come "PWideChar" invece di "WideString", o "BSTR" è erroneamente dichiarato "PWideChar" invece di "WideString". Ciò nondimeno, l'ho risolto e ho anche affrontato le altre preoccupazioni. Grazie. –

+0

I * ha fatto * vedere un motivo per cui potrebbe non riuscire per le stringhe che hanno solo un byte, però. Con il controllo del raggio abilitato, l'espressione 'ws [1]' dovrebbe fallire in quel caso. (Delphi QC bug 9425 e Free Pascal bug 0010013 incidono sulla mancata riuscita di una particolare versione.) –

6

Non c'è niente di speciale in ampi archi, di leggere e scrivere più velocemente possibile è necessario leggere e scrivere il più possibile in un colpo solo:

procedure TForm1.Button1Click(Sender: TObject); 
var 
    Str: TStream; 
    W, W2: WideString; 
    L: integer; 
begin 
    W := 'foo bar baz'; 

    Str := TFileStream.Create('test.bin', fmCreate); 
    try 
    // write WideString 
    L := Length(W); 
    Str.WriteBuffer(L, SizeOf(integer)); 
    if L > 0 then 
     Str.WriteBuffer(W[1], L * SizeOf(WideChar)); 

    Str.Seek(0, soFromBeginning); 
    // read back WideString 
    Str.ReadBuffer(L, SizeOf(integer)); 
    if L > 0 then begin 
     SetLength(W2, L); 
     Str.ReadBuffer(W2[1], L * SizeOf(WideChar)); 
    end else 
     W2 := ''; 
    Assert(W = W2); 
    finally 
    Str.Free; 
    end; 
end; 
2

WideStrings contengono una 'stringa' di WideChar di , che usano 2 byte ciascuno. Se si desidera memorizzare le stringhe UTF-16 (che WideStrings utilizza internamente) in un file e poter utilizzare questo file in altri programmi come Blocco note, è necessario scrivere prima un byte order mark: #$FEFF.

Se conosci questa, la scrittura può apparire come segue:

Stream1.Write(WideString1[1],Length(WideString)*2); //2=SizeOf(WideChar) 

lettura può apparire come segue:

Stream1.Read(WideChar1,2);//assert returned 2 and WideChar1=#$FEFF 
SetLength(WideString1,(Stream1.Size div 2)-1); 
Stream1.Read(WideString1[1],(Stream1.Size div 2)-1); 
+1

Ha detto che vuole archiviare molte stringhe, che saranno mescolate con dati binari e che saranno precedute dalle loro lunghezze. qualcosa da usare con Blocco note Il tuo codice dedica l'intero stream a una singola stringa –

+0

Il codice che accede incondizionatamente al primo elemento di una stringa vuota causerà violazioni di accesso – mghie

1

È inoltre possibile utilizzare TFastFileStream per la lettura dei dati o stringhe, ho incollato il unità a http://pastebin.com/m6ecdc8c2 e un esempio di seguito:

program Project36; 

{$APPTYPE CONSOLE} 

uses 
    SysUtils, Classes, 
    FastStream in 'FastStream.pas'; 

const 
    WideNull: WideChar = #0; 

procedure WriteWideStringToStream(Stream: TFileStream; var Data: WideString); 
var 
    len: Word; 
begin 
    len := Length(Data); 
    // Write WideString length 
    Stream.Write(len, SizeOf(len)); 
    if (len > 0) then 
    begin 
    // Write WideString 
    Stream.Write(Data[1], len * SizeOf(WideChar)); 
    end; 
    // Write null termination 
    Stream.Write(WideNull, SizeOf(WideNull)); 
end; 

procedure CreateTestFile; 
var 
    Stream: TFileStream; 
    MyString: WideString; 
begin 
    Stream := TFileStream.Create('test.bin', fmCreate); 
    try 
    MyString := 'Hello World!'; 
    WriteWideStringToStream(Stream, MyString); 

    MyString := 'Speed is Delphi!'; 
    WriteWideStringToStream(Stream, MyString); 
    finally 
    Stream.Free; 
    end; 
end; 

function ReadWideStringFromStream(Stream: TFastFileStream): WideString; 
var 
    len: Word; 
begin 
    // Read length of WideString 
    Stream.Read(len, SizeOf(len)); 
    // Read WideString 
    Result := PWideChar(Cardinal(Stream.Memory) + Stream.Position); 
    // Update position and skip null termination 
    Stream.Position := Stream.Position + (len * SizeOf(WideChar)) + SizeOf(WideNull); 
end; 

procedure ReadTestFile; 
var 
    Stream: TFastFileStream; 

    my_wide_string: WideString; 
begin 
    Stream := TFastFileStream.Create('test.bin'); 
    try 
    Stream.Position := 0; 
    // Read WideString 
    my_wide_string := ReadWideStringFromStream(Stream); 
    WriteLn(my_wide_string); 
    // Read another WideString 
    my_wide_string := ReadWideStringFromStream(Stream); 
    WriteLn(my_wide_string); 
    finally 
    Stream.Free; 
    end; 
end; 

begin 
    CreateTestFile; 
    ReadTestFile; 
    ReadLn; 
end. 
+2

Nota: tale codice non funzionerà se la stringa da leggere contiene qualsiasi carattere nullo –

+0

Il codice che accede incondizionatamente al primo elemento di una stringa vuota causerà violazioni di accesso – mghie

+0

Grazie mghie, il codice è corretto. – pani

Problemi correlati