C++ 在不中断多字节序列的情况下查找最长的UTF-8序列

C++ 在不中断多字节序列的情况下查找最长的UTF-8序列,c++,algorithm,utf-8,C++,Algorithm,Utf 8,我需要截断UTF-8编码的字符串,使其长度不超过预定义的字节大小。特定协议还要求,被截断的字符串仍然形成有效的UTF-8编码,即不必拆分多字节序列 给定最大字节数,我可以向前移动,计算每个代码点的编码大小,直到达到最大字节数。不过,O(n)不是很吸引人。有没有一种算法可以更快地完成,最好是在(摊销)O(1)时间内完成?更新2019-06-24:经过一夜的睡眠,这个问题似乎比我第一次尝试看起来要容易得多。出于历史原因,我在下面留下了前面的答案 UTF-8编码是。这使得能够确定符号流中任意选择的代码

我需要截断UTF-8编码的字符串,使其长度不超过预定义的字节大小。特定协议还要求,被截断的字符串仍然形成有效的UTF-8编码,即不必拆分多字节序列


给定最大字节数,我可以向前移动,计算每个代码点的编码大小,直到达到最大字节数。不过,O(n)不是很吸引人。有没有一种算法可以更快地完成,最好是在(摊销)O(1)时间内完成?

更新2019-06-24:经过一夜的睡眠,这个问题似乎比我第一次尝试看起来要容易得多。出于历史原因,我在下面留下了前面的答案

UTF-8编码是。这使得能够确定符号流中任意选择的代码单元是否是代码序列的开始。UTF-8序列可以拆分到代码序列开头的左侧

代码序列的开头是ASCII字符(
0xxxxxxxb
)或多字节序列中的前导字节(
11xxxxxxb
)。尾随字节遵循模式
10xxxxxxb
。UTF-8编码的开始满足条件
(代码单位&0b11000000)!=0b10000000
,换句话说:它不是尾随字节

通过应用以下算法,可以在恒定时间(O(1))内确定不超过请求字节计数的最长UTF-8序列:

  • 如果输入长度不超过请求的字节数,则返回实际的字节数
  • 否则,循环到开始(开始一个代码单元超过请求的字节计数),直到找到序列的开始。返回序列开头左侧的字节计数
  • 输入代码:

    #include <string_view>
    
    size_t find_max_utf8_length(std::string_view sv, size_t max_byte_count)
    {
        // 1. Input no longer than max byte count
        if (sv.size() <= max_byte_count)
        {
            return sv.size();
        }
    
        // 2. Input longer than max byte count
        while ((sv[max_byte_count] & 0b11000000) == 0b10000000)
        {
            --max_byte_count;
        }
        return max_byte_count;
    }
    
    该算法仅对UTF-8编码进行操作。它不会试图以任何方式处理Unicode。虽然它将始终生成有效的UTF-8编码序列,但编码的代码点可能不会形成有意义的Unicode图形

    该算法在固定时间内完成。不管输入大小如何,如果当前限制为每个UTF-8编码最多4个字节,则最终循环将最多旋转3次。该算法将继续工作,并在恒定时间内完成,以防UTF-8编码发生变化,从而允许每个编码码点最多5或6个字节


    先前的答案

    这可以在O(1)中完成,方法是将问题分解为以下情况:

  • 输入长度不超过请求的字节计数。在这种情况下,只需返回输入即可
  • 输入长于请求的字节计数。在索引
    max\u byte\u count-1中查找编码中的相对位置:
    
  • 如果这是一个ASCII字符(最高位未设置
    0xxxxxxxb
    ),则我们处于一个自然边界,可以在它后面剪切字符串
  • 否则,我们要么在多字节序列的开始、中间或结尾。要找出哪里,考虑下面的字符。如果它是ASCII字符(
    0xxxxxxxb
    )或多字节序列的开头(
    11xxxxxxb
    ),则我们处于多字节序列的尾部,即自然边界
  • 否则,我们要么在多字节序列的开头,要么在中间。向字符串的开头迭代,直到找到多字节编码的开头(
    11xxxxxxb
    )。剪切该字符之前的字符串
  • 以下代码在给定最大字节数的情况下计算被截断字符串的长度。输入需要形成有效的UTF-8编码

    #包括
    大小查找最大utf8长度(标准::字符串视图sv,大小最大字节计数)
    {
    //1.不超过最大字节数
    
    如果(sv.size()@lig:我认为这也很有用。它是如此的有用,以至于我确信,一定有人在某处写过这篇文章。我搜索了好几天,没有发现任何类似的东西。在所有关于UTF-8是自同步的炒作中,人们希望能很容易地找到一个利用这一特性的实现。然而我d什么都没有。很高兴知道,这似乎对其他人有用,谢谢。@I可检测:你看不到这段代码的原因是因为你通常不需要它。在任意代码单元边界上拆分字符串(你必须给我不超过X个代码单元,但我需要它们来编码完整的代码点)对于基本的Unicode处理来说,这样做并不是非常有用。通常需要在代码点或grapheme群集边界上拆分字符串,这有着完全不同的处理要求。在Unicode处理之外,处理UTF-8文本通常是通过复制来完成的。@nic:我不同意这样的观点,即通常不需要。每当您处理一个协议时,该协议将信息单元打包为最大数据包大小,如果您可以确定单个数据包的编码有效性,它将大大简化处理。Unicode处理具有足够的挑战性;向混合中添加瞬态解码状态肯定不会使您的网络客户端成为y更简单。你是对的,不过,我应该补充一点,这个实现对Unicode处理没有任何作用。@IInspectable:为什么协议需要在数据包级别验证UTF-8序列的有效性?在更高的级别上,当事情组装起来时,检查是有意义的。但是数据包级别的验证是有意义的ng类似于CRC损坏检查,而不是UTF-8编码检查。UTF-8编码失败不是数据包或网络传输问题,而是“发送方不知道在数据包中放入什么”问题。因此,它应该在网络层之外处理。如果您在数据包级别检测到它,则发送方不可能在数据包级别修复它。@nic:确实,协议不应该验证任何内容。它应该只强制enc
    #include <iostream>
    #include <iomanip>
    #include <string_view>
    #include <string>
    
    int main()
    {
        using namespace std::literals::string_view_literals;
    
        std::cout << "max size output\n=== ==== ======" << std::endl;
    
        auto test{u8"€«test»"sv};
        for (size_t count{0}; count <= test.size(); ++count)
        {
            auto byte_count{find_max_utf8_length(test, count)};
            std::cout << std::setw(3) << std::setfill(' ') << count
                      << std::setw(5) << std::setfill(' ') << byte_count
                      << " " << std::string(begin(test), byte_count) << std::endl;
        }
    }
    
    max size output
    === ==== ======
      0    0 
      1    0 
      2    0 
      3    3 €
      4    3 €
      5    5 €«
      6    6 €«t
      7    7 €«te
      8    8 €«tes
      9    9 €«test
     10    9 €«test
     11   11 €«test»