首页 > 编程语言 >Java 中文官方教程 2022 版(十)

Java 中文官方教程 2022 版(十)

时间:2024-04-12 14:23:08浏览次数:36  
标签:regex index 教程 Java ending 2022 Enter found starting

原文:docs.oracle.com/javase/tutorial/reallybigindex.html

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 是一个简单的接口,支持启动新任务。

  • ExecutorServiceExecutor 的子接口,增加了一些功能,有助于管理单个任务和执行器本身的生命周期。

  • ScheduledExecutorServiceExecutorService 的子接口,支持未来和/或定期执行任务。

通常,引用执行器对象的变量声明为这三种接口类型之一,而不是执行器类类型。

Executor 接口

Executor 接口提供了一个方法 execute,旨在成为常见线程创建习语的替代品。如果 r 是一个 Runnable 对象,e 是一个 Executor 对象,你可以替换

(new Thread(r)).start();

with

e.execute(r);

然而,execute 的定义不太具体。低级习语创建一个新线程并立即启动它。根据 Executor 的实现,execute 可能会做同样的事情,但更有可能使用现有的工作线程来运行 r,或者将 r 放入队列等待工作线程可用。(我们将在线程池部分描述工作线程。)

java.util.concurrent 中的执行器实现旨在充分利用更高级的 ExecutorServiceScheduledExecutorService 接口,尽管它们也与基本的 Executor 接口一起工作。

ExecutorService 接口

ExecutorService 接口通过类似但更灵活的 submit 方法来补充 execute。与 execute 一样,submit 接受 Runnable 对象,但也接受 Callable 对象,允许任务返回一个值。submit 方法返回一个 Future 对象,用于检索 Callable 返回值并管理 CallableRunnable 任务的状态。

ExecutorService 还提供了提交大量 Callable 对象的方法。最后,ExecutorService 提供了一些方法来管理执行器的关闭。为了支持立即关闭,任务应正确处理中断。

ScheduledExecutorService 接口

ScheduledExecutorService 接口通过 schedule 补充了其父接口 ExecutorService 的方法,该方法在指定延迟后执行 RunnableCallable 任务。此外,该接口定义了 scheduleAtFixedRatescheduleWithFixedDelay,以在定义的间隔时间内重复执行指定任务。

线程池

原文:docs.oracle.com/javase/tutorial/essential/concurrency/pools.html

java.util.concurrent 中的大多数执行器实现使用线程池,其中包含工作线程。这种类型的线程与它执行的RunnableCallable任务分开存在,并经常用于执行多个任务。

使用工作线程可以最小化由于线程创建而产生的开销。线程对象使用大量内存,在大规模应用程序中,分配和释放许多线程对象会产生显著的内存管理开销。

一种常见的线程池类型是固定线程池。这种类型的池始终有指定数量的线程在运行;如果某个线程在仍在使用时被终止,它将自动被新线程替换。任务通过内部队列提交到池中,当活动任务多于线程时,队列会保存额外的任务。

使用固定线程池的一个重要优势是应用程序在使用它时优雅降级。要理解这一点,考虑一个 Web 服务器应用程序,其中每个 HTTP 请求都由一个单独的线程处理。如果应用程序只是为每个新的 HTTP 请求创建一个新线程,并且系统接收到的请求多于它立即处理的能力,当所有这些线程的开销超过系统容量时,应用程序将突然停止响应所有请求。通过限制可以创建的线程数量,应用程序将不会像请求进来那样快速地为 HTTP 请求提供服务,但它将以系统能够维持的速度为它们提供服务。

创建使用固定线程池的执行器的简单方法是在java.util.concurrent.Executors中调用newFixedThreadPool工厂方法。该类还提供以下工厂方法:

  • newCachedThreadPool方法创建一个具有可扩展线程池的执行器。此执行器适用于启动许多短暂任务的应用程序。

  • newSingleThreadExecutor方法创建一个一次执行一个任务的执行器。

  • 几个工厂方法是上述执行器的ScheduledExecutorService版本。

