多字节字符串的最后一个字符 天真的解决方案从右到左的解决方案这有什么意义?

问题描述

在处理多字节字符串时,我经常需要做的一件事就是删除它的最后一个字符。我如何找到最后一个字符,以便我可以使用正常的字节操作将其切掉,最好是尽可能少地读取?

请注意,此问题适用于大多数,如果不是所有,多字节编码。像 UTF-8 这样的自同步编码的答案是微不足道的,因为您可以在字节串中从右到左作为开始标记

解决方法

答案将使用 C 语言编写,带有 POSIX 多字节函数。所述功能也可在 Windows 上找到。假设字节串以 len 结尾,并且到目前为止格式良好;假设适当的 setlocale 调用。移植到 mbrlen 留给读者作为练习。

天真的解决方案

显然正确的解决方案涉及“按预期”解析编码,从左到右。

ssize_t index_of_last_char_left(const char *c,size_t len) {
  size_t pos = 0;
  size_t next = 1;
  mblen(NULL,0);

  while (pos < len - 1) {
    next = mblen(c + pos,len - pos);
    if (next == -1)  // Invalid input
      return pos;
    pos += next;
  }

  return pos - next;
}

像这样删除多个字符会导致“不小心二次方”的情况;记住中间位置会有所帮助,但需要额外的管理。

从右到左的解决方案

正如我在问题中提到的,对于自同步编码,唯一要做的就是寻找开始标记。但是那些不进行自我同步的人有什么问题呢?

  • 一或二字节 EUC encodings 的两字节序列的两个字节都高于 0x7f,并且开始和继续字节之间几乎没有区别。为此,我们可以检查 mblen(pos) == bytes_left,因为我们知道字符串格式正确。
  • Big5、GBK 和 GB10830 编码还允许 ASCII 范围内的连续字节,因此后视是强制性的。

清除后(并假设直到 len 的字节串格式正确),我们可以:

// As much as CJK encodings do. I don't have time to see if it works for UTF-1.
#define MAX_MB_LEN 4

ssize_t index_of_last_char_right(const char *c,size_t len) {
  ssize_t pos = len - 1;
  bool last = true;
  bool last_is_okay = false;
  assert(!mblen(NULL,0));   // No,we really cannot handle shift states.

  for (; pos >= 0 && pos >= len - 2 - MAX_MB_LEN; pos--) {
    int next = mblen(c + pos,len - pos);
    bool okay = (next > 0) && (next == len - pos - 1);
    if (last) {
      last_is_okay = okay;
      last = false;
    } else if (okay)
      return pos;
  }

  return last_is_okay ? len - 1 : -1;
}

(您应该能够通过 (next > 0) && (next <= len - pos - 1) 找到格式错误的字符串的最后一个好的字符。但是当最后一个字节没问题时不要返回它!)

这有什么意义?

上面的代码示例适用于不想只编写“UTF-8 支持”而是基于 C 库的“语言环境支持”的理想主义者。 2021 年可能根本没有这个意义:)