Elixir语言中 String
相关用法介绍如下。
Elixir 中的字符串是 UTF-8 编码的二进制文件。
Elixir 中的字符串是一系列 Unicode 字符,通常写在双引号字符串之间,例如 "hello"
和 "héllò"
。
如果字符串本身必须有 double-quote,则必须使用反斜杠对双引号进行转义,例如:"this is a string with \"double quotes\""
。
您可以使用
运算符连接两个字符串:<>/2
iex> "hello" <> " " <> "world"
"hello world"
插值
Elixir 中的字符串也支持插值。这允许您使用 #{}
语法在字符串中间放置一些值:
iex> name = "joe"
iex> "hello #{name}"
"hello joe"
任何 Elixir 表达式在插值内都是有效的。如果给定字符串,则按原样插入字符串。如果给出任何其他值,Elixir 将尝试使用
协议将其转换为字符串。例如,这允许从插值输出一个整数:String.Chars
iex> "2 + 2 = #{2 + 2}"
"2 + 2 = 4"
如果您要插入的值无法转换为字符串,因为它没有人工文本表示,则会引发协议错误。
转义字符
除了允许 double-quotes 用反斜杠转义外,字符串还支持以下转义字符:
\a
- 贝尔\b
- 退格\t
- 水平选项卡\n
- 换行(新行)\v
- 垂直选项卡\f
- 换页\r
- 回车\e
- 命令转义\#
- 返回#
字符本身,跳过插值\xNN
- 由十六进制表示的字节NN
\uNNNN
- 由NNNN
表示的 Unicode 代码点
请注意,通常不建议在 Elixir 字符串中使用 \xNN
,因为引入无效的字节序列会使字符串无效。如果您必须通过其十六进制表示来引入字符,最好使用 Unicode 代码点,例如 \uNNNN
。事实上,在对字符串进行低级操作时,理解 Unicode 码位是必不可少的,接下来让我们详细探讨一下。
代码点和字素簇
此模块中的函数根据The Unicode Standard, Version 14.0.0 执行。
根据标准,代码点是单个 Unicode 字符,可以用一个或多个字节表示。
例如,尽管代码点 "é" 是单个字符,但其底层表示使用两个字节:
iex> String.length("é")
1
iex> byte_size("é")
2
此外,该模块还介绍了字素簇的概念(从现在起称为字素)。字形可以由多个代码点组成,读者可以将其视为单个字符。例如,"é" 可以表示为单个“带尖音符的 e”代码点,也可以表示为字母 "e" 后跟“组合尖音符”(两个代码点):
iex> string = "\u0065\u0301"
iex> byte_size(string)
3
iex> String.length(string)
1
iex> String.codepoints(string)
["e", "́"]
iex> String.graphemes(string)
["é"]
虽然上面的例子是由两个字符组成的,但它被用户认为是一个字符。
字形也可以是被某些语言解释为一个的两个字符。例如,某些语言可能会将"ch" 视为单个字符。但是,由于此信息取决于区域设置,因此此模块不考虑它。
通常,此模块中的函数依赖于 Unicode 标准,但不包含任何特定于语言环境的行为。关于字素的更多信息可以在Unicode Standard Annex #29 中找到。
要将二进制文件转换为不同的编码和 Unicode 规范化机制,请参阅 Erlang 的 :unicode
模块。
字符串和二进制操作
根据 Unicode 标准,此模块中的许多函数以线性时间运行,因为它们需要考虑适当的 Unicode 代码点遍历整个字符串。
例如,随着输入的增长,
将花费更长的时间。另一方面,String.length/1
始终以恒定时间运行(即,无论输入大小如何)。Kernel.byte_size/1
这意味着与直接使用二进制文件的更底层操作相比,使用此模块中的函数通常会产生性能成本:
Kernel.binary_part/3
Kernel.bit_size/1
Kernel.byte_size/1
Kernel.is_bitstring/1
Kernel.is_binary/1
- 加上一些用于处理二进制文件(字节)的函数
:binary
模块
在许多情况下,可以避免使用
模块以支持二进制函数或模式匹配。例如,假设您有一个字符串 String
prefix
并且您想从另一个名为 full
的字符串中删除此前缀。
有人可能会想写:
iex> take_prefix = fn full, prefix ->
...> base = String.length(prefix)
...> String.slice(full, base, String.length(full) - base)
...> end
iex> take_prefix.("Mr. John", "Mr. ")
"John"
尽管上面的函数有效,但它的性能很差。要计算字符串的长度,我们需要完全遍历它,所以我们同时遍历prefix
和full
字符串,然后将full
切片,再次遍历。
改进它的第一次尝试可能是使用范围:
iex> take_prefix = fn full, prefix ->
...> base = String.length(prefix)
...> String.slice(full, base..-1)
...> end
iex> take_prefix.("Mr. John", "Mr. ")
"John"
虽然这要好得多(我们不会遍历 full
两次),但它仍然可以改进。在这种情况下,由于我们想从字符串中提取子字符串,我们可以使用
和Kernel.byte_size/1
,因为我们不可能在由超过一个字节组成的代码点的中间进行切片:Kernel.binary_part/3
iex> take_prefix = fn full, prefix ->
...> base = byte_size(prefix)
...> binary_part(full, base, byte_size(full) - base)
...> end
iex> take_prefix.("Mr. John", "Mr. ")
"John"
或者干脆使用模式匹配:
iex> take_prefix = fn full, prefix ->
...> base = byte_size(prefix)
...> <<_::binary-size(base), rest::binary>> = full
...> rest
...> end
iex> take_prefix.("Mr. John", "Mr. ")
"John"
另一方面,如果您想根据整数值动态地对字符串进行切片,那么使用
是最好的选择,因为它可以保证我们不会错误地将有效代码点拆分为多个字节。String.slice/3
整数代码点
尽管代码点表示为整数,但此模块将其编码格式的代码点表示为字符串。例如:
iex> String.codepoints("olá")
["o", "l", "á"]
有几种方法可以检索字符代码点。可以使用?
构造:
iex> ?o
111
iex> ?á
225
或者也通过模式匹配:
iex> <<aacute::utf8>> = "á"
iex> aacute
225
正如我们在上面看到的,代码点可以通过它们的十六进制代码插入到字符串中:
iex> "ol\u00E1"
"olá"
最后,要将字符串转换为整数代码点列表,在 Elixir 中称为 "charlists",您可以调用 String.to_charlist
:
iex> String.to_charlist("olá")
[111, 108, 225]
Self-synchronization
UTF-8 编码是self-synchronizing。这意味着如果遇到格式错误的数据(即根据编码定义不可能的数据),则只需拒绝一个代码点。
此模块依赖此行为来忽略此类无效字符。例如,
将返回正确的结果,即使输入了无效的代码点也是如此。length/1
换句话说,该模块期望在其他地方检测到无效数据,通常是在从外部源检索数据时。例如,从数据库读取字符串的驱动程序将负责检查编码的有效性。
可用于将字符串分成有效和无效部分。String.chunk/2
编译二进制模式
此模块中的许多函数都使用模式。例如,
可以在给定模式的情况下将一个字符串拆分为多个字符串。此模式可以是字符串、字符串列表或编译模式:String.split/3
iex> String.split("foo bar", " ")
["foo", "bar"]
iex> String.split("foo bar!", [" ", "!"])
["foo", "bar", ""]
iex> pattern = :binary.compile_pattern([" ", "!"])
iex> String.split("foo bar!", pattern)
["foo", "bar", ""]
当一次又一次地完成相同的匹配时,编译模式很有用。请注意,编译后的模式不能存储在模块属性中,因为模式是在运行时生成的,并且不会在编译时存活。
相关用法
- Elixir StringIO.flush用法及代码示例
- Elixir String.contains?用法及代码示例
- Elixir String.reverse用法及代码示例
- Elixir String.to_integer用法及代码示例
- Elixir String.pad_trailing用法及代码示例
- Elixir String.split_at用法及代码示例
- Elixir String.valid?用法及代码示例
- Elixir String.replace_prefix用法及代码示例
- Elixir String.printable?用法及代码示例
- Elixir String.replace_trailing用法及代码示例
- Elixir String.trim_leading用法及代码示例
- Elixir String.length用法及代码示例
- Elixir String.replace_suffix用法及代码示例
- Elixir String.first用法及代码示例
- Elixir String.upcase用法及代码示例
- Elixir String.graphemes用法及代码示例
- Elixir String.at用法及代码示例
- Elixir String.replace用法及代码示例
- Elixir String.next_grapheme用法及代码示例
- Elixir String.next_codepoint用法及代码示例
- Elixir String.myers_difference用法及代码示例
- Elixir String.trim_trailing用法及代码示例
- Elixir String.normalize用法及代码示例
- Elixir String.ends_with?用法及代码示例
- Elixir String.downcase用法及代码示例
注:本文由纯净天空筛选整理自elixir-lang.org大神的英文原创作品 String。非经特殊声明,原始代码版权归原作者所有,本译文未经允许或授权,请勿转载或复制。