无法从字符串中删除“空字符”。

4
我曾几个月前提出了一个类似的问题链接。多亏了Rob Kennedy,我可以把整个文本加载到Richedit中,但是我无法删除Null chars。我能够加载我的文本是因为我使用了Stream
现在看这段代码:
var
  strm : TMemorystream;
  str  : UTF8string;
  ss   : TStringstream;

begin
  strm := tmemorystream.Create;

  try
    strm.LoadFromFile('C:\Text.txt');
    setstring(str,PAnsichar(strm.Memory),strm.Size);
    str := StringReplace(str, #0, '', [rfReplaceAll]);  //This line doesn't work at all
    ss  := tstringstream.Create(str);
    Richedit1.Lines.LoadFromStream(ss);
  finally
    strm.Free;
    ss.Free;
  end;
end;

我将 TMemorystream 转换为 string,使用 StringReplace() 去除了其中的 Null Chars,然后再将其转换回 TStringstream,最后使用 Richedit.lines.LoadFromStream 加载。

但我的问题是,我无法使用 StringReplace() 去除 Null Character。我可以替换其他字符,但不能替换 #0

有没有直接在 TMemorystream 中去除 null charcters 并加载到 Richedit 的方法?如果不可能或者很复杂,那么当我将文本转换为 string 时,如何去除这些字符呢?

谢谢。


1
1)输入一个字符 2)如果它不是“NUL”,则输出它,否则丢弃它 3)回到步骤1。 - Free Consulting
1
如果我的答案不起作用,为什么之前你还接受了它?请注意,我已更新了我的答案,提到了 StringReplace 的缺点,并链接到另一个答案,该答案可以完成相同的任务。 - Rob Kennedy
3
实际问题是,为什么文件一开始就有空字符?一个UTF-8编码的文本文件不应该包含任何空字符,因此该文件很可能一开始就不是UTF-8编码。而且这段代码非常低效,涉及到许多UTF8->UTF16->UTF8->UTF16的转换。 - Remy Lebeau
当我将 UTF8string 更改为 string 时,文本将变得无法读取。 - Sky
2
@Sky:网页有一个字符集与其相关联,该字符集通过HTTP“Content-Type”标头或HTML本身的<meta>标记进行指定。在将数据解码为Unicode时必须使用正确的字符集。你不能随意加载使用任何字符集的数据,否则这样会导致数据丢失。 - Remy Lebeau
显示剩余4条评论
2个回答

10

Sertac的答案很准确,你应该接受它。如果性能很重要,并且您有一个包含频繁出现空字符实例的大字符串,则应尝试减少堆分配的数量。以下是我将如何实施:

function RemoveNull(const Input: string): string;
var
  OutputLen, Index: Integer;
  C: Char;
begin
  SetLength(Result, Length(Input));
  OutputLen := 0;
  for Index := 1 to Length(Input) do
  begin
    C := Input[Index];   
    if C <> #0 then
    begin
      inc(OutputLen);
      Result[OutputLen] := C;
    end;
  end;
  SetLength(Result, OutputLen);
end;

如果你想直接在内存流中进行操作,那么可以这样做:

procedure RemoveNullFromMemoryStream(Stream: TMemoryStream);
var
  i: Integer;
  pIn, pOut: PByte;
begin
  pIn := Stream.Memory;
  pOut := pIn;
  for i := 0 to Stream.Size-1 do
  begin
    if pIn^ <> 0 then
    begin
      pOut^ := pIn^;
      inc(pOut);
    end;
    inc(pIn);
  end;
  Stream.SetSize(NativeUInt(pOut)-NativeUInt(Stream.Memory));
end;

好的。接受Sertac的答案 :) - Sky

8
据我所见,所有的搜索/替换工具在某个时刻都将输入强制转换为PChar,其中 '#0' 是终止字符。因此它们从不超过第一个空字符之前的字符串部分。您可能需要设计自己的机制。以下是一个快速示例:
var
  i: Integer;
begin
  Assert(str <> '');
  i := 1;
  while i <= Length(str) do
    if str[i] = #0 then
      Delete(str, i, 1)
    else
      Inc(i);

在流中进行替换同样需要测试每个字符,然后在决定删除一个字符后相应地调整流,然后继续前进。


1
你可以在回答中指出,“delete”只应该用于少量数据。比较delete和其他操作 - moskito-x
@moskito - 是的,看起来是这样。不过在我看来,解释原因并举一个简单的例子应该就足够了。 - Sertac Akyuz
@Sertac 我认为应该是这样的。可悲的现实是,那些不了解情况的人会盲目地使用你发布的任何代码。对我来说,一个声明说明“不要在生产环境中使用此代码,因为它会破坏堆栈”就足够了。即使如此,我相信很多人也会忽略这个警告! - David Heffernan
@David - 我想我错了,海报似乎完全没有关心行为背后的原因。实际上,所要求的只是“写出代码”,在这种情况下,你的答案确实是唯一的选择。我对你的答案被选中并没有任何问题,困扰我的是我并不总能明确需要什么样的答案。 - Sertac Akyuz
我同意。你的回答解释了为什么StringReplace会表现出这样的行为。因此,它应该被接受。实际上,看起来问者根本没有兴趣理解发生了什么,这是这个问题仍然存在的根本原因。 - David Heffernan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接