中文字符串分割的一些思路

场景

工作中需要发送文本消息,这里的文本主要指含有中文的字符串。在很多网络服务接口中,假设发送短信,接口要求单次只接受utf-8格式的文本,并限制了文本长度,例如1024个byte大小的数据包。
随即就衍生出两个问题:

  1. 中文字符串的长度计算。
  2. 字符串过长需要截断发送。

其他:
本文是做一个思路分析,在任何语言上都是相通的。本文仅使用C#语言做示范。

分析

中文字符串的长度计算

本文所有的长度,均以字节byte为计算单位。
示例:

string s1 = "123456";
string s2 = "1234你好";

Len(s1);
Len(s2);

void Len(string str)
{
	Console.WriteLine($"str: {str}");
	Console.WriteLine($"string len: {str.Length}");
	Console.WriteLine($"utf8 bytes len: {Encoding.UTF8.GetByteCount(str)}");
	Console.WriteLine("\n");
}

output:

str: 123456
string len: 6
utf8 bytes len: 6

str: 1234你好
string len: 6
utf8 bytes len: 10

由于1个字母为1个字节,但一个汉字大于1个字节,所以中文字符串的长度需要转码并计算byte才能准确。
1个汉字占用的字节长度与其编码格式有关。gb2312占用2个字节,utf8是变长编码,占用3-4个字节。
因此并不用去考虑源文本是什么编码,只需要统统转成utf8并计算字节数即可。

中文字符串的截断

涉及到截断,这里就要增加一个概念,最大长度BufferSize(数据包大小)。超过此长度则截断。
截断后仍为字符串。

1. 转byte数组,分段取值截断

string text = "hello你好";
int buffersize = 5;
byte[] bytes = Encoding.UTF8.GetBytes(text);
// 按字节操作,步长取数据包最大值。
int stepSize = buffersize; 
// 截断次数,字节数除以步长,并向上取整。
int cutTimes = (int)Math.Ceiling(bytes.Length / (double)stepSize); 

Len(text);
Console.WriteLine($"buffer_size:{buffersize}\nstep_size:{stepSize}\ncut_times:{cutTimes}\n");

for(int i = 0; i< cutTimes; i++)
{
	var from = i * stepSize;
	var to = Math.Min((i + 1) * stepSize, bytes.Length);
	var tmp = bytes[from..to];
	Console.WriteLine(Encoding.UTF8.GetString(tmp));
}

output:

str: hello你好
string len: 7
utf8 bytes len: 11

buffer_size:5
step_size:5
cut_times:3

hello
你�
�

这里出现了乱码。其实很简单,这里的汉字占3个字节。截断步长是5。取了5个字节,“你”的3个字节与“好”的前2个字节。数据缺失导致了乱码。

2. 中文占比动态截断

中文占比就是字节长度/字符串长度。可以简单的衡量字符串中汉字的比例。如果全是字母,则为最小的1。如果是全汉字,则可能是2,3,或者4或者其他。
动态截断即截断步长不固定,每次都根据输入的文本内容自动调整。

string text = "hello你好";
int buffersize = 5;
// 计算中文比率,最小为1,最大为4。数据包最大值除以比率并向下取整得到步长。
int stepSize = (int)(buffersize / ((double)Encoding.UTF8.GetByteCount(text) / text.Length)); 
// 截断次数,字符数除以步长,并向上取整。
int cutTimes = (int)Math.Ceiling(text.Length / (double)stepSize); 

Len(text);
Console.WriteLine($"buffer_size:{buffersize}\nstep_size:{stepSize}\ncut_times:{cutTimes}\n");

for(int i = 0; i< cutTimes;i++)
{
	var from = i * stepSize;
	var to = Math.Min((i + 1) * stepSize, text.Length);
	var tmp = text[from..to];
	Console.WriteLine(tmp);
}

output:

str: hello你好
string len: 7
utf8 bytes len: 11

buffer_size:5
step_size:3
cut_times:3

hel
lo你
好

根据输入的文本,7字符,11字节,以及BufferSize=5,计算得到本次的截断步长为3。按中文占比比率换算,保证每次截取的字符对应的字节长度都不会超过最大限制,且不会出现截取不完整的情况。缺点就是会增加截断次数。

你可能感兴趣的:(其他,C#/.NET)