14 Strings
14.1 Introduction
到目前为止,您已使用过大量字符串却未深入了解其细节。 现在我们将深入探索字符串的本质特性,并掌握一些强大的字符串处理工具。
首先我们将详细介绍创建字符串和字符向量的方法。 接着学习如何从数据中生成字符串,以及反过来如何从数据中提取字符串。 随后我们将探讨处理单个字母的工具。 本章最后将介绍针对单个字母操作的函数,并简要讨论英语使用习惯在处理其他语言时可能导致的认知偏差。
下一章我们将继续研究字符串,届时您将深入了解正则表达式的强大功能。
14.1.1 Prerequisites
本章我们将使用 stringr 包中的函数,该包是核心 tidyverse 的组成部分。 我们还会使用 babynames 数据集,因为它提供了一些有趣的字符串供我们操作。
您可以快速识别何时在使用 stringr 函数,因为所有 stringr 函数都以 str_
开头。 如果您使用 RStudio,这个特性尤其有用,因为输入 str_
会触发自动补全功能,帮助您回忆可用的函数。
14.2 Creating a string
本书前面曾顺带创建过字符串但未讨论细节。 首先可以使用单引号('
)或双引号("
)创建字符串。 两者行为并无差异,根据一致性原则,tidyverse style guide 建议使用 "
,除非字符串本身包含多个 "
。
string1 <- "This is a string"
string2 <- 'If I want to include a "quote" inside a string, I use single quotes'
如果忘记闭合引号,你会看到 +
这个续行提示符:
> "This is a string without a closing quote
+
+
+ HELP I'M STUCK IN A STRING
若遇到这种情况且无法判断该闭合哪个引号,请按 Esc 键取消操作后重试。
14.2.1 Escapes
要在字符串中包含原义的单引号或双引号,可使用反斜杠 \
进行转义:
double_quote <- "\"" # or '"'
single_quote <- '\'' # or "'"
因此若需要在字符串中包含原义的反斜杠,则必须对其进行转义:"\\"
:
backslash <- "\\"
请注意字符串的打印显示结果与其实际内容不同,因为打印显示会呈现转义符(换句话说,当打印字符串时,你可以复制粘贴输出内容来重建该字符串)。 To see the raw contents of the string, use str_view()
1: 要查看字符串的原始内容,请使用 str_view()
2:
14.2.2 Raw strings
创建包含多个引号或反斜杠的字符串会很快变得令人困惑。 为了说明这个问题,我们创建一个包含定义 double_quote
和 single_quote
变量代码块内容的字符串:
tricky <- "double_quote <- \"\\\"\" # or '\"'
single_quote <- '\\'' # or \"'\""
str_view(tricky)
#> [1] │ double_quote <- "\"" # or '"'
#> │ single_quote <- '\'' # or "'"
这里出现了大量反斜杠! (这种情况有时被称为 leaning toothpick syndrome。)要避免转义,可以使用原始字符串3:
tricky <- r"(double_quote <- "\"" # or '"'
single_quote <- '\'' # or "'")"
str_view(tricky)
#> [1] │ double_quote <- "\"" # or '"'
#> │ single_quote <- '\'' # or "'"
原始字符串通常以 r"(
开头,以 )"
结尾。 但如果字符串包含 )"
,则可以改用 r"[]"
或 r"{}"
,若仍不足以处理,还可以插入任意数量的破折号使起始和结束标记唯一,例如 `r"--()--"
、`r"---()---"
等。原始字符串的灵活性足以处理任何文本。
14.2.3 Other special characters
除了 \"
、\'
和 \\
之外,还有一些其他可能用到的特殊字符。 最常见的是\n
(换行符)和\t
(制表符)。 有时你还会看到以\u
或\U
开头的Unicode转义字符串。 这是一种可在所有系统中编写非英文字符的方法。 你可以在?Quotes
中查看其他特殊字符的完整列表。
请注意,str_view()
使用蓝色背景来显示制表符以便更容易识别。 处理文本时的挑战之一在于空白字符可能存在多种表现形式,这种背景色有助于您察觉异常情况。
14.2.4 Exercises
-
创建包含以下值的字符串:
He said "That's amazing!"
\a\b\c\d
\\\\\\
-
在你的 R 会话中创建该字符串并打印。 特殊字符 “\u00a0” 会如何显示?
str_view()
会如何呈现它? 你能通过搜索查到这个特殊字符的含义吗?x <- "This\u00a0is\u00a0tricky"
14.3 Creating many strings from data
现在你已学会手动创建字符串的基础知识,接下来我们将详细探讨如何基于其他字符串生成新字符串。 这将帮助你解决一个常见问题:如何将自编文本与数据框中的字符串组合。 例如,你可以将 “Hello” 与 name
变量结合来创建问候语。 我们将演示如何使用 str_c()
和 str_glue()
实现这一功能,以及如何将它们 mutate()
结合使用。 这自然引出了关于哪些字符串函数可与 summarize()
配合使用的问题,因此本节最后将讨论字符串的汇总函数 str_flatten()
。
14.3.1 str_c()
str_c()
可接受任意数量的向量作为参数,并返回一个字符向量:
str_c()
与基础的 paste0()
非常相似,但遵循 tidyverse 的循环和缺失值传递规则,专为与 mutate()
配合使用而设计:
若希望以其他方式显示缺失值,可使用 coalesce()
进行替换。根 据具体需求,可选择在 str_c()
内部或外部使用:
df |>
mutate(
greeting1 = str_c("Hi ", coalesce(name, "you"), "!"),
greeting2 = coalesce(str_c("Hi ", name, "!"), "Hi!")
)
#> # A tibble: 4 × 3
#> name greeting1 greeting2
#> <chr> <chr> <chr>
#> 1 Flora Hi Flora! Hi Flora!
#> 2 David Hi David! Hi David!
#> 3 Terra Hi Terra! Hi Terra!
#> 4 <NA> Hi you! Hi!
14.3.2 str_glue()
如果你使用 str_c()
混合许多固定字符串和变量字符串,会发现需要输入大量 "
,导致代码整体目标难以看清。 glue package 通过str_glue()
4提供了另一种解决方案:只需输入一个具有特殊功能的字符串——任何在 {}
内的内容都会像在引号外一样被解析执行:
如你所见,str_glue()
目前会将缺失值转换为 "NA"
字符串,这不幸地导致了与str_c()
的不一致。
你可能也好奇如果需要在字符串中包含常规的{
或}
时会发生什么。 如果你猜测需要以某种方式转义它们,那么你的思路是正确的。 技巧在于glue采用了略微不同的转义技术:不是使用像\
这样的特殊字符作为前缀,而是将特殊字符加倍:
14.3.3 str_flatten()
str_c()
和 str_glue()
与 mutate()
配合良好,因为它们的输出长度与输入相同。 如果你需要适用于 summarize()
的函数,即总是返回单个字符串? 这就是str_flatten()
5的功能:它接收字符向量并将向量的每个元素组合成单个字符串:
str_flatten(c("x", "y", "z"))
#> [1] "xyz"
str_flatten(c("x", "y", "z"), ", ")
#> [1] "x, y, z"
str_flatten(c("x", "y", "z"), ", ", last = ", and ")
#> [1] "x, y, and z"
这使得它能与 summarize()
良好配合:
df <- tribble(
~ name, ~ fruit,
"Carmen", "banana",
"Carmen", "apple",
"Marvin", "nectarine",
"Terence", "cantaloupe",
"Terence", "papaya",
"Terence", "mandarin"
)
df |>
group_by(name) |>
summarize(fruits = str_flatten(fruit, ", "))
#> # A tibble: 3 × 2
#> name fruits
#> <chr> <chr>
#> 1 Carmen banana, apple
#> 2 Marvin nectarine
#> 3 Terence cantaloupe, papaya, mandarin
14.3.4 Exercises
14.4 Extracting data from strings
将多个变量压缩到单个字符串中的情况十分常见。 在本节中,您将学习使用四个 tidyr 函数来提取这些变量:
df |> separate_longer_delim(col, delim)
df |> separate_longer_position(col, width)
df |> separate_wider_delim(col, delim, names)
df |> separate_wider_position(col, widths)
仔细观察可以发现这里存在通用模式:separate_
,接着 longer
或 wider
,然后 _
,最后是 delim
或 position
。 这是因为这四个函数由两个更简单的基础操作组合而成:
- 与
pivot_longer()
和pivot_wider()
类似,_longer
函数通过创建新行使输入数据框变长,而_wider
函数通过生成新列使输入数据框变宽。 -
delim
使用分隔符(如", "
或" "
)拆分字符串;position
按指定宽度进行分割,如c(3, 5, 2)
。
我们将在 Chapter 15 讨论该函数家族的最后一个成员 separate_wider_regex()
。 它是 wider
函数中最灵活的,但需要先了解正则表达式才能使用。
接下来两节将介绍这些 separate 函数的基本原理,先讲解拆分为行(相对简单),再讲解拆分为列。 最后我们将讨论 wider
函数提供的诊断问题工具。
14.4.1 Separating into rows
当每行的组成部分数量不一致时,将字符串拆分为行往往最为实用。 最常见的情况是需要使用 separate_longer_delim()
根据分隔符进行拆分:
df1 <- tibble(x = c("a,b,c", "d,e", "f"))
df1 |>
separate_longer_delim(x, delim = ",")
#> # A tibble: 6 × 1
#> x
#> <chr>
#> 1 a
#> 2 b
#> 3 c
#> 4 d
#> 5 e
#> 6 f
在实际应用中 separate_longer_position()
较为少见,但一些旧数据集确实会采用非常紧凑的格式,每个字符都被用来记录一个值:
df2 <- tibble(x = c("1211", "131", "21"))
df2 |>
separate_longer_position(x, width = 1)
#> # A tibble: 9 × 1
#> x
#> <chr>
#> 1 1
#> 2 2
#> 3 1
#> 4 1
#> 5 1
#> 6 3
#> # ℹ 3 more rows
14.4.2 Separating into columns
当每个字符串包含固定数量的组成部分且需要将其展开为列时,将字符串拆分为列最为实用。 这比对应的 longer
函数稍复杂些,因为需要为列命名。 例如在以下数据集中,x
由代码、版本号和年份组成,以"."
分隔。 使用 separate_wider_delim()
时,我们需要在两个参数中分别指定分隔符和列名:
df3 <- tibble(x = c("a10.1.2022", "b10.2.2011", "e15.1.2015"))
df3 |>
separate_wider_delim(
x,
delim = ".",
names = c("code", "edition", "year")
)
#> # A tibble: 3 × 3
#> code edition year
#> <chr> <chr> <chr>
#> 1 a10 1 2022
#> 2 b10 2 2011
#> 3 e15 1 2015
如果某个特定片段不需要,可以使用 NA
名称将其从结果中省略:
df3 |>
separate_wider_delim(
x,
delim = ".",
names = c("code", NA, "year")
)
#> # A tibble: 3 × 2
#> code year
#> <chr> <chr>
#> 1 a10 2022
#> 2 b10 2011
#> 3 e15 2015
separate_wider_position()
的工作方式略有不同,因为通常需要指定每列的宽度。 因此需要提供一个命名的整数向量,其中名称表示新列的名称,值表示该列占据的字符数。 通过不命名某些值可以将其从输出中省略:
df4 <- tibble(x = c("202215TX", "202122LA", "202325CA"))
df4 |>
separate_wider_position(
x,
widths = c(year = 4, age = 2, state = 2)
)
#> # A tibble: 3 × 3
#> year age state
#> <chr> <chr> <chr>
#> 1 2022 15 TX
#> 2 2021 22 LA
#> 3 2023 25 CA
14.4.3 Diagnosing widening problems
separate_wider_delim()
6 要求固定且已知的列数。 如果某些行没有预期数量的片段会发生什么? 可能存在两种问题:片段过少或过多,因此 separate_wider_delim()
提供了两个参数来帮助处理:too_few
和 too_many
。 我们首先通过以下示例数据集看看 too_few
的情况:
df <- tibble(x = c("1-1-1", "1-1-2", "1-3", "1-3-2", "1"))
df |>
separate_wider_delim(
x,
delim = "-",
names = c("x", "y", "z")
)
#> Error in `separate_wider_delim()`:
#> ! Expected 3 pieces in each element of `x`.
#> ! 2 values were too short.
#> ℹ Use `too_few = "debug"` to diagnose the problem.
#> ℹ Use `too_few = "align_start"/"align_end"` to silence this message.
您会注意到出现了错误,但该错误提供了一些后续操作建议。 让我们从调试问题开始:
debug <- df |>
separate_wider_delim(
x,
delim = "-",
names = c("x", "y", "z"),
too_few = "debug"
)
#> Warning: Debug mode activated: adding variables `x_ok`, `x_pieces`, and
#> `x_remainder`.
debug
#> # A tibble: 5 × 6
#> x y z x_ok x_pieces x_remainder
#> <chr> <chr> <chr> <lgl> <int> <chr>
#> 1 1-1-1 1 1 TRUE 3 ""
#> 2 1-1-2 1 2 TRUE 3 ""
#> 3 1-3 3 <NA> FALSE 2 ""
#> 4 1-3-2 3 2 TRUE 3 ""
#> 5 1 <NA> <NA> FALSE 1 ""
使用调试模式时,输出结果会添加三个额外列:x_ok
、x_pieces
和 x_remainder
(若分离不同名称的变量,前缀会相应变化)。 此处 x_ok
可帮助快速定位失败的输入:
debug |> filter(!x_ok)
#> # A tibble: 2 × 6
#> x y z x_ok x_pieces x_remainder
#> <chr> <chr> <chr> <lgl> <int> <chr>
#> 1 1-3 3 <NA> FALSE 2 ""
#> 2 1 <NA> <NA> FALSE 1 ""
x_pieces
显示找到的片段数量,与预期值 3(即names
的长度)相比较。 当片段过少时 x_remainder
没有实际用处,但我们稍后会再次见到它。
有时查看这些调试信息能发现分隔策略的问题,或表明在分离前需要更多预处理。 此时应在上游解决问题,并确保移除 too_few = "debug"
以保证新问题会触发报错。
其他情况下,你可能希望用 NA
填充缺失片段后继续处理。 这时可以使用 too_few = "align_start"
和 too_few = "align_end"
来控制 NA
的填充位置:
df |>
separate_wider_delim(
x,
delim = "-",
names = c("x", "y", "z"),
too_few = "align_start"
)
#> # A tibble: 5 × 3
#> x y z
#> <chr> <chr> <chr>
#> 1 1 1 1
#> 2 1 1 2
#> 3 1 3 <NA>
#> 4 1 3 2
#> 5 1 <NA> <NA>
片段过多时同样适用以下原则:
df <- tibble(x = c("1-1-1", "1-1-2", "1-3-5-6", "1-3-2", "1-3-5-7-9"))
df |>
separate_wider_delim(
x,
delim = "-",
names = c("x", "y", "z")
)
#> Error in `separate_wider_delim()`:
#> ! Expected 3 pieces in each element of `x`.
#> ! 2 values were too long.
#> ℹ Use `too_many = "debug"` to diagnose the problem.
#> ℹ Use `too_many = "drop"/"merge"` to silence this message.
但现在,当我们调试结果时,可以看到 x_remainder
的作用:
debug <- df |>
separate_wider_delim(
x,
delim = "-",
names = c("x", "y", "z"),
too_many = "debug"
)
#> Warning: Debug mode activated: adding variables `x_ok`, `x_pieces`, and
#> `x_remainder`.
debug |> filter(!x_ok)
#> # A tibble: 2 × 6
#> x y z x_ok x_pieces x_remainder
#> <chr> <chr> <chr> <lgl> <int> <chr>
#> 1 1-3-5-6 3 5 FALSE 4 -6
#> 2 1-3-5-7-9 3 5 FALSE 5 -7-9
处理过多片段时选项略有不同:可以静默”丢弃”额外片段,或将其全部”合并”到最后一列:
df |>
separate_wider_delim(
x,
delim = "-",
names = c("x", "y", "z"),
too_many = "drop"
)
#> # A tibble: 5 × 3
#> x y z
#> <chr> <chr> <chr>
#> 1 1 1 1
#> 2 1 1 2
#> 3 1 3 5
#> 4 1 3 2
#> 5 1 3 5
df |>
separate_wider_delim(
x,
delim = "-",
names = c("x", "y", "z"),
too_many = "merge"
)
#> # A tibble: 5 × 3
#> x y z
#> <chr> <chr> <chr>
#> 1 1 1 1
#> 2 1 1 2
#> 3 1 3 5-6
#> 4 1 3 2
#> 5 1 3 5-7-9
14.5 Letters
本节我们将介绍处理字符串内单个字母的函数。 您将学习如何获取字符串长度、提取子字符串,以及在图表和表格中处理长字符串的方法。
14.5.1 Length
str_length()
可显示字符串包含的字母数量:
str_length(c("a", "R for data science", NA))
#> [1] 1 18 NA
您可以将其与 count()
结合使用来统计美国婴儿名字的长度分布,再通过 filter()
查看最长的名字,目前最长名字有 15 个字母7:
babynames |>
count(length = str_length(name), wt = n)
#> # A tibble: 14 × 2
#> length n
#> <int> <int>
#> 1 2 338150
#> 2 3 8589596
#> 3 4 48506739
#> 4 5 87011607
#> 5 6 90749404
#> 6 7 72120767
#> # ℹ 8 more rows
babynames |>
filter(str_length(name) == 15) |>
count(name, wt = n, sort = TRUE)
#> # A tibble: 34 × 2
#> name n
#> <chr> <int>
#> 1 Franciscojavier 123
#> 2 Christopherjohn 118
#> 3 Johnchristopher 118
#> 4 Christopherjame 108
#> 5 Christophermich 52
#> 6 Ryanchristopher 45
#> # ℹ 28 more rows
14.5.2 Subsetting
您可以使用 str_sub(string, start, end)
来提取字符串的部分内容,其中 start
和 end
位置指定了子串的开始和结束点。 start
和 end
参数具有包含性,因此返回字符串的长度将为 end - start + 1
:
您可以使用负数值从字符串末尾向前计数:-1 表示最后一个字符,-2 表示倒数第二个字符,依此类推:
str_sub(x, -3, -1)
#> [1] "ple" "ana" "ear"
请注意,如果字符串过短,str_sub()
不会报错:它会尽可能返回可用内容:
str_sub("a", 1, 5)
#> [1] "a"
我们可以结合 str_sub()
与 mutate()
来找出每个名字的首字母和尾字母:
babynames |>
mutate(
first = str_sub(name, 1, 1),
last = str_sub(name, -1, -1)
)
#> # A tibble: 1,924,665 × 7
#> year sex name n prop first last
#> <dbl> <chr> <chr> <int> <dbl> <chr> <chr>
#> 1 1880 F Mary 7065 0.0724 M y
#> 2 1880 F Anna 2604 0.0267 A a
#> 3 1880 F Emma 2003 0.0205 E a
#> 4 1880 F Elizabeth 1939 0.0199 E h
#> 5 1880 F Minnie 1746 0.0179 M e
#> 6 1880 F Margaret 1578 0.0162 M t
#> # ℹ 1,924,659 more rows
14.5.3 Exercises
- 在计算婴儿名字长度分布时,我们为何使用
wt = n
参数? - 运用
str_length()
和str_sub()
函数提取每个婴儿名字的中间字母。如果字符串包含偶数个字符,您将如何处理? - 婴儿名字的长度随时间推移是否存在显著趋势?首字母和尾字母的流行度又有哪些变化?
14.6 Non-English text
迄今为止,我们主要关注英语文本的处理,这类文本操作起来特别方便,原因有二。 首先,英文字母表相对简单,仅包含 26 个字母。 其次(或许更重要的),当今使用的计算基础设施主要由英语使用者设计。 遗憾的是,我们无法全面探讨非英语语言的处理,但仍希望提醒您注意可能遇到的几个主要挑战:字符编码、字母变体以及依赖区域设置的函数。
14.6.1 Encoding
处理非英语文本时,第一个挑战通常是编码(encoding)问题。 要理解其中的原理,我们需要深入探究计算机如何表示字符串。 在 R 中,可以使用 charToRaw()
获取字符串的底层表示:
charToRaw("Hadley")
#> [1] 48 61 64 6c 65 79
这六个十六进制数字分别代表一个字母:48
是H,61
是a,依此类推。 从十六进制数字到字符的映射称为编码,这里的编码叫做 ASCII。 ASCII 能出色地表示英文字符,因为它是美国信息交换标准代码。
但对非英语语言来说情况就不那么简单了。 在计算机早期阶段,存在许多相互竞争的非英语字符编码标准。 例如欧洲曾有两种不同编码:Latin1(即ISO-8859-1)用于西欧语言,而Latin2(即ISO-8859-2)用于中欧语言。 在Latin1中,字节b1
是”±“,但在Latin2中却是”ą”! 幸运的是,如今有一个几乎无处不在的标准:UTF-8。 UTF-8 可以编码当今人类使用的几乎所有字符,以及许多额外符号(如表情符号)。
readr 在所有地方都使用 UTF-8。 这是个很好的默认设置,但对于不使用 UTF-8 的旧系统产生的数据会读取失败。 发生这种情况时,打印字符串会显示异常。 有时可能只是一两个字符乱码,有时则会得到完全无法识别的乱码。 例如以下是两个采用非常见编码的内联CSV文件8:
要正确读取这些数据,需要通过locale
参数指定编码:
如何找到正确的编码? 如果幸运的话,数据文档的某个地方会注明编码方式。 但遗憾的是这种情况很少见,因此 readr 提供 guess_encoding()
来帮助您识别。 虽然这种方法并非万无一失,且文本量越大效果越好(与当前示例不同),但作为起点是合理的。 通常需要尝试多种编码才能找到正确的方案。
编码是一个丰富而复杂的主题;我们在此仅触及表面。 若想深入了解,建议阅读 http://kunststube.net/encoding/.
14.6.2 Letter variations
处理带重音符号的语言时,确定字母位置(例如使用 str_length()
和 str_sub()
)会面临重大挑战,因为带重音字母可能被编码为单个字符(如ü),也可能通过组合无重音字母(如u)和变音符号(如¨)形成两个字符。 例如以下代码展示了两种看起来完全相同的ü表示方式:
但两个字符串的长度不同,且它们的首字符也不同:
str_length(u)
#> [1] 1 2
str_sub(u, 1, 1)
#> [1] "ü" "u"
最后要注意的是:使用 ==
比较这些字符串时会被解析为不同,而 stringr 中的实用函数 str_equal()
能识别它们具有相同显示效果:
u[[1]] == u[[2]]
#> [1] FALSE
str_equal(u[[1]], u[[2]])
#> [1] TRUE
14.6.3 Locale-dependent functions
最后要注意的是:有部分stringr函数的行为会依赖于区域(locale)设置。 区域设置类似于语言选项,但包含可选的地区标识符以处理语言内的地域差异。 区域设置由小写语言代码指定,可选择后接_
和大写地区标识符。 例如”en”代表英语,“en_GB”代表英式英语,“en_US”代表美式英语。 若不清楚所需语言代码,Wikipedia提供详细列表,也可通过stringi::stri_locale_list()
查看stringr支持的区域设置。
Base R 的字符串函数会自动使用操作系统设置的区域。 这意味着 base R 字符串函数会按您预期的语言方式工作,但若与不同国家的用户共享代码,其运行结果可能不同。 为避免此问题,stringr 默认采用”en”区域设置(英语规则),需要您显式指定locale
参数来覆盖。 幸运的是,有两类函数特别受区域设置影响:大小写转换和排序。
不同语言的大小写转换规则存在差异。 例如土耳其语有两个i:带点和不带点的。 由于这是两个不同的字母,它们的大写形式也不同:
str_to_upper(c("i", "ı"))
#> [1] "I" "I"
str_to_upper(c("i", "ı"), locale = "tr")
#> [1] "İ" "I"
字符串排序取决于字母表顺序,而不同语言的字母表顺序并不相同9! 例如在捷克语中,“ch”是一个复合字母,在字母表中排在h
之后。
使用dplyr::arrange()
进行字符串排序时也会出现这种情况,这就是为什么该函数同样具有locale
参数。
14.7 Summary
本章您已了解 stringr 包的部分功能:如何创建、组合和提取字符串,以及处理非英语字符串时可能面临的挑战。 现在该学习字符串处理中最重要且强大的工具之一:正则表达式。 正则表达式是一种非常简洁但极具表现力的语言,用于描述字符串中的模式,这将是下一章的主题。
或使用 base R 函数
writeLines()
。↩︎或使用 base R 函数
writeLines()
。↩︎在 R 4.0.0 及以上版本获取.↩︎
如果你没有使用 stringr,也可以直接通过
glue::glue()
调用该功能。↩︎同样原则适用于
separate_wider_position()
和separate_wider_regex()
。↩︎查看这些条目时,我们推测 babynames 数据可能删除了空格或连字符,并在 15 个字母后进行了截断。↩︎
此处我使用特殊的
\x
将二进制数据直接编码到字符串中。↩︎对中文等没有字母系统的语言进行排序则更为复杂。↩︎