队列名称最多可以是 255 个字节的 UTF-8 字符。
在 ruby (1.9.3) 中,如何在不中断字符中间的情况下按字节数截断 UTF-8 字符串?结果字符串应该是符合字节限制的最长的有效 UTF-8 字符串。
对于 Rails >= 3.0,您有 ActiveSupport::Multibyte::Chars 限制方法。
来自 API 文档:
- (Object) limit(limit)
将字符串的字节大小限制为不中断字符的字节数。当字符串的存储由于某种原因受到限制时可用。
例子:
'こんにちは'.mb_chars.limit(7).to_s # => "こん"
bytesize
将为您提供以字节为单位的字符串长度,而(只要字符串的编码设置正确)诸如 slice 之类的操作不会破坏字符串。
一个简单的过程就是遍历字符串
s.each_char.each_with_object('') do|char, result|
if result.bytesize + char.bytesize > 255
break result
else
result << char
end
end
如果你很狡猾,你会直接复制前 63 个字符,因为任何 unicode 字符在 utf-8 中最多为 4 个字节。
请注意,这仍然不完美。例如,假设字符串的最后 4 个字节是字符 'e' 并结合了重音符号。切片最后 2 个字节会产生一个仍然是 utf8 的字符串,但就用户看到的内容而言,会将输出从“é”更改为“e”,这可能会改变文本的含义。当您只是命名 RabbitMQ 队列时,这可能不是什么大问题,但在其他情况下可能很重要。例如,在法语中,时事通讯标题“Unpolicier tué”的意思是“一名警察被杀”,而“Unpolicier tue”的意思是“一名警察杀人”。
我想我找到了一些有用的东西。
def limit_bytesize(str, size)
str.encoding.name == 'UTF-8' or raise ArgumentError, "str must have UTF-8 encoding"
# Change to canonical unicode form (compose any decomposed characters).
# Works only if you're using active_support
str = str.mb_chars.compose.to_s if str.respond_to?(:mb_chars)
# Start with a string of the correct byte size, but
# with a possibly incomplete char at the end.
new_str = str.byteslice(0, size)
# We need to force_encoding from utf-8 to utf-8 so ruby will re-validate
# (idea from halfelf).
until new_str[-1].force_encoding('utf-8').valid_encoding?
# remove the invalid char
new_str = new_str.slice(0..-2)
end
new_str
end
用法:
>> limit_bytesize("abc\u2014d", 4)
=> "abc"
>> limit_bytesize("abc\u2014d", 5)
=> "abc"
>> limit_bytesize("abc\u2014d", 6)
=> "abc—"
>> limit_bytesize("abc\u2014d", 7)
=> "abc—d"
更新...
没有 active_support 的分解行为:
>> limit_bytesize("abc\u0065\u0301d", 4)
=> "abce"
>> limit_bytesize("abc\u0065\u0301d", 5)
=> "abce"
>> limit_bytesize("abc\u0065\u0301d", 6)
=> "abcé"
>> limit_bytesize("abc\u0065\u0301d", 7)
=> "abcéd"
使用 active_support 分解的行为:
>> limit_bytesize("abc\u0065\u0301d", 4)
=> "abc"
>> limit_bytesize("abc\u0065\u0301d", 5)
=> "abcé"
>> limit_bytesize("abc\u0065\u0301d", 6)
=> "abcéd"
这个怎么样:
s = "δogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδog"
count = 0
while true
more_truncate = "a" + (255-count).to_s
s2 = s.unpack(more_truncate)[0]
s2.force_encoding 'utf-8'
if s2[-1].valid_encoding?
break
else
count += 1
end
end
s2.force_encoding 'utf-8'
puts s2
Rails 6 将提供一个String#truncate_bytes,其行为类似于truncate
,但采用字节计数而不是字符计数。而且,当然,它返回一个有效的字符串(它不会在多字节字符的中间盲目切割)。
取自文档:
>> "".size
=> 20
>> "".bytesize
=> 80
>> "".truncate_bytes(20)
=> "…"
Ruby 的 String#byteslice 可以与范围一起使用。我建议尝试以下方法:
string.bytslice(0...max_bytesize)
这三个点将允许 max_bytesize 值包含在内。
Fredrick Cheung 的回答是启发此解决方案的绝佳O(n)
起点O(log n)
:
def limit_bytesize(str, max_bytesize)
return str unless str.bytesize > max_bytesize
# find the minimum index that exceeds the bytesize, then subtract 1
just_over = (0...str.size).bsearch { |l| str[0..l].bytesize > max_bytesize }
str[0..(just_over - 1)]
end
我相信这也实现了max_bytesize / 4
该答案中提到的自动加速,因为bsearch
从中间开始。