如果上述工厂方法提供的任何执行器都不符合您的需求,构造java.util.concurrent.ThreadPoolExecutorjava.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中运行就很简单,包括以下步骤:

  1. 创建一个代表所有要完成工作的任务。

    // source image pixels are in src
    // destination image pixels are in dst
    ForkBlur fb = new ForkBlur(src, 0, src.length, dst);
    
    
  2. 创建将运行任务的ForkJoinPool

    ForkJoinPool pool = new ForkJoinPool();
    
    
  3. 运行任务。

    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 定义了一个先进先出的数据结构,当尝试向满队列添加或从空队列检索时会阻塞或超时。

  • ConcurrentMapjava.util.Map 的子接口,定义了有用的原子操作。这些操作仅在键存在时移除或替换键值对,或仅在键不存在时添加键值对。使这些操作原子化有助于避免同步。ConcurrentMap 的标准通用实现是 ConcurrentHashMap,它是 HashMap 的并发模拟。

  • ConcurrentNavigableMapConcurrentMap 的子接口,支持近似匹配。ConcurrentNavigableMap 的标准通用实现是 ConcurrentSkipListMap,它是 TreeMap 的并发模拟。

所有这些集合都有助于避免内存一致性错误,通过定义将一个对象添加到集合的操作与随后访问或移除该对象的操作之间的 happens-before 关系。

原子变量

原文:docs.oracle.com/javase/tutorial/essential/concurrency/atomicvars.html

java.util.concurrent.atomic包定义了支持单个变量上原子操作的类。所有类都有类似于对volatile变量进行读取和写入的getset方法。也就是说,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

问题

  1. 你能将Thread对象传递给Executor.execute吗?这样的调用有意义吗?

练习

  1. 编译并运行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的更改在主线程中可见?

  2. 修改 Guarded Blocks 中的生产者-消费者示例,使用标准库类代替Drop类。

检查你的答案。

课程:平台环境

原文:docs.oracle.com/javase/tutorial/essential/environment/index.html

应用程序在平台环境中运行,由底层操作系统、Java 虚拟机、类库和应用程序启动时提供的各种配置数据定义。本课程描述了应用程序用于检查和配置其平台环境的一些 API。本课程包括三个部分:

  • 配置工具描述了用于访问应用程序部署时提供的配置数据或应用程序用户提供的 API。

  • 系统工具描述了在SystemRuntime类中定义的各种 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 对象的可能生命周期

  • 启动中

    第一个三个框中给出的操作发生在应用程序启动时。首先,应用程序将默认属性从一个众所周知的位置加载到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对象中,则返回truePropertiesHashtable继承这些方法。因此,它们接受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.setHastable.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

请注意,应用程序会将每个单词 — DrinkHotJava — 单独显示在一行上。这是因为空格字符分隔命令行参数。要将DrinkHotJava解释为单个参数,用户应该用引号将它们括起来。

*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);
    }
}

如果parseIntargs[0]的格式无效,会抛出NumberFormatException。所有的Number类 — IntegerFloatDouble等等 — 都有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返回nullEnv示例以这种方式使用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 实现可能在USERLOGNAME或两者中提供用户名。

为了最大化可移植性,在系统属性中提供相同值时,永远不要引用环境变量。例如,如果操作系统提供用户名,则始终可以在系统属性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 类有两个用于读取系统属性的方法:getPropertygetProperties

System 类有两个不同版本的 getProperty。两者都检索参数列表中命名的属性的值。其中较简单的 getProperty 方法接受一个参数,即属性键。例如,要获取 path.separator 的值,请使用以下语句:

System.getProperty("path.separator");

getProperty 方法返回一个包含属性值的字符串。如果属性不存在,此版本的 getProperty 返回 null。

getProperty 的另一个版本需要两个 String 参数:第一个参数是要查找的键,第二个参数是在找不到键或键没有值时要返回的默认值。例如,下面的 getProperty 调用查找名为 subliminal.messageSystem 属性。这不是一个有效的系统属性,所以该方法返回提供的第二个参数作为默认值:“购买 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.AccessControlExceptionSecurityException的子类。

System 中的其他方法

原文:docs.oracle.com/javase/tutorial/essential/environment/sysmisc.html

本节描述了一些在前几节中未涵盖的System中的方法。

arrayCopy 方法有效地在数组之间复制数据。有关更多信息,请参考 Arrays 中的 Language Basics 课程。

