判断文本文件是否UTF-8编码

  utf-8编码的文本文档,有的带有BOM (Byte Order Mark, 字节序标志),即0xEF, 0xBB,0xBF,有的没有。用Windows的notepad编辑的文本保存是会自动添加BOM,我们常用UE编辑器在保存utf-8编码的时候也会自动添加BOM,Notepad++默认设置中保存utf-8编码时是无BOM的。其它文本编辑器就没有尝试过,有兴趣的可以自己试试。
  utf-8是一种多字节编码的字符集,表示一个Unicode字符时,它可以是1个至多个字节。即在文本全部是ASCII字符时utf-8是和ASCII一致的(utf-8向下兼容ASCII)。utf-8字节流如下所示:

1字节:0xxxxxxx
2字节:110xxxxx 10xxxxxx
3字节:1110xxxx 10xxxxxx 10xxxxxx

  按照utf-8编码规则进行验证,例如:
  第一个字符的第一个字节的第一个bit为0,说明是个ANSII字符。继续查看第二个字符,若第一个比特是1,则查看第二个比特,若第二个比特为1,如果不为1说明这不是一个utf-8编码的文本。如果第二个比特为1,则查看第三个比特为0,不为0则说明不是utf-8编码,如果是0则说明该字符肯能是2字节的utf-8。查看该字符的第二个字节,如果前两个比特符合10则说明这是一个utf-8编码的字符。依次类推,若一旦有一个bit不满足UTF-8编码要求,就判定文本为ANSI(GBK),若直到文本结束都符合utf-8编码规则,则说明文本是UTF-8编码的。
  由上述描述可知字符的第一个字节如果介于0x80与0xC0之间或大于0xF0则不符合utf-8的编码规则,可直接判断不是utf-8编码的文本。如果第一个字节符合utf-8规则且小于0xC0则判断第二个字节,如果第二个字节和0xC0做与操作结果不是0x80则可判断不是utf-8编码的文本。依次类推,如果第一个字节介于0xE0、0xF0之间,且第二个字节符合规则,第三个字节与第二个字节做同样判断,如果符合规则则该字符是utf-8字符,判断下一个字符直到文本结束。具体的代码实现如下所示,这里列了Delphi及C/C++的两种语言的实现结果:

Delphi:

function IsUtf8Format(buffer: PChar; size: Int64): Boolean;
var
  ii: Integer;
  tmp: Byte;
begin
  Result := True;
  ii := 0;
  while ii < size do
  begin
    tmp := Byte(buffer[ii]);
    if tmp < $80 then        //值小于0x80的为ASCII字符 
      Inc(ii)
    else if tmp < $C0 then   //值介于0x80与0xC0之间的为无效UTF-8字符
    begin
      Result := False;
      Break;
    end
    else if tmp < $E0 then   //此范围内为2字节UTF-8字符
    begin
      if ii >= size - 1 then
        Break;
      if (Byte(buffer[ii + 1]) and $C0) <> $80 then
      begin
        Result := False;
        Break;
      end;
      Inc(ii, 2);
    end
    else if tmp < $F0 then  //此范围内为3字节UTF-8字符
    begin
      if ii >= size - 2 then
        Break;
      if ((Byte(buffer[ii + 1]) and $C0) <> $80) or ((Byte(buffer[ii + 2]) and $C0) <> $80) then
      begin
        Result := False;
        Break;
      end;
      Inc(ii, 3);
    end
    else
    begin
      Result := False;
      Break;
    end; 
  end;
end;

function IsUtf8File(fStream: TFileStream): string;
var
  fStream: TFileStream;
  context: string;
begin
  fStream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyNone);
  try
    SetLength(context, fStream.Size);
    fStream.Read(context[1], fStream.Size);
    if isUtf8Format(PChar(context), fStream.Size) then
      showMessage('是utf-8编码');
    else
      showMessage('其它编码');
  finally
    fStream.Free;
  end;
end;

C/C++

function IsUtf8Format(buffer: PChar; size: Int64): Boolean;
var
  ii: Integer;
  tmp: Byte;
begin
  Result := True;
  ii := 0;
  while ii < size do
  begin
    tmp := Byte(buffer[ii]);
    if tmp < $80 then        //值小于0x80的为ASCII字符 
      Inc(ii)
    else if tmp < $C0 then   //值介于0x80与0xC0之间的为无效UTF-8字符
    begin
      Result := False;
      Break;
    end
    else if tmp < $E0 then   //此范围内为2字节UTF-8字符
    begin
      if ii >= size - 1 then
        Break;
      if (Byte(buffer[ii + 1]) and $C0) <> $80 then
      begin
        Result := False;
        Break;
      end;
      Inc(ii, 2);
    end
    else if tmp < $F0 then  //此范围内为3字节UTF-8字符
    begin
      if ii >= size - 2 then
        Break;
      if ((Byte(buffer[ii + 1]) and $C0) <> $80) or ((Byte(buffer[ii + 2]) and $C0) <> $80) then
      begin
        Result := False;
        Break;
      end;
      Inc(ii, 3);
    end
    else
    begin
      Result := False;
      Break;
    end; 
  end;
end;

function Utf8StrToAnsi(fStream: TFileStream): string;
var
  headerStr, context:string;
begin
  fStream.Position := 0;
  SetLength(HeaderStr, 3);
  fStream.Read(HeaderStr[1], 3);
  if HeaderStr = #$EF#$BB#$BF then
  begin
    SetLength(context, fStream.Size - 3);
    fStream.Read(context[1], fStream.Size - 3);
  end
  else
  begin
    fStream.Position := 0;
    SetLength(context, fStream.Size);
    fStream.Read(context[1], fStream.Size);
  end;
  Result := Utf8ToAnsi(context);
end;

本文参考资料:
  文本utf-8编码判断C/C++源码及utf-8编码解释
  utf-8编码规则解释

你可能感兴趣的:(判断文本文件是否UTF-8编码)