2012-02-11 6 views
2

나는 (유니 코드 - 2009 델파이) 매우 큰 파일을 구문 분석하고, 그리고는 유래 질문에 설명 된대로 그렇게 사용 PChar는 변수를 수행하기위한 매우 효율적인 루틴이 : 나는 파일로 실행까지 What is the fastest way to Parse a line in Delphi?PChar를 가져와 델파이에서 파일 끝까지 지난 16 진수 00으로 이동하려면 어떻게해야합니까?

모든 큰 일했습니다 그 안에 16 진수 : 00 문자가 일부 포함되어 있습니다. 이 문자는 PChar 문자열의 끝을 알리고 내 파싱은 그 시점에서 멈 춥니 다. 당신이로 파일을로드 할 때

그러나 :

FileStream := TFileStream.Create(Filename, fmOpenRead or fmShareDenyWrite); 
Size := FileStream.Size; 

는 해당 파일의 크기가 훨씬 더 큰 것을 찾을 수 있습니다. 메모장을 사용하여 파일을 열면 PChar가 수행하는 것처럼 처음 16 진수 : 00에 멈추지 않고 파일의 끝에로드됩니다.

내 읽기/구문 분석 속도를 늦추지 않고 PChar 구문 분석을 사용하면서 파일 끝까지 읽을 수있는 방법은 무엇입니까?

+1

PChars를 사용하는 실제 코드를 보지 않고도 대답하는 것은 어렵습니다. 그것은 당신이 그들을 다루는 방법에 달려 있습니다 - 문자열 함수는 정의에 의해 문자열의 끝이므로 항상 첫 번째 0 바이트에서 멈 춥니 다. 반면에, 이들은 메모리에 대한 포인터 만 입력합니다. 그것들을 일반 포인터로 처리 할 수 ​​있으며 처음 0 바이트에서 멈추지 않고 길이를 다른 곳에 저장하십시오. – Chris

+0