currentTimeMillisnanoTime 方法在应用程序执行期间测量时间间隔很有用。要测量毫秒级时间间隔,需要在间隔开始和结束时分别调用currentTimeMillis,并将第一个返回值从第二个返回值中减去。类似地,连续调用nanoTime可以测量纳秒级时间间隔。


注意: currentTimeMillisnanoTime 的准确性受操作系统提供的时间服务限制。不要假设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 上使用PATHCLASSPATH环境变量。请查阅随 Java 开发工具包(JDK)软件包安装包含的安装说明以获取最新信息。

安装软件后,JDK 目录将具有如下结构。

JDK 目录结构

bin目录包含编译器和启动器。

更新 PATH 环境变量(Microsoft Windows)

没有设置PATH环境变量也可以正常运行 Java 应用程序。或者,您可以选择性地设置它以方便使用。

如果要方便地从任何目录运行可执行文件(javac.exejava.exejavadoc.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

  1. 选择开始,选择控制面板。双击系统,选择高级选项卡。

  2. 点击环境变量。在系统变量部分,找到PATH环境变量并选择它。点击编辑。如果PATH环境变量不存在,点击新建

  3. 编辑系统变量(或新建系统变量)窗口中,指定PATH环境变量的值。点击确定。通过点击确定关闭所有剩余窗口。

Windows Vista:

  1. 从桌面上右键单击我的电脑图标。

  2. 从上下文菜单中选择属性

  3. 点击高级选项卡(在 Vista 中点击高级系统设置链接)。

  4. 点击环境变量。在系统变量部分,找到PATH环境变量并选择它。点击编辑。如果PATH环境变量不存在,点击新建

  5. 编辑系统变量(或新建系统变量)窗口中,指定PATH环境变量的值。点击确定。通过点击确定关闭所有剩余窗口。

Windows 7:

  1. 从桌面上右键单击计算机图标。

  2. 从上下文菜单中选择属性

  3. 点击高级系统设置链接。

  4. 点击环境变量。在系统变量部分,找到PATH环境变量并选择它。点击编辑。如果PATH环境变量不存在,点击新建

  5. 编辑系统变量(或新建系统变量)窗口中,指定PATH环境变量的值。点击确定。通过点击确定关闭所有剩余窗口。


注意:在从控制面板编辑PATH环境变量时,可能会看到类似以下内容的环境变量:

%JAVA_HOME%\bin;%SystemRoot%\system32;%SystemRoot%;%SystemRoot%\System32\Wbem

用百分号(%)括起来的变量是现有的环境变量。如果这些变量中的一个在控制面板的环境变量窗口中列出(如JAVA_HOME),则可以编辑其值。如果未显示,则是操作系统定义的特殊环境变量。例如,SystemRoot是 Microsoft Windows 系统文件夹的位置。要获取环境变量的值,请在命令提示符下输入以下内容。(此示例获取SystemRoot环境变量的值):

echo %SystemRoot%


更新 PATH 变量(Solaris 和 Linux)

您可以很好地运行 JDK 而不设置PATH变量,或者可以选择设置它作为便利。但是,如果要能够从任何目录运行可执行文件(javacjavajavadoc等),而不必键入命令的完整路径,则应该设置路径变量。如果不设置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

对于kshbashsh

% . /.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包主要由三个类组成:PatternMatcherPatternSyntaxException

  • 一个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,带有编号的单元格和索引值。

字符串中的每个字符都位于自己的单元格中,索引位置指向每个单元格之间。字符串“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)))中,有四个这样的组:

  1. ((A)(B(C)))

  2. (A)

  3. (B(C))

  4. (C)

要找出表达式中存在多少组,请在匹配器对象上调用groupCount方法。groupCount方法返回一个int,显示匹配器模式中存在的捕获组数。在这个例子中,groupCount将返回数字4,显示该模式包含 4 个捕获组。

