Julia 教程 / 字符串与正则表达式
字符串与正则表达式
1. 字符串基础
Julia 的 String 类型采用 UTF-8 编码,支持完整的 Unicode 字符集。
字符串字面量
# 标准字符串(双引号)
s1 = "Hello, Julia!"
# 字符串类型
typeof(s1) # String
# 单个字符是 Char 类型,不是 String
c = 'A'
typeof(c) # Char
# 多行字符串(三引号)
s2 = """
第一行
第二行
第三行
"""
# 注意:三引号会去掉公共前导空白
# 原始字符串(不处理转义和插值)
s3 = raw"C:\Users\test\n\path"
# "C:\\Users\\test\\n\\path"
转义字符
| 转义序列 | 含义 |
|---|---|
\n | 换行 |
\t | 制表符 |
\\ | 反斜杠 |
\" | 双引号 |
\0 | 空字符 |
\uXXXX | Unicode 字符(4 位十六进制) |
\UXXXXXXXX | Unicode 字符(8 位十六进制) |
\$ | 转义 $(防止插值) |
println("第一行\n第二行")
println("路径: C:\\Users\\test")
println("价格: \$100") # $100
println("Unicode: \u03b1") # α
2. 字符串索引与 UTF-8 编码
⚠️ 关键区别: Julia 的字符串索引基于字节位置而非字符位置。
s = "Hello, 世界!"
# 字节数(UTF-8 编码后)
sizeof(s) # 15(英文 1 字节/字符,中文 3 字节/字符)
# 字符数
length(s) # 10
# 索引(字节位置)
s[1] # 'H'(字节位置 1)
s[8] # '世'(字节位置 8)
s[9] # ERROR: 字节位置 9 不是有效的起始位置
# 获取有效索引
nextind(s, 8) # 11('世' 之后的下一个有效索引)
prevind(s, 8) # 7(' ' 的位置)
# 遍历字符(安全方式)
for c in s
println(c)
end
# 获取字符的字节范围
ncodeunits(s) # 15(字节数)
codeunit(s, 1) # 72('H' 的 ASCII 码)
⚠️ 注意: 直接对中文字符串进行
s[i]索引可能出错。使用s[n]时必须确保n是有效的 UTF-8 字节起始位置。推荐使用迭代或collect(s)获取字符列表。
# 错误示范
s = "你好"
s[2] # ERROR: StringIndexError
# 正确方式
collect(s) # ['你', '好']
[s[i] for i in eachindex(s)] # 通过 eachindex 获取有效索引
3. SubString 与视图
SubString 是原字符串的一个视图,不复制底层数据:
s = "Hello, World!"
# 创建子字符串
sub = SubString(s, 1, 5) # "Hello"
typeof(sub) # SubString{String}
# 使用范围索引
sub2 = s[1:5] # "Hello"
sub3 = s[8:end] # "World!"
# 两者等价
s[1:5] == SubString(s, 1, 5) # true
# SubString 不分配额外内存(共享底层数据)
# 适合处理大文本
💡 提示: 处理大文本时,使用
SubString而非s[start:stop]可以避免内存拷贝。但要注意,持有SubString会阻止底层字符串被垃圾回收。
4. 字符串常用方法
查找
s = "Hello, Julia! Hello, World!"
# 查找子字符串
findfirst("Julia", s) # 8:12
findfirst("Julia", s) !== nothing # true
# 查找所有匹配
findall("Hello", s) # [1:5, 15:19]
# 字符查找
findfirst('J', s) # 8
# 是否包含
contains(s, "Julia") # true
startswith(s, "Hello") # true
endswith(s, "!") # true
# 出现次数
count("l", s) # 4
替换
s = "Hello, World!"
# 替换(返回新字符串)
replace(s, "World" => "Julia")
# "Hello, Julia!"
# 替换所有
replace(s, "l" => "L")
# "HeLLo, WorLd!"
# 替换指定次数
replace(s, "l" => "L", count=1)
# "HeLlo, World!"
# 多个替换
replace(s, "Hello" => "Hi", "World" => "Julia")
# "Hi, Julia!"
分割与连接
# 分割
split("a,b,c,d", ",") # ["a", "b", "c", "d"]
split("Hello World") # ["Hello", "World"]
split("Hello World") # ["Hello", "", "World"](连续空格产生空串)
split("Hello World", limit=2) # ["Hello", "World"]
# 连接
join(["a", "b", "c"], ",") # "a,b,c"
join(["Hello", "World"], " ") # "Hello World"
join(1:5, ", ") # "1, 2, 3, 4, 5"
# 字符串插值($ 语法)
name = "Julia"
version = 1.10
"Hello from $name v$version"
# "Hello from Julia v1.10"
"$((1 + 2) * 3)" # "9"(表达式需要括号)
格式化
# 填充与对齐
lpad("42", 6, '0') # "000042"
rpad("Hi", 10, '.') # "Hi........"
# 大小写
uppercase("hello") # "HELLO"
lowercase("HELLO") # "hello"
titlecase("hello world") # "Hello World"
uppercasefirst("hello") # "Hello"
# 去空白
strip(" hello ") # "hello"
lstrip(" hello ") # "hello "
rstrip(" hello ") # " hello"
# 重复
repeat("ha", 3) # "hahaha"
# 包含测试
occursin("Julia", "I love Julia") # true
5. 正则表达式
创建正则表达式
# 使用 r"" 字面量
r1 = r"\d+" # 匹配一个或多个数字
r2 = r"^[A-Z][a-z]+$" # 首字母大写的单词
r3 = r"(?i)hello" # 不区分大小写
# 使用 Regex 构造函数(动态模式)
pattern = "\\d+"
r4 = Regex(pattern)
# 带标志的正则
r5 = r"hello"i # 不区分大小写
r6 = r"^line"m # 多行模式(^ 匹配每行开头)
r7 = r"a.b"s # 单行模式(. 匹配换行符)
match 函数
# 基本匹配
m = match(r"\d+", "abc123def456")
# RegexMatch("123")
m.match # "123"(匹配的内容)
m.offset # 4(起始位置)
m.captures # String[](捕获组,此处无)
# 带捕获组
m = match(r"(\d+)-(\d+)", "电话: 123-4567")
m.match # "123-4567"
m[1] # "123"
m[2] # "4567"
m.captures # ["123", "4567"]
# 命名捕获
m = match(r"(?<year>\d{4})-(?<month>\d{2})", "2024-01")
m[:year] # "2024"
m[:month] # "01"
# 无匹配返回 nothing
m = match(r"\d+", "no numbers here")
m === nothing # true
eachmatch — 查找所有匹配
text = "日期: 2024-01-15, 2024-02-20, 2024-03-10"
# 查找所有日期
for m in eachmatch(r"\d{4}-\d{2}-\d{2}", text)
println(m.match)
end
# 2024-01-15
# 2024-02-20
# 2024-03-10
# 收集为数组
dates = [m.match for m in eachmatch(r"\d{4}-\d{2}-\d{2}", text)]
# ["2024-01-15", "2024-02-20", "2024-03-10"]
replace 与正则
# 替换匹配内容
text = "Call 555-1234 or 555-5678"
replace(text, r"\d{4}-\d{4}" => "XXX-XXXX")
# "Call XXX-XXXX or XXX-XXXX"
# 使用函数替换
replace(text, r"\d+" => m -> string(parse(Int, m) * 2))
# "Call 1110-2468 or 1110-11356"
6. Regex 对象方法
r = r"\d{3}-\d{4}"
# 正则属性
r.pattern # "\\d{3}-\\d{4}"(原始模式字符串)
r.compile_options # 编译选项
# 测试匹配(返回 Bool)
occursin(r, "555-1234") # true
occursin(r, "555-123") # false
# 在 REPL 中查看正则
julia> r"\d+"
r"\\d+"
7. 字符 Char 操作
# 字符分类
isletter('a') # true
isdigit('7') # true
isnumeric('3') # true
isuppercase('A') # true
islowercase('a') # true
isalpha('x') # true
isalnum('x') # true
isspace(' ') # true
ispunct('!') # true
isascii('A') # true
isascii('中') # false
# 字符转换
uppercase('a') # 'A'
lowercase('A') # 'a'
# 字符运算
'A' + 1 # 'B'
'A' < 'B' # true
'A' < 'a' # true(大写字母码值更小)
# 字符与整数互转
Int('A') # 65
Char(65) # 'A'
# ASCII 字符范围
'a':'z' # 'a':1:'z'
collect('a':'f') # ['a', 'b', 'c', 'd', 'e', 'f']
8. 字符串插值详解
# 基本插值
x = 42
"The answer is $x" # "The answer is 42"
# 表达式插值(需括号)
"The answer is $(2 * 21)" # "The answer is 42"
# 函数调用插值
"Length of hello: $(length("hello"))" # "Length of hello: 5"
# 复杂表达式
data = [1, 2, 3, 4, 5]
"Sum: $(sum(data)), Mean: $(sum(data)/length(data))"
# "Sum: 15, Mean: 3.0"
# 转义 $ 符号
price = 100
"价格: \$$price" # "价格: $100"
9. 字符串处理实战
实战一:日志解析
# 解析 Nginx 访问日志
log_line = """192.168.1.1 - - [15/Jan/2024:10:30:00 +0800] "GET /api/users HTTP/1.1" 200 1234"""
function parse_nginx_log(line)
pattern = r"^(\S+) \S+ \S+ \[([^\]]+)\] \"(\S+) (\S+) [^\"]+\" (\d{3}) (\d+)"
m = match(pattern, line)
if m !== nothing
return (
ip = m[1],
time = m[2],
method = m[3],
path = m[4],
status = parse(Int, m[5]),
size = parse(Int, m[6])
)
end
return nothing
end
result = parse_nginx_log(log_line)
# (ip = "192.168.1.1", time = "15/Jan/2024:10:30:00 +0800",
# method = "GET", path = "/api/users", status = 200, size = 1234)
实战二:URL 提取
text = """
访问 https://example.com/path?q=1 或
http://test.org:8080/api/v2/users#section
了解更多。
"""
function extract_urls(text)
pattern = r"https?://[^\s<>\"'{}\|\^`\[\]]+"
return [m.match for m in eachmatch(pattern, text)]
end
urls = extract_urls(text)
# ["https://example.com/path?q=1", "http://test.org:8080/api/v2/users#section"]
实战三:CSV 简单解析
csv_data = """
name,age,city
Alice,30,Beijing
Bob,25,Shanghai
Charlie,35,Guangzhou
"""
function parse_csv(csv_str)
lines = split(strip(csv_str), '\n')
headers = split(lines[1], ',')
rows = []
for line in lines[2:end]
values = split(line, ',')
row = Dict(zip(headers, values))
push!(rows, row)
end
return rows
end
data = parse_csv(csv_data)
# [
# Dict("name" => "Alice", "age" => "30", "city" => "Beijing"),
# Dict("name" => "Bob", "age" => "25", "city" => "Shanghai"),
# Dict("name" => "Charlie", "age" => "35", "city" => "Guangzhou"),
# ]
实战四:敏感信息脱敏
function mask_phone(phone::String)
# 手机号脱敏:138****5678
return replace(phone, r"(\d{3})\d{4}(\d{4})" => s"\1****\2")
end
function mask_email(email::String)
# 邮箱脱敏:a***@example.com
m = match(r"^(\w)[^@]*(@.+)$", email)
if m !== nothing
return "$(m[1])***$(m[2])"
end
return email
end
mask_phone("13812345678") # "138****5678"
mask_email("[email protected]") # "a***@example.com"
10. 字符串性能优化
| 操作 | 推荐 | 避免 |
|---|---|---|
| 字符串拼接 | join(v, sep) 或 IOBuffer | 循环中 * 拼接 |
| 子字符串 | SubString 或范围 s[a:b] | 频繁 String(s[a:b]) |
| 大量替换 | replace() | 循环中 replace |
| 正则预编译 | r"pattern" 在全局定义 | 函数内每次 Regex(pattern) |
# 低效:循环拼接(每次分配新内存)
function bad_concat(n)
s = ""
for i in 1:n
s *= string(i) * " "
end
return s
end
# 高效:使用 IOBuffer
function good_concat(n)
io = IOBuffer()
for i in 1:n
print(io, i, " ")
end
return String(take!(io))
end
# 高效:使用 join
function also_good(n)
return join(1:n, " ")
end
扩展阅读
📌 本章小结: Julia 字符串是 UTF-8 编码的,索引基于字节位置而非字符位置。使用
eachindex(s)遍历有效索引,SubString创建零拷贝视图。正则表达式通过r""字面量创建,match/eachmatch配合捕获组使用。字符串插值使用$语法,IOBuffer 是大量拼接的高效方案。