Executors
原文:
docs.oracle.com/javase/tutorial/essential/concurrency/executors.html
在所有先前的示例中,新线程执行的任务与其Runnable
对象定义的线程本身(由Thread
对象定义)之间存在密切联系。这对于小型应用程序效果很好,但在大型应用程序中,将线程管理和创建与应用程序的其余部分分离是有意义的。封装这些功能的对象称为executors。以下小节详细描述了 executors。
-
Executor Interfaces 定义了三种 executor 对象类型。
-
Thread Pools 是最常见的 executor 实现类型。
-
Fork/Join 是一个利用多处理器的框架(JDK 7 中新增)。
执行器接口
原文:
docs.oracle.com/javase/tutorial/essential/concurrency/exinter.html
java.util.concurrent
包定义了三个执行器接口:
-
Executor
是一个简单的接口,支持启动新任务。 -
ExecutorService
是Executor
的子接口,增加了一些功能,有助于管理单个任务和执行器本身的生命周期。 -
ScheduledExecutorService
是ExecutorService
的子接口,支持未来和/或定期执行任务。
通常,引用执行器对象的变量声明为这三种接口类型之一,而不是执行器类类型。
Executor
接口
Executor
接口提供了一个方法 execute
,旨在成为常见线程创建习语的替代品。如果 r
是一个 Runnable
对象,e
是一个 Executor
对象,你可以替换
(new Thread(r)).start();
with
e.execute(r);
然而,execute
的定义不太具体。低级习语创建一个新线程并立即启动它。根据 Executor
的实现,execute
可能会做同样的事情,但更有可能使用现有的工作线程来运行 r
,或者将 r
放入队列等待工作线程可用。(我们将在线程池部分描述工作线程。)
java.util.concurrent
中的执行器实现旨在充分利用更高级的 ExecutorService
和 ScheduledExecutorService
接口,尽管它们也与基本的 Executor
接口一起工作。
ExecutorService
接口
ExecutorService
接口通过类似但更灵活的 submit
方法来补充 execute
。与 execute
一样,submit
接受 Runnable
对象,但也接受 Callable
对象,允许任务返回一个值。submit
方法返回一个 Future
对象,用于检索 Callable
返回值并管理 Callable
和 Runnable
任务的状态。
ExecutorService
还提供了提交大量 Callable
对象的方法。最后,ExecutorService
提供了一些方法来管理执行器的关闭。为了支持立即关闭,任务应正确处理中断。
ScheduledExecutorService
接口
ScheduledExecutorService
接口通过 schedule
补充了其父接口 ExecutorService
的方法,该方法在指定延迟后执行 Runnable
或 Callable
任务。此外,该接口定义了 scheduleAtFixedRate
和 scheduleWithFixedDelay
,以在定义的间隔时间内重复执行指定任务。
线程池
原文:
docs.oracle.com/javase/tutorial/essential/concurrency/pools.html
java.util.concurrent
中的大多数执行器实现使用线程池,其中包含工作线程。这种类型的线程与它执行的Runnable
和Callable
任务分开存在,并经常用于执行多个任务。
使用工作线程可以最小化由于线程创建而产生的开销。线程对象使用大量内存,在大规模应用程序中,分配和释放许多线程对象会产生显著的内存管理开销。
一种常见的线程池类型是固定线程池。这种类型的池始终有指定数量的线程在运行;如果某个线程在仍在使用时被终止,它将自动被新线程替换。任务通过内部队列提交到池中,当活动任务多于线程时,队列会保存额外的任务。
使用固定线程池的一个重要优势是应用程序在使用它时优雅降级。要理解这一点,考虑一个 Web 服务器应用程序,其中每个 HTTP 请求都由一个单独的线程处理。如果应用程序只是为每个新的 HTTP 请求创建一个新线程,并且系统接收到的请求多于它立即处理的能力,当所有这些线程的开销超过系统容量时,应用程序将突然停止响应所有请求。通过限制可以创建的线程数量,应用程序将不会像请求进来那样快速地为 HTTP 请求提供服务,但它将以系统能够维持的速度为它们提供服务。
创建使用固定线程池的执行器的简单方法是在java.util.concurrent.Executors
中调用newFixedThreadPool
工厂方法。该类还提供以下工厂方法:
-
newCachedThreadPool
方法创建一个具有可扩展线程池的执行器。此执行器适用于启动许多短暂任务的应用程序。 -
newSingleThreadExecutor
方法创建一个一次执行一个任务的执行器。 -
几个工厂方法是上述执行器的
ScheduledExecutorService
版本。
如果上述工厂方法提供的任何执行器都不符合您的需求,构造java.util.concurrent.ThreadPoolExecutor
或java.util.concurrent.ScheduledThreadPoolExecutor
的实例将为您提供额外的选项。
分叉/合并
原文:
docs.oracle.com/javase/tutorial/essential/concurrency/forkjoin.html
分叉/合并框架是ExecutorService
接口的一种实现,可以帮助你充分利用多个处理器。它专为可以递归地分解为较小片段的工作而设计。目标是利用所有可用的处理能力来提高应用程序的性能。
与任何ExecutorService
实现一样,分叉/合并框架将任务分配给线程池中的工作线程。分叉/合并框架的独特之处在于它使用工作窃取算法。工作线程如果没有任务可执行,可以从其他仍在忙碌的线程中窃取任务。
分叉/合并框架的核心是ForkJoinPool
类,它是AbstractExecutorService
类的扩展。ForkJoinPool
实现了核心的工作窃取算法,并可以执行ForkJoinTask
进程。
基本用法
使用分叉/合并框架的第一步是编写执行一部分工作的代码。你的代码应该类似于以下伪代码:
if (my portion of the work is small enough)
do the work directly
else
split my work into two pieces
invoke the two pieces and wait for the results
将这段代码封装在一个ForkJoinTask
子类中,通常使用其中的一个更专门的类型,要么是RecursiveTask
(可以返回结果),要么是RecursiveAction
。
当你的ForkJoinTask
子类准备就绪后,创建代表所有要完成工作的对象,并将其传递给ForkJoinPool
实例的invoke()
方法。
清晰的模糊
为了帮助你理解分叉/合并框架的工作原理,请考虑以下示例。假设你想要对图像进行模糊处理。原始源图像由一个整数数组表示,其中每个整数包含单个像素的颜色值。模糊后的目标图像也由一个与源图像大小相同的整数数组表示。
执行模糊操作是通过逐个像素地处理源数组来完成的。每个像素与其周围像素(红色、绿色和蓝色分量取平均值)进行平均,结果放入目标数组中。由于图像是一个大数组,这个过程可能需要很长时间。你可以利用多处理器系统上的并发处理,使用分叉/合并框架来实现算法。以下是一个可能的实现:
public class ForkBlur extends RecursiveAction {
private int[] mSource;
private int mStart;
private int mLength;
private int[] mDestination;
// Processing window size; should be odd.
private int mBlurWidth = 15;
public ForkBlur(int[] src, int start, int length, int[] dst) {
mSource = src;
mStart = start;
mLength = length;
mDestination = dst;
}
protected void computeDirectly() {
int sidePixels = (mBlurWidth - 1) / 2;
for (int index = mStart; index < mStart + mLength; index++) {
// Calculate average.
float rt = 0, gt = 0, bt = 0;
for (int mi = -sidePixels; mi <= sidePixels; mi++) {
int mindex = Math.min(Math.max(mi + index, 0),
mSource.length - 1);
int pixel = mSource[mindex];
rt += (float)((pixel & 0x00ff0000) >> 16)
/ mBlurWidth;
gt += (float)((pixel & 0x0000ff00) >> 8)
/ mBlurWidth;
bt += (float)((pixel & 0x000000ff) >> 0)
/ mBlurWidth;
}
// Reassemble destination pixel.
int dpixel = (0xff000000 ) |
(((int)rt) << 16) |
(((int)gt) << 8) |
(((int)bt) << 0);
mDestination[index] = dpixel;
}
}
...
现在你要实现抽象的compute()
方法,该方法可以直接执行模糊操作,也可以将其拆分为两个较小的任务。一个简单的数组长度阈值有助于确定是执行工作还是拆分任务。
protected static int sThreshold = 100000;
protected void compute() {
if (mLength < sThreshold) {
computeDirectly();
return;
}
int split = mLength / 2;
invokeAll(new ForkBlur(mSource, mStart, split, mDestination),
new ForkBlur(mSource, mStart + split, mLength - split,
mDestination));
}
如果前面的方法在RecursiveAction
类的子类中,那么设置任务在ForkJoinPool
中运行就很简单,包括以下步骤:
-
创建一个代表所有要完成工作的任务。
// source image pixels are in src // destination image pixels are in dst ForkBlur fb = new ForkBlur(src, 0, src.length, dst);
-
创建将运行任务的
ForkJoinPool
。ForkJoinPool pool = new ForkJoinPool();
-
运行任务。
pool.invoke(fb);
要查看完整的源代码,包括一些额外的代码来创建目标图像文件,请参见ForkBlur
示例。
标准实现
除了在多处理器系统上并行执行任务的自定义算法(例如前一节中的ForkBlur.java
示例)中使用分支/合并框架之外,Java SE 中还有一些通用功能已经使用分支/合并框架实现。其中一种实现是在 Java SE 8 中引入的,被java.util.Arrays
类用于其parallelSort()
方法。这些方法类似于sort()
,但通过分支/合并框架利用并发性能。在多处理器系统上运行时,大型数组的并行排序比顺序排序更快。然而,这些方法如何利用分支/合并框架超出了 Java 教程的范围。有关此信息,请参阅 Java API 文档。
另一个实现分支/合并框架的方法是使用java.util.streams
包中的方法,该包是Project Lambda的一部分,计划在 Java SE 8 发布中使用。更多信息,请参阅 Lambda 表达式部分。
并发集合
原文:
docs.oracle.com/javase/tutorial/essential/concurrency/collections.html
java.util.concurrent
包包含了许多对 Java 集合框架的补充。这些最容易通过提供的集合接口进行分类:
-
BlockingQueue
定义了一个先进先出的数据结构,当尝试向满队列添加或从空队列检索时会阻塞或超时。 -
ConcurrentMap
是java.util.Map
的子接口,定义了有用的原子操作。这些操作仅在键存在时移除或替换键值对,或仅在键不存在时添加键值对。使这些操作原子化有助于避免同步。ConcurrentMap
的标准通用实现是ConcurrentHashMap
,它是HashMap
的并发模拟。 -
ConcurrentNavigableMap
是ConcurrentMap
的子接口,支持近似匹配。ConcurrentNavigableMap
的标准通用实现是ConcurrentSkipListMap
,它是TreeMap
的并发模拟。
所有这些集合都有助于避免内存一致性错误,通过定义将一个对象添加到集合的操作与随后访问或移除该对象的操作之间的 happens-before 关系。
原子变量
原文:
docs.oracle.com/javase/tutorial/essential/concurrency/atomicvars.html
java.util.concurrent.atomic
包定义了支持单个变量上原子操作的类。所有类都有类似于对volatile
变量进行读取和写入的get
和set
方法。也就是说,set
与同一变量上的任何后续get
之间存在 happens-before 关系。原子compareAndSet
方法也具有这些内存一致性特性,整数原子变量适用的简单原子算术方法也是如此。
要了解这个包可能如何使用,让我们回到最初用来演示线程干扰的Counter
类:
class Counter {
private int c = 0;
public void increment() {
c++;
}
public void decrement() {
c--;
}
public int value() {
return c;
}
}
使Counter
免受线程干扰的一种方法是使其方法同步,就像SynchronizedCounter
中那样:
class SynchronizedCounter {
private int c = 0;
public synchronized void increment() {
c++;
}
public synchronized void decrement() {
c--;
}
public synchronized int value() {
return c;
}
}
对于这个简单的类,同步是一个可接受的解决方案。但对于一个更复杂的类,我们可能希望避免不必要同步的活跃度影响。用AtomicInteger
替换int
字段可以让我们在不使用同步的情况下防止线程干扰,就像AtomicCounter
中那样:
import java.util.concurrent.atomic.AtomicInteger;
class AtomicCounter {
private AtomicInteger c = new AtomicInteger(0);
public void increment() {
c.incrementAndGet();
}
public void decrement() {
c.decrementAndGet();
}
public int value() {
return c.get();
}
}
并发随机数
原文:
docs.oracle.com/javase/tutorial/essential/concurrency/threadlocalrandom.html
在 JDK 7 中,java.util.concurrent
包含一个方便的类,ThreadLocalRandom
,适用于期望从多个线程或ForkJoinTask
中使用随机数的应用程序。
对于并发访问,使用ThreadLocalRandom
而不是Math.random()
会减少争用,最终提高性能。
你只需调用ThreadLocalRandom.current()
,然后调用其中的方法来获取一个随机数。以下是一个示例:
int r = ThreadLocalRandom.current() .nextInt(4, 77);
进一步阅读
原文:
docs.oracle.com/javase/tutorial/essential/concurrency/further.html
-
Concurrent Programming in Java: Design Principles and Pattern (2nd Edition) 作者:Doug Lea。这是一部由领先专家撰写的全面作品,他也是 Java 平台并发框架的架构师。
-
Java Concurrency in Practice 作者:Brian Goetz, Tim Peierls, Joshua Bloch, Joseph Bowbeer, David Holmes, 和 Doug Lea。一本旨在让初学者易于理解的实用指南。
-
Effective Java Programming Language Guide (2nd Edition) 作者:Joshua Bloch。虽然这是一本通用的编程指南,但其中关于线程的章节包含了并发编程的基本“最佳实践”。
-
Concurrency: State Models & Java Programs (2nd Edition) 作者:Jeff Magee 和 Jeff Kramer。通过建模和实际示例相结合,介绍并发编程。
-
Java Concurrent Animated: 展示并发特性使用的动画。
问题和练习:并发
原文:
docs.oracle.com/javase/tutorial/essential/concurrency/QandE/questions.html
问题
- 你能将
Thread
对象传递给Executor.execute
吗?这样的调用有意义吗?
练习
-
编译并运行
BadThreads.java
:public class BadThreads { static String message; private static class CorrectorThread extends Thread { public void run() { try { sleep(1000); } catch (InterruptedException e) {} // Key statement 1: message = "Mares do eat oats."; } } public static void main(String args[]) throws InterruptedException { (new CorrectorThread()).start(); message = "Mares do not eat oats."; Thread.sleep(2000); // Key statement 2: System.out.println(message); } }
应用程序应该打印出“Mares do eat oats.” 这是一定会发生的吗?如果不是,为什么?改变两次
Sleep
调用的参数会有帮助吗?如何确保所有对message
的更改在主线程中可见? -
修改 Guarded Blocks 中的生产者-消费者示例,使用标准库类代替
Drop
类。
检查你的答案。
课程:平台环境
原文:
docs.oracle.com/javase/tutorial/essential/environment/index.html
应用程序在平台环境中运行,由底层操作系统、Java 虚拟机、类库和应用程序启动时提供的各种配置数据定义。本课程描述了应用程序用于检查和配置其平台环境的一些 API。本课程包括三个部分:
-
配置工具描述了用于访问应用程序部署时提供的配置数据或应用程序用户提供的 API。
-
系统工具描述了在
System
和Runtime
类中定义的各种 API。 -
路径和类路径描述了用于配置 JDK 开发工具和其他应用程序的环境变量。
配置实用程序
原文:
docs.oracle.com/javase/tutorial/essential/environment/config.html
这一部分描述了一些配置实用程序,帮助应用程序访问其启动上下文。
属性
原文:
docs.oracle.com/javase/tutorial/essential/environment/properties.html
属性是作为键/值对管理的配置值。在每对中,键和值都是String
值。键标识并用于检索值,就像使用变量名检索变量的值一样。例如,一个能够下载文件的应用程序可能使用名为“download.lastDirectory”的属性来跟踪用于最后下载的目录。
要管理属性,请创建java.util.Properties
的实例。此类提供以下方法:
-
从流中加载键/值对到
Properties
对象中, -
通过其键检索值,
-
列出键及其值,
-
枚举键,
-
将属性保存到流中。
有关流的介绍,请参阅输入/输出流中的基本 I/O 课程。
Properties
扩展了java.util.Hashtable
。从Hashtable
继承的一些方法支持以下操作:
-
测试特定键或值是否在
Properties
对象中, -
获取当前键/值对的数量,
-
删除键及其值,
-
向
Properties
列表添加键/值对, -
枚举值或键,
-
通过键检索值,
-
查看
Properties
对象是否为空。
安全注意事项: 访问属性需经当前安全管理器批准。本节中的示例代码段假定为独立应用程序,这些应用程序默认没有安全管理器。同样的代码在 applet 中可能无法正常工作,具体取决于运行的浏览器。请参阅 Applet 的功能和限制中的 Java Applets 课程,了解 applet 的安全限制信息。
System
类维护一个定义当前工作环境配置的Properties
对象。有关这些属性的更多信息,请参阅系统属性。本节的其余部分将解释如何使用属性来管理应用程序配置。
应用程序生命周期中的属性
以下图示说明了典型应用程序如何在执行过程中使用Properties
对象管理其配置数据。
-
启动中
第一个三个框中给出的操作发生在应用程序启动时。首先,应用程序将默认属性从一个众所周知的位置加载到
Properties
对象中。通常,默认属性存储在磁盘上的文件中,与应用程序的.class
和其他资源文件一起。接下来,应用程序创建另一个
Properties
对象,并加载上次运行应用程序时保存的属性。许多应用程序按用户为单位存储属性,因此此步骤中加载的属性通常位于应用程序在用户主目录中维护的特定目录中的特定文件中。最后,应用程序使用默认和记忆的属性来初始化自身。关键在于一致性。应用程序必须始终将属性加载和保存到相同位置,以便下次执行时能够找到它们。
-
运行中
在应用程序执行期间,用户可能会更改一些设置,也许在首选项窗口中,并且
Properties
对象将更新以反映这些更改。如果要记住用户更改以供将来的会话使用,则必须保存这些更改。 -
退出
退出时,应用程序将属性保存到其众所周知的位置,以便在下次启动应用程序时再次加载。
设置Properties
对象
以下 Java 代码执行了前一节描述的前两个步骤:加载默认属性和加载记住的属性:
. . .
// create and load default properties
Properties defaultProps = new Properties();
FileInputStream in = new FileInputStream("defaultProperties");
defaultProps.load(in);
in.close();
// create application properties with default
Properties applicationProps = new Properties(defaultProps);
// now load properties
// from last invocation
in = new FileInputStream("appProperties");
applicationProps.load(in);
in.close();
. . .
首先,应用程序设置一个默认的Properties
对象。该对象包含一组属性,如果在其他地方没有明确设置值,则使用这些属性。然后,load 方法从名为defaultProperties
的磁盘上的文件中读取默认值。
接下来,应用程序使用不同的构造函数创建第二个Properties
对象applicationProps
,其默认值包含在defaultProps
中。当检索属性时,默认值起作用。如果在applicationProps
中找不到属性,则会搜索其默认列表。
最后,代码从名为appProperties
的文件中将一组属性加载到applicationProps
中。该文件中的属性是上次调用应用程序时保存的属性,如下一节所述。
保存属性
以下示例使用Properties.store
从前一个示例中写出应用程序属性。默认属性不需要每次保存,因为它们永远不会更改。
FileOutputStream out = new FileOutputStream("appProperties");
applicationProps.store(out, "---No Comment---");
out.close();
store
方法需要一个要写入的流,以及一个字符串,该字符串用作输出顶部的注释。
获取属性信息
一旦应用程序设置了其Properties
对象,应用程序可以查询该对象以获取有关其包含的各种键和值的信息。应用程序在启动后从Properties
对象获取信息,以便根据用户的选择初始化自身。Properties
类有几种获取属性信息的方法:
-
contains(Object value)
和containsKey(Object key)
如果值或键在
Properties
对象中,则返回true
。Properties
从Hashtable
继承这些方法。因此,它们接受Object
参数,但只应使用String
值。 -
getProperty(String key)
和getProperty(String key, String default)
返回指定属性的值。第二个版本提供默认值。如果找不到键,则返回默认值。
-
list(PrintStream s)
和list(PrintWriter w)
将所有属性写入指定的流或写入器。这对于调试很有用。
-
elements()
,keys()
和propertyNames()
返回一个包含
Properties
对象中包含的键或值(如方法名所示)的枚举。keys
方法仅返回对象本身的键;propertyNames
方法还返回默认属性的键。 -
stringPropertyNames()
类似于
propertyNames
,但返回一个Set<String>
,并且仅返回键和值都是字符串的属性名称。请注意,Set
对象不由Properties
对象支持,因此对其中一个对象的更改不会影响另一个对象。 -
size()
返回当前键/值对的数量。
设置属性
用户在应用程序执行期间与应用程序的交互可能会影响属性设置。这些更改应该反映在Properties
对象中,以便在应用程序退出时(并调用store
方法时)保存这些更改。以下方法更改Properties
对象中的属性:
-
setProperty(String key, String value)
将键/值对放入
Properties
对象中。 -
remove(Object key)
删除与键关联的键/值对。
注意: 上述描述的一些方法在Hashtable
中定义,因此接受除String
之外的键和值参数类型。始终使用String
作为键和值,即使该方法允许其他类型。同时不要在Properties
对象上调用Hashtable.set
或Hastable.setAll
;始终使用Properties.setProperty
。
命令行参数
原文:
docs.oracle.com/javase/tutorial/essential/environment/cmdLineArgs.html
一个 Java 应用程序可以从命令行接受任意数量的参数。这允许用户在启动应用程序时指定配置信息。
用户在调用应用程序时输入命令行参数,并在要运行的类名后指定这些参数。例如,假设一个名为Sort
的 Java 应用程序对文件中的行进行排序。要对名为friends.txt
的文件中的数据进行排序,用户会输入:
java Sort friends.txt
当一个应用程序启动时,运行时系统会通过一个String
数组将命令行参数传递给应用程序的主方法。在前面的示例中,传递给Sort
应用程序的命令行参数是包含一个单独String
的数组:"friends.txt"
。
回显命令行参数
Echo
示例会将每个命令行参数单独显示在一行上:
public class Echo {
public static void main (String[] args) {
for (String s: args) {
System.out.println(s);
}
}
}
以下示例展示了用户如何运行Echo
。用户输入以斜体显示。
*java Echo Drink Hot Java*
Drink
Hot
Java
请注意,应用程序会将每个单词 Drink
、Hot
和 Java
单独显示在一行上。这是因为空格字符分隔命令行参数。要将Drink
、Hot
和Java
解释为单个参数,用户应该用引号将它们括起来。
*java Echo "Drink Hot Java"*
Drink Hot Java
解析数值型命令行参数
如果一个应用程序需要支持一个表示数字的命令行参数,它必须将代表数字的String
参数(如"34")转换为数值。以下是一个将命令行参数转换为int
的代码片段:
int firstArg;
if (args.length > 0) {
try {
firstArg = Integer.parseInt(args[0]);
} catch (NumberFormatException e) {
System.err.println("Argument" + args[0] + " must be an integer.");
System.exit(1);
}
}
如果parseInt
的args[0]
的格式无效,会抛出NumberFormatException
。所有的Number
类 Integer
、Float
、Double
等等 都有parseXXX
方法,将代表数字的String
转换为其类型的对象。
环境变量
原文:
docs.oracle.com/javase/tutorial/essential/environment/env.html
许多操作系统使用环境变量将配置信息传递给应用程序。与 Java 平台中的属性类似,环境变量是键/值对,其中键和值都是字符串。设置和使用环境变量的约定在操作系统之间以及命令行解释器之间有所不同。要了解如何在您的系统上将环境变量传递给应用程序,请参考系统文档。
查询环境变量
在 Java 平台上,应用程序使用System.getenv
来检索环境变量的值。没有参数时,getenv
返回一个只读的java.util.Map
实例,其中映射键是环境变量名称,映射值是环境变量值。这在EnvMap
示例中有所展示:
import java.util.Map;
public class EnvMap {
public static void main (String[] args) {
Map<String, String> env = System.getenv();
for (String envName : env.keySet()) {
System.out.format("%s=%s%n",
envName,
env.get(envName));
}
}
}
使用String
参数,getenv
返回指定变量的值。如果未定义变量,则getenv
返回null
。Env
示例以这种方式使用getenv
来查询在命令行上指定的特定环境变量:
public class Env {
public static void main (String[] args) {
for (String env: args) {
String value = System.getenv(env);
if (value != null) {
System.out.format("%s=%s%n",
env, value);
} else {
System.out.format("%s is"
+ " not assigned.%n", env);
}
}
}
}
将环境变量传递给新进程
当 Java 应用程序使用ProcessBuilder
对象创建新进程时,传递给新进程的默认环境变量集合与提供给应用程序的虚拟机进程的集合相同。应用程序可以使用ProcessBuilder.environment
更改此集合。
平台依赖性问题
不同系统上实现环境变量的方式之间存在许多微妙的差异。例如,Windows 在环境变量名称中忽略大小写,而 UNIX 则不会。环境变量的使用方式也各不相同。例如,Windows 在名为USERNAME
的环境变量中提供用户名,而 UNIX 实现可能在USER
、LOGNAME
或两者中提供用户名。
为了最大化可移植性,在系统属性中提供相同值时,永远不要引用环境变量。例如,如果操作系统提供用户名,则始终可以在系统属性user.name
中找到。
其他配置实用程序
原文:
docs.oracle.com/javase/tutorial/essential/environment/other.html
这里是一些其他配置实用程序的摘要。
首选项 API允许应用程序在一个与实现相关的后备存储中存储和检索配置数据。支持异步更新,并且同一组首选项可以被多个线程甚至多个应用程序安全地更新。更多信息,请参考首选项 API 指南。
部署在JAR 归档中的应用程序使用清单来描述归档的内容。更多信息,请参考在 JAR 文件中打包程序课程。
Java Web Start 应用程序的配置包含在一个JNLP 文件中。更多信息,请参考 Java Web Start 课程。
Java 插件小程序的配置部分取决于用于在网页中嵌入小程序的 HTML 标签。根据小程序和浏览器,这些标签可以包括<applet>
、<object>
、<embed>
和<param>
。更多信息,请参考 Java 小程序课程。
类java.util.ServiceLoader
提供了一个简单的服务提供者设施。服务提供者是服务的实现一组众所周知的接口和(通常是抽象的)类。服务提供者的类通常实现服务中定义的接口并子类化类。服务提供者可以作为扩展安装(参见扩展机制)。提供者也可以通过将它们添加到类路径或通过其他特定于平台的方式来提供。
系统工具
原文:
docs.oracle.com/javase/tutorial/essential/environment/system.html
System
类实现了许多系统工具。其中一些已经在之前关于配置工具的部分中介绍过。本节介绍其他一些系统工具。
命令行 I/O 对象
原文:
docs.oracle.com/javase/tutorial/essential/environment/cl.html
System
提供了几个预定义的 I/O 对象,这些对象在一个旨在从命令行启动的 Java 应用程序中非常有用。这些对象实现了大多数操作系统提供的标准 I/O 流,还有一个对于输入密码很有用的控制台对象。更多信息,请参考从命令行进行 I/O 在 基本 I/O 课程中。
系统属性
原文:
docs.oracle.com/javase/tutorial/essential/environment/sysprop.html
在 Properties 中,我们研究了应用程序如何使用 Properties
对象来维护其配置。Java 平台本身使用 Properties
对象来维护自己的配置。System
类维护一个描述当前工作环境配置的 Properties
对象。系统属性包括当前用户、当前 Java 运行时版本以及用于分隔文件路径名组件的字符的信息。
以下表格描述了一些最重要的系统属性。
键 | 含义 |
---|---|
"file.separator" |
文件路径中分隔组件的字符。在 UNIX 上是 "/ ",在 Windows 上是 "\ "。 |
"java.class.path" |
用于查找包含类文件的目录和 JAR 存档的路径。类路径的元素由 path.separator 属性中指定的特定于平台的字符分隔。 |
"java.home" |
Java Runtime Environment (JRE) 的安装目录 |
"java.vendor" |
JRE 供应商名称 |
"java.vendor.url" |
JRE 供应商 URL |
"java.version" |
JRE 版本号 |
"line.separator" |
操作系统用于在文本文件中分隔行的序列 |
"os.arch" |
操作系统架构 |
"os.name" |
操作系统名称 |
"os.version" |
操作系统版本 |
"path.separator" |
java.class.path 中使用的路径分隔符字符 |
"user.dir" |
用户工作目录 |
"user.home" |
用户主目录 |
"user.name" |
用户账户名 |
安全注意事项: 访问系统属性可能受到 安全管理器 的限制。这在小程序中最常见,小程序被阻止读取某些系统属性,并且无法写入任何系统属性。有关在小程序中访问系统属性的更多信息,请参阅 System Properties 中的 Java 富互联网应用程序进阶 课程。
读取系统属性
System
类有两个用于读取系统属性的方法:getProperty
和 getProperties
。
System
类有两个不同版本的 getProperty
。两者都检索参数列表中命名的属性的值。其中较简单的 getProperty
方法接受一个参数,即属性键。例如,要获取 path.separator
的值,请使用以下语句:
System.getProperty("path.separator");
getProperty
方法返回一个包含属性值的字符串。如果属性不存在,此版本的 getProperty
返回 null。
getProperty
的另一个版本需要两个 String
参数:第一个参数是要查找的键,第二个参数是在找不到键或键没有值时要返回的默认值。例如,下面的 getProperty
调用查找名为 subliminal.message
的 System
属性。这不是一个有效的系统属性,所以该方法返回提供的第二个参数作为默认值:“购买 StayPuft 棉花糖!
”
System.getProperty("subliminal.message", "Buy StayPuft Marshmallows!");
System
类提供的最后一个方法用于访问属性值的是 getProperties
方法,它返回一个 Properties
对象。该对象包含完整的系统属性定义集。
写入系统属性
要修改现有的系统属性集,请使用 System.setProperties
。此方法接受一个已初始化以包含要设置的属性的 Properties
对象。此方法用新的由 Properties
对象表示的属性集替换整个系统属性集。
警告: 改变系统属性可能是潜在危险的,应谨慎操作。许多系统属性在启动后不会重新读取,并且仅用于信息目的。更改某些属性可能会产生意想不到的副作用。
下一个示例,PropertiesTest
,创建一个 Properties
对象,并从 myProperties.txt
初始化它。
subliminal.message=Buy StayPuft Marshmallows!
PropertiesTest
然后使用 System.setProperties
将新的 Properties
对象安装为当前系统属性集。
import java.io.FileInputStream;
import java.util.Properties;
public class PropertiesTest {
public static void main(String[] args)
throws Exception {
// set up new properties object
// from file "myProperties.txt"
FileInputStream propFile =
new FileInputStream( "myProperties.txt");
Properties p =
new Properties(System.getProperties());
p.load(propFile);
// set the system properties
System.setProperties(p);
// display new properties
System.getProperties().list(System.out);
}
}
注意 PropertiesTest
如何创建 Properties
对象 p
,并将其用作 setProperties
的参数:
Properties p = new Properties(System.getProperties());
此语句使用当前系统属性集初始化新的属性对象 p
,在这个小应用程序的情况下,这是运行时系统初始化的属性集。然后应用程序从文件 myProperties.txt
中加载额外的属性到 p
中,并将系统属性设置为 p
。这将导致将 myProperties.txt
中列出的属性添加到运行时系统在启动时创建的属性集中。请注意,应用程序可以创建没有任何默认 Properties
对象的 p
,如下所示:
Properties p = new Properties();
还要注意系统属性的值是可以被覆盖的!例如,如果 myProperties.txt
包含以下行,则 java.vendor
系统属性将被覆盖:
java.vendor=Acme Software Company
一般来说,要小心不要覆盖系统属性。
setProperties
方法更改当前运行应用程序的系统属性集。这些更改不是持久的。也就是说,在应用程序内更改系统属性不会影响此应用程序或任何其他应用程序的将来调用 Java 解释器。运行时系统每次启动时都会重新初始化系统属性。如果要使系统属性的更改持久化,则应用程序必须在退出之前将值写入某个文件,并在启动时重新读取。
安全管理器
原文:
docs.oracle.com/javase/tutorial/essential/environment/security.html
安全管理器是为应用程序定义安全策略的对象。该策略指定了不安全或敏感的操作。安全策略不允许的任何操作都会导致抛出SecurityException
。应用程序还可以查询其安全管理器以发现哪些操作是允许的。
通常,Web 小程序在浏览器或 Java Web Start 插件提供的安全管理器下运行。其他类型的应用程序通常在没有安全管理器的情况下运行,除非应用程序本身定义了一个。如果没有安全管理器存在,应用程序就没有安全策略,并且可以无限制地运行。
本节解释了应用程序如何与现有安全管理器交互。有关更详细的信息,包括如何设计安全管理器的信息,请参考安全指南。
与安全管理器交互
安全管理器是SecurityManager
类型的对象;要获取对此对象的引用,请调用System.getSecurityManager
。
SecurityManager appsm = System.getSecurityManager();
如果没有安全管理器,则此方法返回null
。
一旦应用程序有了安全管理器对象的引用,它可以请求执行特定操作的权限。标准库中的许多类都会这样做。例如,System.exit
用于终止具有退出状态的 Java 虚拟机,会调用SecurityManager.checkExit
来确保当前线程有权限关闭应用程序。
SecurityManager 类定义了许多其他方法,用于验证其他类型的操作。例如,SecurityManager.checkAccess
验证线程访问,SecurityManager.checkPropertyAccess
验证对指定属性的访问。每个操作或操作组都有自己的check*XXX*()
方法。
此外,check*XXX*()
方法集表示已受安全管理器保护的操作集。通常,应用程序不必直接调用任何check*XXX*()
方法。
识别安全违规
许多在没有安全管理器的情况下是例行操作的操作,在有安全管理器的情况下可能会抛出SecurityException
。即使调用一个没有记录为抛出SecurityException
的方法也是如此。例如,考虑以下用于写入文件的代码:
reader = new FileWriter("xanadu.txt");
在没有安全管理器的情况下,此语句执行时不会出错,前提是xanadu.txt
存在且可写。但假设此语句插入到一个通常在不允许文件输出的安全管理器下运行的 Web 小程序中。可能会导致以下错误消息:
*appletviewer fileApplet.html*
Exception in thread "AWT-EventQueue-1" java.security.AccessControlException: access denied (java.io.FilePermission xanadu.txt write)
at java.security.AccessControlContext.checkPermission(AccessControlContext.java:323)
at java.security.AccessController.checkPermission(AccessController.java:546)
at java.lang.SecurityManager.checkPermission(SecurityManager.java:532)
at java.lang.SecurityManager.checkWrite(SecurityManager.java:962)
at java.io.FileOutputStream.<init>(FileOutputStream.java:169)
at java.io.FileOutputStream.<init>(FileOutputStream.java:70)
at java.io.FileWriter.<init>(FileWriter.java:46)
*...*
请注意,在这种情况下抛出的具体异常java.security.AccessControlException
是SecurityException
的子类。
System 中的其他方法
原文:
docs.oracle.com/javase/tutorial/essential/environment/sysmisc.html
本节描述了一些在前几节中未涵盖的System
中的方法。
arrayCopy
方法有效地在数组之间复制数据。有关更多信息,请参考 Arrays 中的 Language Basics 课程。
currentTimeMillis
和 nanoTime
方法在应用程序执行期间测量时间间隔很有用。要测量毫秒级时间间隔,需要在间隔开始和结束时分别调用currentTimeMillis
,并将第一个返回值从第二个返回值中减去。类似地,连续调用nanoTime
可以测量纳秒级时间间隔。
注意: currentTimeMillis
和 nanoTime
的准确性受操作系统提供的时间服务限制。不要假设currentTimeMillis
精确到最近的毫秒,也不要假设nanoTime
精确到最近的纳秒。此外,既不应该使用currentTimeMillis
也不应该使用nanoTime
来确定当前时间。应使用高级方法,如java.util.Calendar.getInstance
。
exit
方法导致 Java 虚拟机关闭,退出状态由参数指定。退出状态可供启动应用程序的进程使用。按照惯例,退出状态为0
表示应用程序正常终止,而其他任何值都是错误代码。
PATH 和 CLASSPATH
原文:
docs.oracle.com/javase/tutorial/essential/environment/paths.html
本节解释了如何在 Microsoft Windows、Solaris 和 Linux 上使用PATH
和CLASSPATH
环境变量。请查阅随 Java 开发工具包(JDK)软件包安装包含的安装说明以获取最新信息。
安装软件后,JDK 目录将具有如下结构。
bin
目录包含编译器和启动器。
更新 PATH 环境变量(Microsoft Windows)
没有设置PATH
环境变量也可以正常运行 Java 应用程序。或者,您可以选择性地设置它以方便使用。
如果要方便地从任何目录运行可执行文件(javac.exe
、java.exe
、javadoc.exe
等),而不必键入命令的完整路径,则设置PATH
环境变量。如果不设置PATH
变量,每次运行时都需要指定可执行文件的完整路径,例如:
C:\Java\jdk1.7.0\bin\javac MyClass.java
PATH
环境变量是一系列由分号(;
)分隔的目录。Microsoft Windows 按照从左到右的顺序在PATH
目录中查找程序。每次只能在路径中有一个 JDK 的bin
目录(第一个之后的将被忽略),因此如果已经存在一个,可以更新该特定条目。
以下是一个PATH
环境变量的示例:
C:\Java\jdk1.7.0\bin;C:\Windows\System32\;C:\Windows\;C:\Windows\System32\Wbem
将PATH
环境变量永久设置是有用的,这样在重新启动后它将保留。要对PATH
变量进行永久更改,请使用控制面板中的系统图标。具体的操作步骤因 Windows 版本而异:
Windows XP
-
选择开始,选择控制面板。双击系统,选择高级选项卡。
-
点击环境变量。在系统变量部分,找到
PATH
环境变量并选择它。点击编辑。如果PATH
环境变量不存在,点击新建
。 -
在编辑系统变量(或新建系统变量)窗口中,指定
PATH
环境变量的值。点击确定。通过点击确定关闭所有剩余窗口。
Windows Vista:
-
从桌面上右键单击我的电脑图标。
-
从上下文菜单中选择属性。
-
点击高级选项卡(在 Vista 中点击高级系统设置链接)。
-
点击环境变量。在系统变量部分,找到
PATH
环境变量并选择它。点击编辑。如果PATH
环境变量不存在,点击新建
。 -
在编辑系统变量(或新建系统变量)窗口中,指定
PATH
环境变量的值。点击确定。通过点击确定关闭所有剩余窗口。
Windows 7:
-
从桌面上右键单击计算机图标。
-
从上下文菜单中选择属性。
-
点击高级系统设置链接。
-
点击环境变量。在系统变量部分,找到
PATH
环境变量并选择它。点击编辑。如果PATH
环境变量不存在,点击新建
。 -
在编辑系统变量(或新建系统变量)窗口中,指定
PATH
环境变量的值。点击确定。通过点击确定关闭所有剩余窗口。
注意:在从控制面板编辑PATH
环境变量时,可能会看到类似以下内容的环境变量:
%JAVA_HOME%\bin;%SystemRoot%\system32;%SystemRoot%;%SystemRoot%\System32\Wbem
用百分号(%
)括起来的变量是现有的环境变量。如果这些变量中的一个在控制面板的环境变量窗口中列出(如JAVA_HOME
),则可以编辑其值。如果未显示,则是操作系统定义的特殊环境变量。例如,SystemRoot
是 Microsoft Windows 系统文件夹的位置。要获取环境变量的值,请在命令提示符下输入以下内容。(此示例获取SystemRoot
环境变量的值):
echo %SystemRoot%
更新 PATH 变量(Solaris 和 Linux)
您可以很好地运行 JDK 而不设置PATH
变量,或者可以选择设置它作为便利。但是,如果要能够从任何目录运行可执行文件(javac
、java
、javadoc
等),而不必键入命令的完整路径,则应该设置路径变量。如果不设置PATH
变量,每次运行时都需要指定可执行文件的完整路径,例如:
% /usr/local/jdk1.7.0/bin/javac MyClass.java
要查看路径是否正确设置,请执行:
% java -version
如果可以找到,这将打印java
工具的版本。如果版本过旧或者出现错误java: Command not found,则路径未正确设置。
要永久设置路径,请在启动文件中设置路径。
对于 C shell(csh
),编辑启动文件(~/.cshrc
):
set path=(/usr/local/jdk1.7.0/bin $path)
对于bash
,编辑启动文件(~/.bashrc
):
PATH=/usr/local/jdk1.7.0/bin:$PATH
export PATH
对于ksh
,启动文件由环境变量ENV
命名。要设置路径:
PATH=/usr/local/jdk1.7.0/bin:$PATH
export PATH
对于sh
,编辑配置文件(~/.profile
):
PATH=/usr/local/jdk1.7.0/bin:$PATH
export PATH
然后加载启动文件,并通过重复java
命令验证路径是否设置:
对于 C shell(csh
):
% source ~/.cshrc
% java -version
对于ksh
、bash
或sh
:
% . /.profile
% java -version
检查 CLASSPATH 变量(所有平台)
CLASSPATH
变量是告诉应用程序,包括 JDK 工具,在哪里查找用户类的一种方式。(作为 JRE、JDK 平台和扩展的一部分的类应该通过其他方式定义,比如引导类路径或扩展目录。)
指定类路径的首选方式是使用-cp
命令行开关。这允许为每个应用程序单独设置CLASSPATH
,而不影响其他应用程序。设置CLASSPATH
可能有些棘手,应谨慎执行。
类路径的默认值为“.”,意味着只搜索当前目录。指定CLASSPATH
变量或-cp
命令行开关会覆盖此值。
要检查在 Microsoft Windows NT/2000/XP 上是否设置了CLASSPATH
,执行以下操作:
C:> echo %CLASSPATH%
在 Solaris 或 Linux 上,执行以下操作:
% echo $CLASSPATH
如果未设置CLASSPATH
,将会出现CLASSPATH: Undefined variable错误(Solaris 或 Linux)或者简单显示%CLASSPATH%(Microsoft Windows NT/2000/XP)。
要修改CLASSPATH
,使用与修改PATH
变量相同的过程。
类路径通配符允许您在类路径中包含整个目录的.jar
文件,而无需逐个命名它们。有关更多信息,包括类路径通配符的解释以及如何清理CLASSPATH
环境变量的详细说明,请参阅设置类路径技术说明。
平台环境的问题和练习
原文:
docs.oracle.com/javase/tutorial/essential/environment/QandE/questions.html
问题
1. 一个程序员安装了一个包含在.jar 文件中的新库。为了从他的代码中访问该库,他将 CLASSPATH 环境变量设置为指向新的.jar 文件。现在他发现当他尝试启动简单的应用程序时,会收到错误消息:
java Hello
Exception in thread "main" java.lang.NoClassDefFoundError: Hello
在这种情况下,Hello
类被编译成一个在当前目录中的.class 文件,但java
命令似乎找不到它。出了什么问题?
练习
1. 编写一个名为PersistentEcho
的应用程序,具有以下功能:
-
如果使用命令行参数运行
PersistentEcho
,它会打印出这些参数。它还会将打印出的字符串保存到一个属性中,并将该属性保存到名为PersistentEcho.txt
的文件中。 -
如果在没有命令行参数的情况下运行
PersistentEcho
,它会查找名为 PERSISTENTECHO 的环境变量。如果该变量存在,PersistentEcho
会打印出其值,并以与命令行参数相同的方式保存该值。 -
如果在没有命令行参数的情况下运行
PersistentEcho
,并且未定义 PERSISTENTECHO 环境变量,则它会从PersistentEcho.txt
中检索属性值并打印出来。
检查你的答案。
课程:正则表达式
原文:
docs.oracle.com/javase/tutorial/essential/regex/index.html
本课程解释了如何使用[
java.util.regex](https://docs.oracle.com/javase/8/docs/api/java/util/regex/package-summary.html)
API 进行正则表达式的模式匹配。尽管此包接受的语法类似于Perl编程语言,但并不需要了解 Perl。本课程从基础知识开始,逐渐深入,涵盖更高级的技术。
介绍
提供了正则表达式的一般概述。还介绍了构成此 API 的核心类。
测试工具
定义了一个简单的应用程序,用于测试正则表达式的模式匹配。
字符串字面值
介绍了基本的模式匹配、元字符和引用。
字符类
描述了简单字符类、否定、范围、并集、交集和差集。
预定义字符类
描述了用于空白、单词和数字字符的基本预定义字符类。
量词
解释了贪婪、懒惰和占有量词,用于匹配指定表达式x的次数。
捕获组
解释了如何将多个字符视为单个单元。
边界匹配器
描述了行、单词和输入边界。
Pattern 类的方法
探讨了Pattern
类的其他有用方法,并探索了高级功能,如使用标志进行编译和使用嵌入式标志表达式。
Matcher 类的方法
描述了Matcher
类的常用方法。
PatternSyntaxException 类的方法
描述了如何检查PatternSyntaxException
。
其他资源
要了解更多关于正则表达式的信息,请查阅此部分的其他资源。
介绍
原文:
docs.oracle.com/javase/tutorial/essential/regex/intro.html
什么是正则表达式?
正则表达式是一种描述一组字符串的方式,基于集合中每个字符串共享的共同特征。它们可用于搜索、编辑或操作文本和数据。您必须学习一种特定的语法来创建正则表达式,这种语法超出了 Java 编程语言的正常语法。正则表达式的复杂程度各不相同,但一旦您了解了它们构建的基础知识,就能够解读(或创建)任何正则表达式。
这个教程介绍了由java.util.regex
API 支持的正则表达式语法,并提供了几个实际示例来说明各种对象是如何交互的。在正则表达式的世界中,有许多不同的风格可供选择,如 grep、Perl、Tcl、Python、PHP 和 awk。java.util.regex
API 中的正则表达式语法与 Perl 中的最为相似。
此包中如何表示正则表达式?
java.util.regex
包主要由三个类组成:Pattern
、Matcher
和PatternSyntaxException
。
-
一个
Pattern
对象是正则表达式的编译表示。Pattern
类不提供公共构造函数。要创建一个模式,您必须首先调用其public static compile
方法之一,然后该方法将返回一个Pattern
对象。这些方法接受一个正则表达式作为第一个参数;这个教程的前几课将教会您所需的语法。 -
一个
Matcher
对象是解释模式并对输入字符串执行匹配操作的引擎。与Pattern
类一样,Matcher
不定义公共构造函数。您可以通过在Pattern
对象上调用matcher
方法来获取Matcher
对象。 -
一个
PatternSyntaxException
对象是指示正则表达式模式中语法错误的未经检查的异常。
这个教程的最后几课将详细探讨每个类。但首先,您必须了解正则表达式是如何构建的。因此,下一节介绍了一个简单的测试工具,将被反复用来探索它们的语法。
测试工具
原文:
docs.oracle.com/javase/tutorial/essential/regex/test_harness.html
这一部分定义了一个可重复使用的测试工具,RegexTestHarness.java
,用于探索此 API 支持的正则表达式构造。运行此代码的命令是java RegexTestHarness
;不接受命令行参数。该应用程序将重复循环,提示用户输入正则表达式和输入字符串。使用这个测试工具是可选的,但您可能会发现在探索下面页面讨论的测试用例时很方便。
import java.io.Console;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
public class RegexTestHarness {
public static void main(String[] args){
Console console = System.console();
if (console == null) {
System.err.println("No console.");
System.exit(1);
}
while (true) {
Pattern pattern =
Pattern.compile(console.readLine("%nEnter your regex: "));
Matcher matcher =
pattern.matcher(console.readLine("Enter input string to search: "));
boolean found = false;
while (matcher.find()) {
console.format("I found the text" +
" \"%s\" starting at " +
"index %d and ending at index %d.%n",
matcher.group(),
matcher.start(),
matcher.end());
found = true;
}
if(!found){
console.format("No match found.%n");
}
}
}
}
在继续下一部分之前,请保存并编译此代码,以确保您的开发环境支持所需的包。
字符串字面值
原文:
docs.oracle.com/javase/tutorial/essential/regex/literals.html
此 API 支持的最基本的模式匹配形式是字符串字面值的匹配。例如,如果正则表达式是foo
,输入字符串是foo
,则匹配将成功,因为字符串是相同的。使用测试工具尝试一下:
Enter your regex: foo
Enter input string to search: foo
I found the text foo starting at index 0 and ending at index 3.
这次匹配成功了。请注意,虽然输入字符串长度为 3 个字符,但开始索引为 0,结束索引为 3。按照惯例,范围包括开始索引但不包括结束索引,如下图所示:
字符串字面值 foo,带有编号的单元格和索引值。
字符串中的每个字符都位于自己的单元格中,索引位置指向每个单元格之间。字符串“foo”从索引 0 开始,到索引 3 结束,即使字符本身只占据单元格 0、1 和 2。
在后续匹配中,您会注意到一些重叠;下一个匹配的开始索引与上一个匹配的结束索引相同:
Enter your regex: foo
Enter input string to search: foofoofoo
I found the text foo starting at index 0 and ending at index 3.
I found the text foo starting at index 3 and ending at index 6.
I found the text foo starting at index 6 and ending at index 9.
元字符
此 API 还支持许多特殊字符,这些字符会影响模式匹配的方式。将正则表达式更改为cat.
,将输入字符串更改为cats
。输出将如下所示:
Enter your regex: cat.
Enter input string to search: cats
I found the text cats starting at index 0 and ending at index 4.
即使输入字符串中没有点“.
”,匹配仍然成功。这是因为点是一个元字符 由匹配器解释的具有特殊含义的字符。元字符.
表示“任何字符”,这就是为什么在这个例子中匹配成功。
此 API 支持的元字符为:<([{\^-=$!|]})?*+.>
注意: 在某些情况下,上面列出的特殊字符 不 会被视为元字符。随着您对正则表达式构造方式的了解越来越多,您会遇到这种情况。但是,您可以使用此列表来检查特定字符是否会被视为元字符。例如,字符@
和#
从不具有特殊含义。
有两种方法可以强制将元字符视为普通字符:
-
在元字符之前加上反斜杠,或
-
将其放在
\Q
(开始引用)和\E
(结束引用)之间。
在使用这种技术时,\Q
和\E
可以放置在表达式的任何位置,只要\Q
先出现。
字符类
原文:
docs.oracle.com/javase/tutorial/essential/regex/char_classes.html
如果您浏览Pattern
类的规范,您会看到总结支持的正则表达式构造的表格。在“字符类”部分,您会找到以下内容:
构造 | 描述 |
---|---|
[abc] |
a、b 或 c(简单类) |
[^abc] |
除了 a、b 或 c 之外的任何字符(否定) |
[a-zA-Z] |
a 到 z,或 A 到 Z,包括(范围) |
[a-d[m-p]] |
a 到 d,或者 m 到 p:[a-dm-p](并集) |
[a-z&&[def]] |
d、e 或 f(交集) |
[a-z&&[^bc]] |
a 到 z,除了 b 和 c:[ad-z](减法) |
[a-z&&[^m-p]] |
a 到 z,但不包括 m 到 p:[a-lq-z](减法) |
左侧列指定了正则表达式构造,而右侧列描述了每个构造将匹配的条件。
注意:短语“字符类”中的“类”并不指代.class
文件。在正则表达式的上下文中,字符类是一组方括号内的字符。它指定了成功匹配给定输入字符串中的单个字符的字符。
简单类
字符类的最基本形式是在方括号内简单地放置一组字符。例如,正则表达式[bcr]at
将匹配单词"bat"、"cat"或"rat",因为它定义了一个字符类(接受"b"、"c"或"r"中的任意一个)作为其第一个字符。
Enter your regex: [bcr]at
Enter input string to search: bat
I found the text "bat" starting at index 0 and ending at index 3.
Enter your regex: [bcr]at
Enter input string to search: cat
I found the text "cat" starting at index 0 and ending at index 3.
Enter your regex: [bcr]at
Enter input string to search: rat
I found the text "rat" starting at index 0 and ending at index 3.
Enter your regex: [bcr]at
Enter input string to search: hat
No match found.
在上面的例子中,只有当第一个字母与字符类定义的字符之一匹配时,整体匹配才成功。
否定
要匹配除列出的字符之外的所有字符,请在字符类的开头插入“^
”元字符。这种技术被称为否定。
Enter your regex: [^bcr]at
Enter input string to search: bat
No match found.
Enter your regex: [^bcr]at
Enter input string to search: cat
No match found.
Enter your regex: [^bcr]at
Enter input string to search: rat
No match found.
Enter your regex: [^bcr]at
Enter input string to search: hat
I found the text "hat" starting at index 0 and ending at index 3.
只有当输入字符串的第一个字符不包含字符类定义的任何字符时,匹配才成功。
范围
有时,您可能想要定义一个包含一系列值的字符类,比如字母"a 到 h"或数字"1 到 5"。要指定一个范围,只需在要匹配的第一个和最后一个字符之间插入“-
”元字符,比如[1-5]
或[a-h]
。您还可以在类内将不同的范围放在一起,以进一步扩展匹配可能性。例如,[a-zA-Z]
将匹配任何字母:a 到 z(小写)或 A 到 Z(大写)。
以下是一些范围和否定的示例:
Enter your regex: [a-c]
Enter input string to search: a
I found the text "a" starting at index 0 and ending at index 1.
Enter your regex: [a-c]
Enter input string to search: b
I found the text "b" starting at index 0 and ending at index 1.
Enter your regex: [a-c]
Enter input string to search: c
I found the text "c" starting at index 0 and ending at index 1.
Enter your regex: [a-c]
Enter input string to search: d
No match found.
Enter your regex: foo[1-5]
Enter input string to search: foo1
I found the text "foo1" starting at index 0 and ending at index 4.
Enter your regex: foo[1-5]
Enter input string to search: foo5
I found the text "foo5" starting at index 0 and ending at index 4.
Enter your regex: foo[1-5]
Enter input string to search: foo6
No match found.
Enter your regex: foo[¹-5]
Enter input string to search: foo1
No match found.
Enter your regex: foo[¹-5]
Enter input string to search: foo6
I found the text "foo6" starting at index 0 and ending at index 4.
并集
您还可以使用并集来创建由两个或多个单独字符类组成的单一字符类。要创建并集,只需将一个类嵌套在另一个类中,比如[0-4[6-8]]
。这个特定的并集创建了一个单一的字符类,匹配数字 0、1、2、3、4、6、7 和 8。
Enter your regex: [0-4[6-8]]
Enter input string to search: 0
I found the text "0" starting at index 0 and ending at index 1.
Enter your regex: [0-4[6-8]]
Enter input string to search: 5
No match found.
Enter your regex: [0-4[6-8]]
Enter input string to search: 6
I found the text "6" starting at index 0 and ending at index 1.
Enter your regex: [0-4[6-8]]
Enter input string to search: 8
I found the text "8" starting at index 0 and ending at index 1.
Enter your regex: [0-4[6-8]]
Enter input string to search: 9
No match found.
交集
要创建一个仅匹配所有嵌套类共同字符的单一字符类,请使用&&
,比如[0-9&&[345]]
。这个特定的交集创建了一个单一的字符类,仅匹配两个字符类共同的数字:3、4 和 5。
Enter your regex: [0-9&&[345]]
Enter input string to search: 3
I found the text "3" starting at index 0 and ending at index 1.
Enter your regex: [0-9&&[345]]
Enter input string to search: 4
I found the text "4" starting at index 0 and ending at index 1.
Enter your regex: [0-9&&[345]]
Enter input string to search: 5
I found the text "5" starting at index 0 and ending at index 1.
Enter your regex: [0-9&&[345]]
Enter input string to search: 2
No match found.
Enter your regex: [0-9&&[345]]
Enter input string to search: 6
No match found.
这里有一个示例,展示了两个范围的交集:
Enter your regex: [2-8&&[4-6]]
Enter input string to search: 3
No match found.
Enter your regex: [2-8&&[4-6]]
Enter input string to search: 4
I found the text "4" starting at index 0 and ending at index 1.
Enter your regex: [2-8&&[4-6]]
Enter input string to search: 5
I found the text "5" starting at index 0 and ending at index 1.
Enter your regex: [2-8&&[4-6]]
Enter input string to search: 6
I found the text "6" starting at index 0 and ending at index 1.
Enter your regex: [2-8&&[4-6]]
Enter input string to search: 7
No match found.
减法
最后,您可以使用减法来否定一个或多个嵌套的字符类,比如[0-9&&[³⁴⁵]]
。这个示例创建了一个单一的字符类,匹配从 0 到 9 的所有内容,除了数字 3、4 和 5。
Enter your regex: [0-9&&[³⁴⁵]]
Enter input string to search: 2
I found the text "2" starting at index 0 and ending at index 1.
Enter your regex: [0-9&&[³⁴⁵]]
Enter input string to search: 3
No match found.
Enter your regex: [0-9&&[³⁴⁵]]
Enter input string to search: 4
No match found.
Enter your regex: [0-9&&[³⁴⁵]]
Enter input string to search: 5
No match found.
Enter your regex: [0-9&&[³⁴⁵]]
Enter input string to search: 6
I found the text "6" starting at index 0 and ending at index 1.
Enter your regex: [0-9&&[³⁴⁵]]
Enter input string to search: 9
I found the text "9" starting at index 0 and ending at index 1.
现在我们已经介绍了如何创建字符类,您可能想在继续下一节之前查看字符类表。
预定义字符类
原文:
docs.oracle.com/javase/tutorial/essential/regex/pre_char_classes.html
Pattern
API 包含许多有用的预定义字符类,提供了常用正则表达式的便捷简写:
构造 | 描述 |
---|---|
. |
任何字符(可能匹配行终止符,也可能不匹配) |
\d |
一个数字:[0-9] |
\D |
一个非数字:[⁰-9] |
\s |
一个空白字符:[ \t\n\x0B\f\r] |
\S |
一个非空白字符:[^\s] |
\w |
一个单词字符:[a-zA-Z_0-9] |
\W |
一个非单词字符:[^\w] |
在上表中,左列中的每个构造都是右列中字符类的简写。例如,\d
表示数字范围(0-9),\w
表示单词字符(任何小写字母、任何大写字母、下划线字符或任何数字)。尽可能使用预定义类。它们使您的代码更易阅读,并消除由格式不正确的字符类引入的错误。
以反斜杠开头的构造称为转义构造。我们在字符串文字部分预览了转义构造,其中我们提到了反斜杠和引号的使用。如果您在字符串文字中使用转义构造,必须在字符串编译之前在反斜杠前加上另一个反斜杠。例如:
private final String REGEX = "\\d"; // a single digit
在这个例子中,\d
是正则表达式;额外的反斜杠是为了代码编译而需要的。测试工具直接从Console
读取表达式,因此额外的反斜杠是不必要的。
以下示例演示了预定义字符类的使用。
Enter your regex: .
Enter input string to search: @
I found the text "@" starting at index 0 and ending at index 1.
Enter your regex: .
Enter input string to search: 1
I found the text "1" starting at index 0 and ending at index 1.
Enter your regex: .
Enter input string to search: a
I found the text "a" starting at index 0 and ending at index 1.
Enter your regex: \d
Enter input string to search: 1
I found the text "1" starting at index 0 and ending at index 1.
Enter your regex: \d
Enter input string to search: a
No match found.
Enter your regex: \D
Enter input string to search: 1
No match found.
Enter your regex: \D
Enter input string to search: a
I found the text "a" starting at index 0 and ending at index 1.
Enter your regex: \s
Enter input string to search:
I found the text " " starting at index 0 and ending at index 1.
Enter your regex: \s
Enter input string to search: a
No match found.
Enter your regex: \S
Enter input string to search:
No match found.
Enter your regex: \S
Enter input string to search: a
I found the text "a" starting at index 0 and ending at index 1.
Enter your regex: \w
Enter input string to search: a
I found the text "a" starting at index 0 and ending at index 1.
Enter your regex: \w
Enter input string to search: !
No match found.
Enter your regex: \W
Enter input string to search: a
No match found.
Enter your regex: \W
Enter input string to search: !
I found the text "!" starting at index 0 and ending at index 1.
在前三个例子中,正则表达式只是.
("点"元字符),表示"任何字符"。因此,在所有三种情况下匹配都成功(随机选择的@
字符、一个数字和一个字母)。其余的例子每个都使用了预定义字符类表中的一个正则表达式构造。您可以参考这个表格来理解每个匹配背后的逻辑:
-
\d
匹配所有数字 -
\s
匹配空格 -
\w
匹配单词字符
或者,大写字母表示相反的意思:
-
\D
匹配非数字 -
\S
匹配非空格 -
\W
匹配非单词字符
量词
原文:
docs.oracle.com/javase/tutorial/essential/regex/quant.html
量词允许您指定要匹配的出现次数。为方便起见,下面呈现了 Pattern API 规范的三个部分,描述了贪婪、勉强和占有量词。乍一看,似乎量词X?
、X??
和X?+
做的事情完全相同,因为它们都承诺匹配"X
,一次或零次"。有微妙的实现差异,将在本节末尾解释。
贪婪 | 勉强 | 占有 | 含义 |
---|---|---|---|
X? |
X?? |
X?+ |
X ,一次或零次 |
X* |
X*? |
X*+ |
X ,零次或多次 |
X+ |
X+? |
X++ |
X ,一次或多次 |
X{n} |
X{n}? |
X{n}+ |
X ,恰好n 次 |
X{n,} |
X{n,}? |
X{n,}+ |
X ,至少n 次 |
X{n,m} |
X{n,m}? |
X{n,m}+ |
X ,至少n 次但不超过m 次 |
让我们通过创建三个不同的正则表达式来开始我们对贪婪量词的研究:字母"a"后面跟着?
、*
或+
。让我们看看当这些表达式针对空输入字符串""
进行测试时会发生什么:
Enter your regex: a?
Enter input string to search:
I found the text "" starting at index 0 and ending at index 0.
Enter your regex: a*
Enter input string to search:
I found the text "" starting at index 0 and ending at index 0.
Enter your regex: a+
Enter input string to search:
No match found.
零长度匹配
在上面的例子中,前两种情况匹配成功,因为表达式a?
和a*
都允许字母a
出现零次。您还会注意到开始和结束索引都是零,这与我们迄今为止看到的任何示例都不同。空输入字符串""
没有长度,因此测试只是在索引 0 处匹配到了空。这种匹配称为零长度匹配。零长度匹配可以出现在几种情况下:在空输入字符串中,在输入字符串的开头,在输入字符串的最后一个字符之后,或在输入字符串的任意两个字符之间。零长度匹配很容易识别,因为它们始终从相同的索引位置开始和结束。
让我们通过几个例子来探索零长度匹配。将输入字符串更改为单个字母"a",您会注意到一些有趣的事情:
Enter your regex: a?
Enter input string to search: a
I found the text "a" starting at index 0 and ending at index 1.
I found the text "" starting at index 1 and ending at index 1.
Enter your regex: a*
Enter input string to search: a
I found the text "a" starting at index 0 and ending at index 1.
I found the text "" starting at index 1 and ending at index 1.
Enter your regex: a+
Enter input string to search: a
I found the text "a" starting at index 0 and ending at index 1.
所有三个量词都找到了字母"a",但前两个还在索引 1 处找到了零长度匹配;也就是说,在输入字符串的最后一个字符之后。请记住,匹配器将字符"a"视为位于索引 0 和索引 1 之间的单元格中,我们的测试工具循环直到无法找到匹配。根据使用的量词,最后一个字符后面的“无内容”可能会或可能不会触发匹配。
现在将输入字符串更改为连续五次的字母"a",您将得到以下结果:
Enter your regex: a?
Enter input string to search: aaaaa
I found the text "a" starting at index 0 and ending at index 1.
I found the text "a" starting at index 1 and ending at index 2.
I found the text "a" starting at index 2 and ending at index 3.
I found the text "a" starting at index 3 and ending at index 4.
I found the text "a" starting at index 4 and ending at index 5.
I found the text "" starting at index 5 and ending at index 5.
Enter your regex: a*
Enter input string to search: aaaaa
I found the text "aaaaa" starting at index 0 and ending at index 5.
I found the text "" starting at index 5 and ending at index 5.
Enter your regex: a+
Enter input string to search: aaaaa
I found the text "aaaaa" starting at index 0 and ending at index 5.
表达式 a?
为每个字符找到一个匹配,因为它匹配 "a" 出现零次或一次时。表达式 a*
找到两个单独的匹配:第一个匹配中的所有字母 "a",然后是索引 5 处最后一个字符后的零长度匹配。最后,a+
匹配所有字母 "a" 的出现,忽略最后索引处的 "nothing" 的存在。
此时,您可能想知道如果前两个量词遇到除 "a" 外的字母会发生什么。例如,如果它遇到字母 "b",比如 "ababaaaab" 会发生什么?
让我们来看看:
Enter your regex: a?
Enter input string to search: ababaaaab
I found the text "a" starting at index 0 and ending at index 1.
I found the text "" starting at index 1 and ending at index 1.
I found the text "a" starting at index 2 and ending at index 3.
I found the text "" starting at index 3 and ending at index 3.
I found the text "a" starting at index 4 and ending at index 5.
I found the text "a" starting at index 5 and ending at index 6.
I found the text "a" starting at index 6 and ending at index 7.
I found the text "a" starting at index 7 and ending at index 8.
I found the text "" starting at index 8 and ending at index 8.
I found the text "" starting at index 9 and ending at index 9.
Enter your regex: a*
Enter input string to search: ababaaaab
I found the text "a" starting at index 0 and ending at index 1.
I found the text "" starting at index 1 and ending at index 1.
I found the text "a" starting at index 2 and ending at index 3.
I found the text "" starting at index 3 and ending at index 3.
I found the text "aaaa" starting at index 4 and ending at index 8.
I found the text "" starting at index 8 and ending at index 8.
I found the text "" starting at index 9 and ending at index 9.
Enter your regex: a+
Enter input string to search: ababaaaab
I found the text "a" starting at index 0 and ending at index 1.
I found the text "a" starting at index 2 and ending at index 3.
I found the text "aaaa" starting at index 4 and ending at index 8.
尽管字母 "b" 出现在单元格 1、3 和 8 中,输出报告在这些位置上的零长度匹配。正则表达式 a?
并不是专门寻找字母 "b";它只是查找字母 "a" 的存在(或不存在)。如果量词允许 "a" 匹配零次,那么输入字符串中不是 "a" 的任何内容都会显示为零长度匹配。剩下的 a 根据前面示例中讨论的规则进行匹配。
要精确匹配一个模式 n 次,只需在大括号内指定数字:
Enter your regex: a{3}
Enter input string to search: aa
No match found.
Enter your regex: a{3}
Enter input string to search: aaa
I found the text "aaa" starting at index 0 and ending at index 3.
Enter your regex: a{3}
Enter input string to search: aaaa
I found the text "aaa" starting at index 0 and ending at index 3.
在这里,正则表达式 a{3}
在连续三个字母 "a" 中搜索三次出现的情况。第一个测试失败,因为输入字符串中没有足够的 a 与之匹配。第二个测试包含输入字符串中恰好 3 个 a,触发了匹配。第三个测试也触发了匹配,因为输入字符串开头恰好有 3 个 a。在此之后的任何内容都与第一个匹配无关。如果在那之后再次出现该模式,将触发后续的匹配:
Enter your regex: a{3}
Enter input string to search: aaaaaaaaa
I found the text "aaa" starting at index 0 and ending at index 3.
I found the text "aaa" starting at index 3 and ending at index 6.
I found the text "aaa" starting at index 6 and ending at index 9.
要求模式至少出现 n 次,数字后加逗号:
Enter your regex: a{3,}
Enter input string to search: aaaaaaaaa
I found the text "aaaaaaaaa" starting at index 0 and ending at index 9.
使用相同的输入字符串,此测试仅找到一个匹配,因为连续 9 个 a 满足 "至少" 3 个 a 的需求。
最后,要指定出现次数的上限,在大括号内添加第二个数字:
Enter your regex: a{3,6} // find at least 3 (but no more than 6) a's in a row
Enter input string to search: aaaaaaaaa
I found the text "aaaaaa" starting at index 0 and ending at index 6.
I found the text "aaa" starting at index 6 and ending at index 9.
第一个匹配强制停在 6 个字符的上限处。第二个匹配包括剩下的内容,这恰好是三个 a,这是允许的最小字符数。如果输入字符串短了一个字符,就不会有第二个匹配,因为只剩下两个 a。
捕获组和带量词的字符类
到目前为止,我们只在包含一个字符的输入字符串上测试了量词。实际上,量词只能附加到一个字符上,因此正则表达式 "abc+" 意味着 "a,后跟 b,后跟 c 一次或多次"。它不意味着 "abc" 一次或多次。然而,量词也可以附加到字符类和捕获组,例如 [abc]+
(a 或 b 或 c,一次或多次)或 (abc)+
(组 "abc",一次或多次)。
让我们通过连续三次指定组(dog)
来说明。
Enter your regex: (dog){3}
Enter input string to search: dogdogdogdogdogdog
I found the text "dogdogdog" starting at index 0 and ending at index 9.
I found the text "dogdogdog" starting at index 9 and ending at index 18.
Enter your regex: dog{3}
Enter input string to search: dogdogdogdogdogdog
No match found.
在第一个例子中找到三个匹配,因为量词应用于整个捕获组。然而,去掉括号,匹配失败,因为量词{3}
现在仅适用于字母"g"。
同样,我们可以将一个量词应用于整个字符类:
Enter your regex: [abc]{3}
Enter input string to search: abccabaaaccbbbc
I found the text "abc" starting at index 0 and ending at index 3.
I found the text "cab" starting at index 3 and ending at index 6.
I found the text "aaa" starting at index 6 and ending at index 9.
I found the text "ccb" starting at index 9 and ending at index 12.
I found the text "bbc" starting at index 12 and ending at index 15.
Enter your regex: abc{3}
Enter input string to search: abccabaaaccbbbc
No match found.
在第一个例子中,量词{3}
应用于整个字符类,但在第二个例子中仅应用于字母"c"。
贪婪、勉强和占有量词之间的区别
贪婪、勉强和占有量词之间存在微妙的区别。
贪婪量词被认为是"贪婪"的,因为它们强制匹配器在尝试第一次匹配之前读取或吞掉整个输入字符串。如果第一次匹配尝试(整个输入字符串)失败,匹配器会向后退一个字符并重试,重复这个过程直到找到匹配或没有更多字符可以后退。根据表达式中使用的量词,它将尝试匹配的最后一件事是 1 个或 0 个字符。
然而,勉强量词采取相反的方法:它们从输入字符串的开头开始,然后不情愿地一次吃掉一个字符寻找匹配。它们尝试的最后一件事是整个输入字符串。
最后,占有量词总是吞掉整个输入字符串,尝试一次(仅一次)匹配。与贪婪量词不同,占有量词永远不会后退,即使这样做可以使整体匹配成功。
为了说明,考虑输入字符串xfooxxxxxxfoo
。
Enter your regex: .*foo // greedy quantifier
Enter input string to search: xfooxxxxxxfoo
I found the text "xfooxxxxxxfoo" starting at index 0 and ending at index 13.
Enter your regex: .*?foo // reluctant quantifier
Enter input string to search: xfooxxxxxxfoo
I found the text "xfoo" starting at index 0 and ending at index 4.
I found the text "xxxxxxfoo" starting at index 4 and ending at index 13.
Enter your regex: .*+foo // possessive quantifier
Enter input string to search: xfooxxxxxxfoo
No match found.
第一个例子使用贪婪量词.*
来找到"任何东西",零次或多次,然后是字母"f" "o" "o"
。因为量词是贪婪的,表达式的.*
部分首先吞掉整个输入字符串。此时,整体表达式无法成功,因为最后三个字母("f" "o" "o"
)已经被消耗。因此,匹配器慢慢地一次后退一个字母,直到最右边的"foo"出现,匹配成功并结束搜索。
然而,第二个例子是勉强的,因此它首先消耗"nothing"。因为"foo"不出现在字符串的开头,它被迫吞下第一个字母(一个"x"),这触发了第一个匹配在 0 和 4。我们的测试工具继续这个过程直到输入字符串耗尽。它在 4 和 13 找到另一个匹配。
第三个例子无法找到匹配,因为量词是占有的。在这种情况下,整个输入字符串被.*+
消耗,没有剩余内容来满足表达式末尾的"foo"。在想要占有所有内容而永远不后退的情况下使用占有量词;在匹配不立即找到的情况下,它将优于等效的贪婪量词。
捕获组
原文:
docs.oracle.com/javase/tutorial/essential/regex/groups.html
在上一节中,我们看到量词如何附加到一个字符、字符类或捕获组上。但直到现在,我们还没有详细讨论捕获组的概念。
捕获组是将多个字符视为单个单元的一种方式。它们是通过将要分组的字符放在一对括号中创建的。例如,正则表达式(dog)
创建了一个包含字母"d" "o"
和"g"
的单个组。与捕获组匹配的输入字符串部分将被保存在内存中,以便稍后通过反向引用进行调用(如下面在反向引用部分中讨论)。
编号
如Pattern
API 中所述,通过从左到右计算其开括号来对捕获组进行编号。例如,在表达式((A)(B(C)))
中,有四个这样的组:
-
((A)(B(C)))
-
(A)
-
(B(C))
-
(C)
要找出表达式中存在多少组,请在匹配器对象上调用groupCount
方法。groupCount
方法返回一个int
,显示匹配器模式中存在的捕获组数。在这个例子中,groupCount
将返回数字4
,显示该模式包含 4 个捕获组。
还有一个特殊组,组 0,它始终表示整个表达式。这个组不包括在groupCount
报告的总数中。以(?
开头的组是纯粹的、非捕获组,不捕获文本,也不计入组总数。(您将在 Pattern 类的方法部分后面看到非捕获组的示例。)
重要的是要理解组是如何编号的,因为一些Matcher
方法接受一个指定特定组号的int
作为参数:
-
public int start(int group)
:返回在先前匹配操作期间由给定组捕获的子序列的起始索引。 -
public int end (int group)
:返回在先前匹配操作期间由给定组捕获的子序列的最后一个字符的索引加一。 -
public String group (int group)
:返回在先前匹配操作期间由给定组捕获的输入子序列。
反向引用
匹配捕获组的输入字符串部分将被保存在内存中,以便通过反向引用进行后续调用。反向引用在正则表达式中被指定为反斜杠(\
)后跟表示要调用的组的数字。例如,表达式(\d\d)
定义了一个捕获组,匹配连续两个数字,可以在表达式中通过反向引用\1
稍后调用。
要匹配任意两个数字,后跟完全相同的两个数字,您可以使用(\d\d)\1
作为正则表达式:
Enter your regex: (\d\d)\1
Enter input string to search: 1212
I found the text "1212" starting at index 0 and ending at index 4.
如果更改最后两个数字,匹配将失败:
Enter your regex: (\d\d)\1
Enter input string to search: 1234
No match found.
对于嵌套的捕获组,反向引用的工作方式完全相同:指定一个反斜杠,后跟要调用的组的编号。
边界匹配器
原文:
docs.oracle.com/javase/tutorial/essential/regex/bounds.html
到目前为止,我们只关心匹配是否在特定输入字符串的某个位置发现。我们从不关心匹配发生在字符串的哪个位置。
通过使用边界匹配器,您可以使模式匹配更加精确。例如,也许您对查找特定单词感兴趣,但只有在它出现在行首或行尾时才匹配。或者您想知道匹配是否发生在单词边界上,或者在前一个匹配的末尾。
以下表格列出并解释了所有的边界匹配器。
边界构造 | 描述 |
---|---|
^ |
一行的开头 |
` | 边界构造 |
--- | --- |
^ |
一行的开头 |
一行的结尾 | |
\b |
单词边界 |
\B |
非单词边界 |
\A |
输入的开头 |
\G |
前一个匹配的结尾 |
\Z |
输入的结尾,但不包括最终终止符(如果有) |
\z |
输入的结尾 |
以下示例演示了边界匹配器^
和$
的使用。如上所述,^
匹配行的开头,$
匹配行的结尾。
Enter your regex: ^dog$
Enter input string to search: dog
I found the text "dog" starting at index 0 and ending at index 3.
Enter your regex: ^dog$
Enter input string to search: dog
No match found.
Enter your regex: \s*dog$
Enter input string to search: dog
I found the text " dog" starting at index 0 and ending at index 15.
Enter your regex: ^dog\w*
Enter input string to search: dogblahblah
I found the text "dogblahblah" starting at index 0 and ending at index 11.
第一个示例成功,因为模式占据了整个输入字符串。第二个示例失败,因为输入字符串开头包含额外的空格。第三个示例指定了一个允许无限空格,然后在行尾跟着"dog"的表达式。第四个示例要求"dog"必须出现在一行的开头,后面跟着无限数量的单词字符。
要检查模式是否从单词边界开始和结束(而不是在更长字符串中的子字符串),只需在两侧使用\b
;例如,\bdog\b
Enter your regex: \bdog\b
Enter input string to search: The dog plays in the yard.
I found the text "dog" starting at index 4 and ending at index 7.
Enter your regex: \bdog\b
Enter input string to search: The doggie plays in the yard.
No match found.
要在非单词边界上匹配表达式,请使用\B
:
Enter your regex: \bdog\B
Enter input string to search: The dog plays in the yard.
No match found.
Enter your regex: \bdog\B
Enter input string to search: The doggie plays in the yard.
I found the text "dog" starting at index 4 and ending at index 7.
要求匹配仅发生在前一个匹配的末尾,请使用\G
:
Enter your regex: dog
Enter input string to search: dog dog
I found the text "dog" starting at index 0 and ending at index 3.
I found the text "dog" starting at index 4 and ending at index 7.
Enter your regex: \Gdog
Enter input string to search: dog dog
I found the text "dog" starting at index 0 and ending at index 3.
这里第二个示例只找到一个匹配,因为第二次出现的"dog"不是从前一个匹配的末尾开始的。
标签:regex,index,教程,Java,ending,2022,Enter,found,starting From: https://www.cnblogs.com/apachecn/p/18131114