还有一个特殊组,组 0,它始终表示整个表达式。这个组不包括在groupCount报告的总数中。以(?开头的组是纯粹的、非捕获组,不捕获文本,也不计入组总数。(您将在 Pattern 类的方法部分后面看到非捕获组的示例。)

重要的是要理解组是如何编号的,因为一些Matcher方法接受一个指定特定组号的int作为参数:

反向引用

匹配捕获组的输入字符串部分将被保存在内存中,以便通过反向引用进行后续调用。反向引用在正则表达式中被指定为反斜杠(\)后跟表示要调用的组的数字。例如,表达式(\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

相关文章

  • Java 中文官方教程 2022 版(十一)
    原文:docs.oracle.com/javase/tutorial/reallybigindex.html模式类的方法原文:docs.oracle.com/javase/tutorial/essential/regex/pattern.html到目前为止,我们只使用测试工具来创建Pattern对象的最基本形式。本节探讨了一些高级技术,如使用标志创建模式和使用嵌入式标志表达式......
  • Java 中文官方教程 2022 版(四)
    原文:docs.oracle.com/javase/tutorial/reallybigindex.html方法引用原文:docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html你可以使用lambda表达式来创建匿名方法。然而,有时候lambda表达式仅仅是调用一个已存在的方法。在这种情况下,通过名称引用现有......
  • Java 中文官方教程 2022 版(六)
    原文:docs.oracle.com/javase/tutorial/reallybigindex.html字符和字符串总结原文:docs.oracle.com/javase/tutorial/java/data/stringsummary.html大多数情况下,如果您使用单个字符值,您将使用基本的char类型。然而,有时您需要将char用作对象—例如,作为期望对象的方法参数。J......
  • 【Camunda】SpringBoot优雅集成Camunda 7工作流引擎,保姆级教程!
    From: https://mp.weixin.qq.com/s/lZmIucZYzqqwjBCtDAL9WA前言项目中需要用到工作流引擎来设计部分业务流程,框架选型最终选择了Camunda7,关于Camunda以及Activity等其他工作流引擎的介绍及对比不再介绍,这里只介绍与现有Springboot项目的集成以及具体使用及配置概念流程......
  • 汇编语言简易教程(8):寻址模式
    汇编语言简易教程(8):寻址模式寻址模式是使用正在访问(读取或写入)的数据项的地址来访问内存中的值的受支持方法。这可能包括变量的名称或数组中的位置。基本的寻址模式包含:寄存器立即数内存寻址注意事项使用[]需要注意:访问内存的唯一方法是使用方括号([]'s)。省略括号......
  • 汇编语言简易教程(9):程序栈
    汇编语言简易教程(9):程序栈在计算机中,栈是一种数据结构,其中项目以相反的顺序添加,然后从栈中删除。也就是说,最近添加的项目是第一个被删除的项目。这通常称为后进先出(LIFO).堆栈在编程中大量使用,用于在过程函数调用期间存储信息。下一章提供有关堆栈的信息和示例将项目添加......
  • [蓝桥杯 2022 省 A] 爬树的甲壳虫
    概率dp,关键是要走出思维定势:一般来讲,都会把dp[i]定义为从0爬到高度i的概率,但是因为任何时刻都有掉下去的可能,这样子不好推(也有大佬这样做出来的)我们把dp[i]定义为从i爬到n的概率,公式就好推了而且,我们可以根据定义很自然地得到:dp[n]=0#include<bits/stdc++.h>usingnamespa......
  • 汇编语言简易教程(8):寻址模式
    汇编语言简易教程(8):寻址模式寻址模式是使用正在访问(读取或写入)的数据项的地址来访问内存中的值的受支持方法。这可能包括变量的名称或数组中的位置。基本的寻址模式包含:寄存器立即数内存寻址注意事项使用[]需要注意:访问内存的唯一方法是使用方括号([]'s)。省略括号......
  • 汇编语言简易教程(9):程序栈
    汇编语言简易教程(9):程序栈在计算机中,栈是一种数据结构,其中项目以相反的顺序添加,然后从栈中删除。也就是说,最近添加的项目是第一个被删除的项目。这通常称为后进先出(LIFO).堆栈在编程中大量使用,用于在过程函数调用期间存储信息。下一章提供有关堆栈的信息和示例将项目添加......
  • Java基础学习 | 2024年4月12日
    修饰符1.受保护的访问修饰符-protected子类与基类在同一包中:被声明为protected的变量、方法和构造器能被同一个包中的任何其他类访问;子类与基类不在同一包中:那么在子类中,子类实例可以访问其从基类继承而来的protected方法,而不能访问基类实例的protected方法。简单来讲,被p......