Lesson 1
Q:JIT 和 AOT 有什么区别?
A:JIT(即时编译,Just-In-Time Compilation)和AOT(预编译,Ahead-Of-Time Compilation)是两种常见的编译方式,它们的主要区别在于编译的时间和方式:
1. JIT(即时编译):
• 编译时间:在程序运行时进行编译。JIT 编译器会将代码从中间表示(通常是字节码或中间语言)编译成机器代码,并在运行时加载到内存中执行。
• 优势:由于编译发生在运行时,JIT 编译器能够根据当前环境和输入的实际情况进行优化,因此可能会生成更加高效的机器代码。
• 缺点:运行时需要额外的编译开销,启动时间较长,因为需要等到代码被编译后才能执行。
2. AOT(预编译):
• 编译时间:在程序运行之前进行编译。AOT 编译器将源代码或中间表示编译成机器代码,并将其保存为可执行文件。执行时不再需要额外的编译步骤。
• 优势:启动速度快,因为代码已经编译成机器代码,无需在运行时再进行编译。适用于需要快速启动的场景(如移动应用、嵌入式设备等)。
• 缺点:不能像 JIT 那样根据运行时情况进行优化,可能导致生成的代码不如 JIT 编译的代码高效。
总结:
• JIT 编译是“运行时编译”,它可以动态优化代码,适用于需要高效运行和灵活性的场景。
• AOT 编译是“预编译”,它生成完全编译好的机器代码,适用于对启动速度和部署有较高要求的场景。
两者有时也会结合使用,比如在某些环境中先进行 AOT 编译,再在运行时使用 JIT 编译来进一步优化性能。
Q:两种编译方式的过程是怎么样的?
A:当然可以!下面我将详细介绍 JIT(即时编译)和 AOT(预编译)的具体编译过程,帮助你更好地理解它们的工作原理。
1. JIT(即时编译)编译过程
JIT 编译器在程序运行时动态地将中间代码(如字节码或中间语言)转换为机器码(即目标平台可以直接执行的代码)。这一过程通常分为以下几个步骤:
步骤 1:程序加载
• 源代码编写:首先,开发者编写源代码,通常使用高级编程语言(如 Java、C#、Python 等)。
• 编译为中间代码:程序源代码被编译成中间代码(字节码或类似的中间语言)。例如,Java 代码编译成字节码(.class 文件),C# 代码编译成中间语言(CIL)。
步骤 2:加载中间代码
• 加载字节码:在程序启动时,JIT 编译器会加载这些中间代码,通常是 .class 或 .dll 文件。
• 解释执行:在初始阶段,虚拟机(如 JVM 或 .NET CLR)会通过解释器解释执行字节码。这种方式较慢,因为每条指令都需要逐条解释。
步骤 3:JIT 编译
• 发现热点代码:虚拟机会在运行时分析程序,识别出频繁执行的代码路径(热点代码)。这些代码通常是程序性能瓶颈的所在。
• 动态编译:对于热点代码,JIT 编译器会将其即时编译成机器码,并缓存这些编译结果。这样下次再执行时,就可以直接使用机器码,而不需要再进行解释或编译。
步骤 4:优化
• 运行时优化:JIT 编译器可以根据实际执行情况进行优化。例如,可以通过收集的运行时数据来优化循环展开、内联函数等。
• 即时反馈:由于编译发生在运行时,JIT 可以在整个执行过程中调整优化策略,使得最终的机器码更加高效。
步骤 5:执行机器码
• 直接执行机器码:编译后的机器码会直接在 CPU 上执行,不需要虚拟机的中介。
优缺点:
• 优点:JIT 可以利用运行时的信息对代码进行优化,通常可以生成更高效的机器码。
• 缺点:程序启动时可能会有额外的延迟(需要时间进行字节码加载和初始编译),以及在运行时还会有编译开销。
2. AOT(预编译)编译过程
与 JIT 不同,AOT 编译是在程序运行之前将源代码或中间代码完全编译成机器码,因此没有运行时编译的开销。AOT 编译过程通常分为以下几个步骤:
步骤 1:源代码编写
• 开发源代码:与 JIT 编译一样,首先开发者编写源代码,使用高级编程语言(如 C、C++、Go 等)。
步骤 2:AOT 编译
• 编译为机器码:AOT 编译器将源代码直接编译成目标机器的机器码。编译器会生成一个完全独立的可执行文件,包含了所有需要的机器码和库依赖。
• 没有中间代码:在 AOT 编译过程中,不会生成中间代码(如字节码)。相反,所有的代码直接编译成目标平台可以直接运行的机器码。
步骤 3:优化
• 静态优化:AOT 编译器在编译时就进行优化。这些优化可能包括:
• 内联函数:将小的函数调用直接插入调用处,以减少函数调用开销。
• 循环优化:通过循环展开、常量折叠等技术优化代码。
• 死代码消除:删除在程序中永远不会执行的代码。
• 内存布局优化:调整内存布局,以提高缓存效率。
这些优化通常是静态的,基于编译时的静态分析,无法利用运行时的动态信息。
步骤 4:生成可执行文件
• 生成可执行文件:AOT 编译后的程序直接变成了一个可执行文件,可以直接在目标机器上运行,而不需要任何虚拟机或解释器。比如,C 或 C++ 程序经过编译后,生成的是 .exe 或其他平台特定的二进制文件。
步骤 5:执行程序
• 直接执行机器码:程序被直接加载到内存中并执行,启动非常快速,因为已经是可执行文件,不需要再进行任何编译或解释。
优缺点:
• 优点:程序启动速度非常快,因为代码已经预编译成机器码;无需在运行时进行编译。
• 缺点:不能根据运行时的动态数据进行优化,因此可能无法生成和 JIT 一样高效的机器码;而且生成的机器码只能在特定平台上运行。
比较 JIT 和 AOT 编译
特性 JIT 编译 AOT 编译 编译时间 运行时编译,动态编译 编译时完成,提前编译成机器码 优化方式 运行时优化,依赖实际执行数据 静态优化,依赖编译时的代码分析 启动速度 较慢,需要加载和初次编译 很快,不需要在运行时编译 执行效率 可能非常高效,依赖运行时信息 较为固定,无法进行运行时动态优化 内存开销 运行时编译占用内存较大,可能缓存机器码 不需要运行时编译,内存开销较小 平台依赖性 一般依赖于虚拟机或解释器(如 JVM) 生成平台特定的机器码,平台依赖性较强 综合使用:JIT 和 AOT 混合
有些现代系统会结合 JIT 和 AOT 编译的优点。例如,.NET Core 和 Android 都使用了 AOT 和 JIT 混合的方法:
• AOT 编译用于提前生成代码,以加速启动时间。
• JIT 编译用于对运行时的代码进行优化,提升长时间运行后的性能。
这种方式试图在性能和启动速度之间找到平衡,适应不同的使用场景。
Q:使用 AOT 编译方式的语言可不可以称为编译型程序语言?
A:使用 AOT(Ahead-Of-Time)编译方式的语言可以被称为 编译型语言,但这个说法并不完全准确或全面,尤其是考虑到编译型语言的定义和不同的编译方式。
1. 编译型语言的传统定义
传统上,编译型语言指的是那些在程序运行前通过编译器将源代码转换为机器代码的语言。编译完成后,程序就可以直接在目标平台上执行,而不依赖于虚拟机或解释器。例如,C、C++、Rust 等语言通常被视为编译型语言,因为它们的源代码会被编译成平台特定的机器码,然后直接执行。
2. AOT 编译与编译型语言
AOT 编译是编译过程的一种方式,指的是将源代码提前编译为目标平台的机器码,而不是在运行时进行编译(如 JIT 编译)。使用 AOT 编译的语言或程序也可以称为编译型程序语言,因为在运行之前,代码就已经被编译成机器码或中间代码,执行时不再需要依赖解释或虚拟机。
例如:
• Go 语言:Go 编译器将源代码直接编译成机器码,完全不依赖运行时解释或虚拟机,符合传统的编译型语言标准。
• Swift:Swift 在 iOS 或 macOS 上开发时,通常使用 AOT 编译方式,生成直接可以执行的机器码,也可视为编译型语言。
• Rust:Rust 程序直接编译为机器码,不需要运行时环境,因此它是典型的编译型语言。
3. 与解释型语言的对比
与编译型语言不同,解释型语言是指在执行过程中,源代码通过解释器逐行解释执行,而不是提前编译为机器码。例如:
• Python:Python 程序需要通过 Python 解释器逐行解释执行,并且没有生成传统的机器码。
• JavaScript:JavaScript 程序通常由浏览器或 Node.js 环境中的解释器执行,尽管也有像 V8 引擎的 JIT 编译,但它仍然被认为是解释型语言。
4. 混合型语言
有些语言结合了编译和解释的特点,既可以使用 AOT 编译,也可以使用 JIT 编译。这些语言可能被称为 混合型语言,例如:
• Java:Java 程序源代码会先被编译成字节码(中间代码),然后在 JVM 上运行时,字节码会通过 JIT 编译成机器码,虽然可以使用 AOT 编译(比如 GraalVM 提供的 AOT 编译器),但 Java 程序通常还是通过虚拟机和 JIT 编译运行。
• C#:C# 也可以先通过 AOT 编译成机器码,或者在运行时由 CLR(公共语言运行时)进行 JIT 编译。
5. 总结
• 使用 AOT 编译 的语言可以被称为编译型语言,因为它们将源代码直接编译为机器码,无需在运行时依赖解释器或虚拟机。
• 然而,编译型语言这一术语通常更广泛地指代所有通过编译器将源代码编译为机器码或中间代码的语言。即使是结合了 AOT 和 JIT 编译的语言(如 Java、C#)也可以被视为编译型语言,尽管它们采用了不同的编译方式。
Q:ART use mixed-mod 是什么意思?
A:ART(Android Runtime)是 Android 系统中的应用程序运行时环境,它负责执行 Android 应用程序的代码。ART 引入了混合模式(Mixed-Mode),这是 ART 在处理应用程序代码时的一种编译方式,结合了 AOT(Ahead-Of-Time 编译)和 JIT(Just-In-Time 编译)两种编译策略。
ART 的混合模式(Mixed-Mode)简介
ART 的混合模式是将 AOT 编译 和 JIT 编译 两者结合使用,目的是平衡启动速度、内存占用和运行时性能。具体来说,ART 在不同的情况下选择使用 AOT 或 JIT,以提供最佳的性能和响应速度。
1. AOT 编译
在 ART 中,AOT 编译会在应用安装时将应用的 字节码(如 .dex 文件)提前编译成机器码。这样做的目的是:
• 提供更快的应用启动时间,因为机器码已经提前生成,不需要在首次运行时编译。
• 减少了运行时的编译开销,提升了长时间运行应用的性能。
AOT 编译的主要目标是提高应用程序启动时的速度和减少 JIT 编译的运行时开销。
2. JIT 编译
JIT 编译发生在应用程序的运行时,它会将应用程序的字节码(如 .dex 文件)根据实际执行情况编译为机器码。JIT 编译的特点是:
• 动态优化:JIT 编译可以根据实际执行的路径(即热点代码)进行优化。它会在应用程序运行时动态地将热点代码转换为机器码,从而提高性能。
• 灵活性:JIT 编译允许根据不同的设备、环境和应用的具体使用情况进行优化,使得代码更高效。
3. 混合模式(Mixed-Mode)
在 ART 中,混合模式意味着:
• AOT 编译用于在应用安装时提前编译代码,以提高启动速度。
• JIT 编译用于运行时动态优化和进一步提升性能,特别是针对一些运行时未能通过 AOT 编译的代码。
这种混合使用的策略可以在尽量减少启动时间的同时,还能通过 JIT 编译优化长时间运行的代码,从而平衡了性能和内存使用。
为什么使用混合模式?
ART 的混合模式有几个明显的优点:
• 快速启动:通过 AOT 编译,应用程序启动时就能直接执行机器码,而无需进行运行时的编译,这大大缩短了启动时间。
• 运行时优化:JIT 编译可以根据实际的使用场景进行动态优化,针对一些热代码(频繁执行的部分)进行优化,提高了运行时的效率。
• 节省内存:由于在 AOT 编译阶段已经生成了机器码,所以在运行时不需要进行大量的 JIT 编译和内存分配,从而节省了内存开销。
举个例子:
在 Android 7.0(Nougat)之前,Dalvik 虚拟机(Android 原来的运行时环境)依赖于 JIT 编译来将字节码编译成机器码。这样做在启动时比较慢,而且应用程序的性能依赖于 JIT 编译的速度。
但是在 Android 5.0(Lollipop)之后,ART 被引入并提供了 AOT 编译。ART 会在应用安装时提前将应用的字节码编译成机器码,从而提供更快的启动时间和更好的性能。但是,混合模式让 ART 在后续运行时依然能够利用 JIT 编译来优化运行时性能,特别是对于一些之前没有 AOT 编译过的代码部分。
总结
ART 的混合模式(Mixed-Mode)结合了 AOT 和 JIT 编译的优点,在应用程序的安装阶段使用 AOT 提前编译代码,以提供更快的启动速度;在应用程序运行时,使用 JIT 编译对热点代码进行动态优化,从而提供更好的运行时性能。这种策略平衡了启动速度、内存使用和性能优化,是 ART 在 Android 上实现高效运行的关键之一。
Q:UNCOL 是什么?
A:UNCOL(Universal Intermediate Code Language,通用中间代码语言)是一种早期的中间表示语言,设计目的是作为不同编程语言之间的中间层,帮助不同的编译器和硬件平台之间进行语言的转换。它是一种抽象的代码表示形式,用于程序的转换和优化,通常出现在一些早期的编译器研究中。
UNCOL 的背景和目的
在计算机科学的历史上,UNCOL 的出现是为了应对编译器的多样性和硬件平台的差异。早期的编译器通常是针对某一种特定的硬件和操作系统进行优化的,这使得将程序从一种语言转换到另一种语言,或者从一种平台移植到另一种平台,变得非常困难。
UNCOL 作为一种中间代码语言,试图解决这个问题。它的设计目标是将程序源代码通过编译转换成一个中立的代码表示,这个表示不依赖于特定的硬件平台。然后,针对不同的机器架构,可以使用特定的编译器将 UNCOL 转换为机器代码。这样,程序可以更加容易地跨平台移植,并且编译过程中的某些步骤可以得到共享。
UNCOL 的特点和作用
1. 中立性:
UNCOL 是一种与特定机器体系结构无关的语言,它并不依赖于任何特定的硬件或操作系统。这使得它可以作为不同编程语言之间的转换工具。
2. 中间代码:
在传统的编译过程中,编译器将源代码(如 C、Fortran)编译成中间代码(如 UNCOL),然后再将其转化为目标机器代码。UNCOL 作为这种中间代码语言,充当了语言和硬件平台之间的桥梁。
3. 编译器的优化和移植:
使用 UNCOL,编译器不再需要针对每种硬件平台都进行特定的优化。相反,优化可以在 UNCOL 上进行一次,然后通过针对不同平台的代码生成器将其转换为最终的机器代码。这种方法提高了编译器的可移植性和复用性。
4. 抽象程度:
UNCOL 语言的设计是相对高级的,它不直接映射到机器指令上,而是提供一个足够抽象的表示,能够支持多种不同的机器架构。这种抽象帮助了不同平台之间的兼容性。
UNCOL 的历史背景
• UNIX 和 C 语言:
在 1960s 到 1970s 年代,随着 UNIX 操作系统 的发展,编程语言和操作系统的多样性增加,移植性 成为一个重要问题。UC Berkeley 的 Ken Thompson 和 Dennis Ritchie 在开发 C 语言时,考虑到语言移植的需求,探索了通过 UNCOL 等中间语言来简化跨平台编译的可能性。
• 编译器的多样性问题:
在当时,编译器通常针对特定的硬件架构进行了优化和编写,这使得移植程序到不同硬件平台上变得非常困难。通过引入像 UNCOL 这样的中间代码,编译过程变得更加灵活和通用,从而简化了跨平台开发。
UNCOL 的局限性和影响
尽管 UNCOL 在理论上提供了跨平台的优势,但它并没有在实践中获得广泛应用。以下是一些原因:
1. 效率问题:
使用 UNCOL 作为中间语言,尽管它提供了平台无关的抽象,但它也可能引入一些效率问题。特别是在早期硬件资源有限的时代,额外的中间代码转换会增加编译时间和运行时开销。
2. 替代方案的出现:
随着计算机科学和编译技术的发展,其他更为有效的中间语言和优化技术(如 LLVM)逐渐取代了 UNCOL。LLVM 提供了更高效的中间表示和更好的优化机制,并且能够支持多种平台。
3. 标准化和工具的发展:
随着 Java 字节码、LLVM IR 和其他现代中间语言的出现,编译器的可移植性和优化变得更加成熟和高效。这些新的技术在移植性、性能和工具支持方面,远远超越了 UNCOL。
总结
UNCOL 是一种早期的中间代码语言,旨在解决编译器和硬件平台之间的差异,促进程序的跨平台移植。虽然它在历史上对编译器理论和技术的发展产生了影响,但由于效率问题和后续技术的进步,UNCOL 已经逐渐被更现代的技术所替代,如 LLVM 和 Java 字节码等。
尽管如此,UNCOL 的设计理念——中间代码和平台无关的抽象——为后来的编译技术提供了有价值的启示,尤其是在可移植性和编译器优化方面。
Q:LLVM 是什么?
A:LLVM(Low Level Virtual Machine)是一个开源的编译器框架,最初由 Chris Lattner 和其他研究人员在 2000 年代初开发。它的设计目标是提供一个优化的、模块化的编译器工具链,可以用于生成高效的机器代码。LLVM 的核心部分并不依赖于特定的源语言或目标架构,而是通过使用一个抽象的中间表示(IR)来支持各种编程语言和硬件平台。
LLVM 的关键特性
1. 中间表示(Intermediate Representation, IR):
• LLVM 的一个核心概念是 中间表示(IR),它是一种介于源代码和目标机器码之间的抽象表示。LLVM IR 既具有机器级别的细节,也具有足够的高级抽象,便于进行优化和生成目标代码。
• LLVM IR 可以是 位级表示(bitcode)或 文本表示(文本 IR),并且与平台无关,意味着同一个程序可以在不同平台上通过 LLVM IR 进行优化和编译。
2. 优化:
• LLVM 提供了多种 优化 技术,可以在编译过程中对 IR 进行广泛的优化。这些优化包括常量折叠、死代码消除、循环优化等。LLVM 允许开发者对不同的优化层次和阶段进行定制和扩展。
• 这些优化不仅限于源代码的编译过程,还可以通过 运行时 JIT(Just-In-Time)编译 实现。
3. 跨平台支持:
• LLVM 的设计使其具有很强的 跨平台能力。开发者可以编写一个编译器前端(例如,针对某个语言的解析器),然后利用 LLVM 的后端将 IR 编译成多种目标平台的机器码。
• 支持的目标架构包括 x86、ARM、MIPS、PowerPC 等,甚至包括一些非传统平台,如 WebAssembly 和 CUDA。
4. 编译器后端:
• LLVM 提供了一个 后端,将中间表示(IR)转化为目标平台的机器代码。LLVM 的后端支持多种硬件架构,能够生成高效的目标代码。
• 在编译过程中,LLVM 后端会根据目标平台的特点进行代码生成、寄存器分配、指令选择等操作。
5. 模块化和可扩展性:
• LLVM 是一个模块化的框架,允许开发者根据需求定制和扩展。编译器前端(如解析器)和后端(如目标代码生成)可以独立工作,并且各个模块可以根据需求进行优化和替换。
• 例如,LLVM 的 Clang 前端就是一个非常流行的 C/C++/Objective-C 编译器,它使用 LLVM 来进行优化和代码生成。
6. 支持多种编程语言:
• 虽然 LLVM 的目标是优化和生成机器代码,但它并不限于某一种编程语言。通过不同的前端,LLVM 支持多种编程语言,包括 C、C++、Rust、Swift、Fortran、Objective-C 等。
• 开发者可以为任何语言编写前端,将其源代码编译成 LLVM IR,然后利用 LLVM 后端生成目标机器代码。
7. JIT 编译:
• LLVM 支持 即时编译(JIT),即在程序运行时将 IR 转换为机器码。JIT 编译可以大大提高动态语言的执行效率,例如 Python、Ruby 或 JavaScript 在某些情况下可以通过 LLVM 的 JIT 编译器加速执行。
• 这使得 LLVM 不仅限于静态编译,也可以处理需要运行时编译的应用。
LLVM 的组成部分
1. LLVM Core:
• LLVM 的核心库包括优化和代码生成的基础设施。这部分包括对 IR 的支持、优化器、目标代码生成器等。
2. 前端:
• 前端是负责将源代码(如 C/C++、Swift、Rust 等)转换为中间表示(LLVM IR)的部分。流行的前端包括 Clang(C/C++)、Swift 编译器、Rustc 等。
3. 后端:
• 后端负责将 IR 转换为目标机器代码,并进行目标架构相关的优化。LLVM 的后端支持多种架构,能够生成适用于不同硬件的代码。
4. 优化器:
• LLVM 内建了多个优化阶段,用于在编译过程中对 IR 进行优化。优化可以是局部的,也可以是全局的,包括如死代码消除、常量折叠、循环优化等。
5. 工具和库:
• LLVM 提供了大量的工具和库,如 llvm-gcc、llc(LLVM 代码生成器)、opt(LLVM 优化器)等。开发者可以利用这些工具来构建自己的编译器或进行 IR 操作。
LLVM 的应用领域
1. 编译器开发:
• LLVM 本身就是一个强大的编译器框架,广泛用于开发新的编程语言的编译器。
• 比如,Rust 编译器 rustc 就是基于 LLVM 的,利用 LLVM 强大的优化和代码生成能力。
2. 优化:
• LLVM 提供了强大的 代码优化 功能,能够在程序编译时进行多层次、多阶段的优化,生成高效的目标代码。
3. 跨平台移植:
• 利用 LLVM,开发者可以为多种目标平台生成机器代码,使得编写跨平台应用变得更加容易。
• Clang 等前端可以将 C/C++ 等语言编译成 LLVM IR,再通过 LLVM 后端生成不同平台的机器代码。
4. JIT 编译与动态语言:
• 利用 LLVM 的 JIT 编译器,动态语言(如 Python、Ruby、JavaScript)可以获得近似静态语言的性能提升,尤其是在需要频繁执行的代码段上。
5. 图形学与 GPU 编程:
• LLVM 还被用于图形学和 GPU 编程(如支持 CUDA 和 OpenCL),通过 LLVM 中的 GPU 代码生成器将高效代码部署到图形硬件。
总结
LLVM 是一个强大而灵活的编译器框架,它通过提供中间表示(IR)和跨平台支持,简化了编译器的开发,并大大提高了代码的优化效率。通过其高度模块化的设计,LLVM 可以支持多种语言和硬件平台,广泛应用于编译器开发、程序优化、JIT 编译以及跨平台应用程序的构建。它不仅适用于静态编译,还能够通过 JIT 编译提高动态语言的执行效率,是现代编译器技术和编程语言开发的重要工具之一。
Lesson 2
Q:Lexemes 和 Tokens 有什么关联?
A:在语言学和编程语言的处理中,lexemes 和 tokens 是两个密切相关的概念,但它们有所不同。
1. Lexemes(词素)
Lexeme 是源代码或文本中的最小的语义单元。它是一个抽象的概念,代表一个词汇项或基本单元。它不考虑具体的表现形式,只关心它的语义。例如,在编程语言中,“if”可以是一个 lexeme,表示条件语句。
2. Tokens(符号)
Token 是对 lexeme 的具体实例化,它是通过词法分析(lexical analysis)从源代码中提取的实际“符号”或者“标记”。一个 token 是 lexeme 的一个实际实现,在源代码中的具体表示。
关联与区别:
• 关联:每个 token 代表一个 lexeme。在词法分析过程中,源代码中的字符被分解成多个 token,每个 token 对应一个 lexeme 的某个实例。
• 区别:lexeme 是语言中的抽象概念,而 token 是具体的、可以直接从源代码中提取的符号或标记。例如,x = 10 这行代码可能会被分解为:
• x(一个标识符的 token)
• =(一个赋值操作符的 token)
• 10(一个整数值的 token)
在这个过程中,x、= 和 10 分别是 tokens,而它们各自对应的 lexemes 是标识符、赋值符号和常数值。
总结:
• Lexemes 是语言的最小语义单位。
• Tokens 是从文本中提取出的 lexeme 的实际实例,它们在语法分析和编译过程中起到了区分不同语法元素的作用。
Q:给我一些例子,让我很快入手正则表达式。
A:好的,下面是一些常见正则表达式及其解释:
基本匹配
• 字符匹配:
• a 匹配字母 a
• \d 匹配一个数字(0-9)
• \w 匹配一个字母、数字或下划线(字母、数字、下划线:[a-zA-Z0-9_])
• \s 匹配一个空白字符(空格、制表符、换行等)
• 匹配任何字符:
• . 匹配任何单个字符(除了换行符)
量词
• 次数限定:
• a* 匹配零次或多次字母 a
• a+ 匹配一次或多次字母 a
• a? 匹配零次或一次字母 a
• a{n} 匹配恰好 n 次字母 a
• a{n,} 匹配至少 n 次字母 a
• a{n,m} 匹配 n 到 m 次字母 a
字符集
• 字符类:
• [abc] 匹配 a、b 或 c
• [^abc] 匹配除 a、b、c 之外的任意字符
• [0-9] 匹配任意数字
• [a-z] 匹配任意小写字母
• [A-Z] 匹配任意大写字母
• [a-zA-Z0-9] 匹配任意字母或数字
边界匹配
• 行/词边界:
• ^ 匹配字符串开头
• $ 匹配字符串结尾
• \b 匹配单词边界
• \B 匹配非单词边界
分组和选择
• 分组:
• (abc) 匹配字符串 abc,并作为一个整体分组
• (a|b) 匹配 a 或 b(分支结构)
反向引用
• 引用分组:
• (\d)\1 匹配两个相同的数字(第一个分组 \d 匹配一个数字,\1 引用该数字)
例子
1. 匹配电话号码(假设格式为 123-456-7890):
• \d{3}-\d{3}-\d{4}
2. 匹配邮箱地址:
• 1+@[\w-]+\.[a-z]{2,3}$(简单版,允许字母、数字和 -)
3. 匹配日期(格式:YYYY-MM-DD):
• ^\d{4}-\d{2}-\d{2}$
4. 匹配URL:
• https?://[\w-]+(\.[\w-]+)+(:\d+)?(/\S*)?
5. 匹配数字(整数或小数):
• ^-?\d+(\.\d+)?$
这些是常见的正则表达式及其应用,能够处理很多常见的字符串匹配和操作需求。
\w-↩︎
关于本文
由 wsy_jim 撰写, 采用 CC BY-NC 4.0 许可协议.