@Chris - 코드는 위에서 언급 한 다른 Stackoverflow 질문에서 허용되는 대답과 매우 유사합니다. 특히, 나는 다음과 같은 줄을 가지고있다 : while (cp ^> # 0) and (cp^<= # 32) do – lkessler

답변

5

다른 문제의 승인 코드는 돌발됩니다. 입력 길이를 저장하고 그 길이를 확인하기 만하면됩니다. 업데이트 된 코드는 다음과 같습니다.

type 
    TLexer = class 
    private 
    FData: string; 
    FTokenStart: PChar; 
    FCurrPos: PChar; 
    FEndPos: PChar;           // << New 
    function GetCurrentToken: string; 
    public 
    constructor Create(const AData: string); 
    function GetNextToken: Boolean; 
    property CurrentToken: string read GetCurrentToken; 
    end; 

{ TLexer } 

constructor TLexer.Create(const AData: string); 
begin 
    FData := AData; 
    FCurrPos := PChar(FData); 
    FEndPos := FCurrPos + Length(AData);      // << New 
end; 

function TLexer.GetCurrentToken: string; 
begin 
    SetString(Result, FTokenStart, FCurrPos - FTokenStart); 
end; 

function TLexer.GetNextToken: Boolean; 
var 
    cp: PChar; 
begin 
    cp := FCurrPos; // copy to local to permit register allocation 

    // skip whitespace 
    while (cp <> FEndPos) and (cp^ <= #32) do     // << Changed 
    Inc(cp); 

    // terminate at end of input 
    Result := cp <> FEndPos;         // << Changed 

    if Result then 
    begin 
    FTokenStart := cp; 
    Inc(cp); 
    while (cp <> FEndPos) and (cp^ > #32) do    // << Changed 
     Inc(cp); 
    end; 

    FCurrPos := cp; 
end; 
+1

우수! 그게 바로 제가 찾던 것입니다. 고맙습니다! – lkessler

+0

+1 정말 대단합니다! – ComputerSaysNo

2

문자가 #0인데도 파일의 모든 문자를 사용하지 않았다면 계속 진행하십시오. 어떻게 계속 하시겠습니까? 계속 진행하는 방법에 따라 달라집니다.

while (cp^ > #0) and (cp^ <= #32) do 
    Inc(cp); 

// using null terminator for end of file 
Result := cp^ <> #0; 

분명히 널 문자에서 중지됩니다 : 당신이 참조

문제는이 코드를 가지고있다. Null 문자가 파일의 끝을 나타내지 않게하려면 Null 문자에서 멈추지 마십시오. 대신에 모든 문자를 소비 한 후에 그만 두십시오. 예상 할 문자의 수를 알아야하고, 본 문자 수를 추적해야합니다.

nChars := Length(FData); 
nCharsSeen := 0; 
while (nCharsSeen < nChars) and (cp^ <= #32) do begin 
    Inc(cp); 
    Inc(nCharsSeen); 
end; 

// using character count for end of file 
Result := nCharsSeen < nChars; 

참조 된 대답은 문자열을 구문 분석했다, 그래서 나는 문자 수를 배울 Length을 사용했습니다. 파일을 파싱하는 경우 대신 TFileStream.Size과 같은 것을 사용하십시오.

+0

글쎄, 그건 효과가있다. 단, Inc (cp)가있을 때마다 추가 테스트 (nCharsSeen lkessler

+0

따라서 파일을 처리하는 데 1 초 미만의 시간이 걸리고 결과적으로 전체 파일을 실제로 처리합니다. 나 한테 좋은 트레이드 오프 같다. 그리고 그것은 여분의 시험이 아닙니다. 그것은 한 테스트를 다른 테스트로 대체합니다. –

+0

귀하의 권리는 추가 테스트가 아니라는 것입니다. 그러나 컴파일러가 최적화해야하는 추가 변수 (nCharsSeen)를 추가합니다. 아무리해도 Craig의 대답은 최고이며 오버 헤드가 없습니다. – lkessler

1

나는 이전에 허용 대답의 코드를 가져다가 두 개의 추가 변수를 추가하여 약간 수정 :

FPosInt: NativeUInt; 
FSize: NativeUInt; 

FSize은 (문자열 변수는이 길이 잠시 저장되어있다 생성자에서 문자열 길이로 초기화됩니다 PChar는 그렇지 않습니다). FPosInt은 파일에있는 현재 문자의 번호입니다. 생성자의 추가 코드 :

FSize := Length(FData); 
FPosInt := 0; 

GetNextToken 함수의 관련 부분은 다음 더 이상 최초의 제로 바이트에서 중지하지 않지만, 문자열의 마지막 문자에 도달 할 때까지 계속 :

// skip whitespace; this test could be converted to an unsigned int 
// subtraction and compare for only a single branch 
while (cp^ <= #32) and (FPosInt < FSize) do 
    begin 
    Inc(cp); 
    Inc(FPosInt); 
    end; 

// end of file is reached if the position counter has reached the filesize 
Result := FPosInt < FSize; 

while 문에서 두 문을 왼쪽에서 오른쪽으로 평가하고 첫 번째 문을 false로 평가하는 문을 더 자주 사용하기 때문에 두 문을 전환했습니다.


대체 접근법은 문자 수를 계산하지 않지만 포인터의 시작 위치를 저장합니다.

FSize := Length(FData); 
FStartPos := NativeUInt(FCurrPos); 

그리고 GetNextToken에서 : 생성자에서 그것은 # 0 문자에 도달하면

// skip whitespace; this test could be converted to an unsigned int 
// subtraction and compare for only a single branch 
while (cp^ <= #32) and ((NativeUInt(cp) - FStartPos) < FSize) do 
    Inc(cp); 

// end of file is reached if the position counter has reached the filesize 
Result := (NativeUInt(cp) - FStartPos) < FSize; 
+0

이것은 Rob (누가 5 분 만에 이길지)와 비슷한 대답입니다. 같은 문제 : 여분의 Inc (FPosInt)가 2 억 번 실행되어야합니다. 그리고 GetNextToken에서 추가 뺄셈과 비교를 추가했습니다. – lkessler

+0

예, 나중에 그의 대답을 보았습니다. 추가 증분 또는 추가 뺄셈 중 하나임에 동의합니다. 그러나 이전 코드에서 포인터를 역 참조 (메모리에서 읽음을 의미 함) 할 때마다 매번 루프에서 두 번 느려질 것이므로 느려질 것이라고 생각하지 않습니다. 컴파일러는 레지스터에 정수를 저장할 가능성이 높으므로 그 값을 증가 시키면 느려지지 않을 것입니다 (제 생각 엔). – Chris