强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

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空字符
\uXXXXUnicode 字符(4 位十六进制)
\UXXXXXXXXUnicode 字符(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 是大量拼接的高效方案。