最近在学习《两周自制脚本语言》这本书,在词法分析的一些复杂的正则中用到了大量的转义字符'\',比如正则字符串中包含了这个部分\\\\\"
你知道它是匹配什么的么?
反斜杠在字符串和正则表达式中都有特殊作用。今天让我们来深入理解一下Java中的转义字符\
。
先提几个问题:
- Java中的字符串中的
\n
是一个字符还是两个? - Java中代码中的字符串字面量"abc",在存储的时候,字符串内容中有没有双引号?
- Pattern pat = Pattern.compile("\\\\\\\\"); (双引号内有8个反斜杠),请问它可以匹配字符串中的什么内容?非得用8个反斜杠才能表达要匹配的字符串么?6个或4个可不可以?
字符串字面量
字面量两边的双引号是干嘛的
在Java代码中写出来的字符串,叫做字符串字面量,比如String name = "Jack"
中的字符串Jack
就是字面量形式给出来的,它在编译后的程序中会保存在字符串常量池中。保存的内容仅仅是Jack这个字符串,共4个字符,是没有两边的双引号的。两边的双引号,仅仅是代码中写的,给Java编译器看的,编译器看到代码中出现了双引号,就知道接下来的内容是字符串,所以真正的字符串内容就是Jack这四个字符。想想name.length()不就是4么。
字符串内容中有双引号怎么办
假设我有个字符串内容是"You hurt me", she said.
。代码中如果将这个字符串不做处理地用双引号包裹起来就出了问题:
java
String str = ""You hurt me", she said.";
编译器的眼里,只有两个字符串,第一个是空字符串,第二个是, she said.
。因为编译器是通过双引号来判断字符串字面量的起止位置的。
如果你想要在字符串中包含双引号,代码要这么写:
java
String str = "\"You hurt me\", she said.";
即在字符串内容中的双引号前加上反斜杠作为转义字符,这样编译器读取到\"
的时候,就不会认为它是字符串的结束了。
字符串内容中有反斜杠怎么办
假设我们字符串的内容中也有反斜杠,比如The backslash \ is an escape character
,我们也需要在反斜杠前加一个反斜杠作为转义字符:
java
String str = "The backslash \\ is an escape character";
多个连续的反斜杠该怎么解读
那如果字符串的内容包含了\"
该怎么写的?比如字符串的内容是The \" inner string literals means a double quote
,那就要写成如下的方式:
java
String str = "The \\\" inner string literals means a double quote";
在字符串字面量中,如果有多个反斜杠连在一起,则奇数位置(1,3,5,7...)上的反斜杠表示转义,和它后边的字符共同决定含义。那么字符串字面量中的\\\"
中的第1个反斜杠表示对它后边的反斜杠的转义,第2个反斜杠就不再是转义字符了,它被它前面的转义字符给剥夺了转义的超能力。前两个反斜杠连在一起表示一个反斜杠字符,第3个反斜杠和它后面的双引号一起表示字符串内容中的双引号。
字符串中多个反斜杠连续起来,只有奇数位置1,3,5,7这些位置上的反斜杠具有转义的超能力,其它位置上的都被它前面的转义字符给剥夺了转义的超能力,仅仅表示反斜杠字符本身了。所以字符串中的\\\\\\\\
(8个反斜杠)表示的其实是4个反斜杠字符。这4个反斜杠字符不再具有转义的能力,不会继续转义下去。
但是,如果这个8个反斜杠的字符串作为正则表达式的话,它的内容是4个反斜杠,这其中奇数位置的反斜杠又有了转义的能力,不过这个转义能力是正则表达式中的转义。 所以8个反斜杠的字符串作为Pattern.compile参数的话,它先是被解读为字符串,然后这个字符串又被当作正则表达式的pattern使用。4个反斜杠在正则表达式中表示的是两个连续的反斜杠。本文后边会讲解正则表达式中的转义。
字符串内容中包含换行怎么办
如果字符串中包含了换行符,那么就需要在字符串中用\n
来表示换行,换行符实际上是一个字符,因为换行符是不可打印不可显示的字符,所以你没办法在代码中直接表示它,各种编程语言都规定用\n
来表示换行,也就是说在程序的代码中,要用反斜杠和字母n的组合来表示换行,但是实际上它们的组合表示的是一个换行符。
java
/**
* 一个字符,才可以用char类型
*/
char c = '\n';
String lineSeparator = "\n";
System.out.println(lineSeparator.length()); // 输出1
正则表达式中的反斜杠
在正则表达式中也有反斜杠\
,它也有转义的能力。比如正则表达式中的元字符|
表示或的关系,如果在它前面加上了反斜杠,就仅仅表示竖线了:
也就是说在正则表达式中,反斜杠字符也是有转义的超能力的。
当Java的字符串遇上正则表达式
注意:反斜杠在Java的字符串和正则表达式中都具有转义的作用,如果它们遇到一起就需要分两步骤来解读反斜杠: 第一步将它作为字符串的含义解读出来,第二步将前一步解读出来的字符串作为正则表达式的含义解读出来。
比如我想匹配字符串中的a|b
,用正则表达式写的pattern就是a\|b
,可是到了java中,就得写成下面的:
java
// 要多加一个转义,看起来貌似正则表达式本身不太一样似的
Pattern pat = Pattern.compile("a\\|b");
而如果正则表达式中要匹配的是反斜杠本身,就更麻烦了,在正则表达式中要用两个反斜杠才能表达反斜杠本身。而要用java的字符串来写正则表达式的pattern,反斜杠的数量还要翻倍,比如:
java
Pattern pat = Pattern.compile("\\\\"); // 用于匹配字符串中单个反斜杠
现将代码中的四个反斜杠的字符串字面量解读成有两个反斜杠字符的字符串内容本身,然后将有两个反斜杠的字符串内容作为正则表达式的pattern,那么本来已经归于平凡的反斜杠在正则表达式中又一次具有了转义的能力!
所以说如果Java中的正则表达式要想匹配字符串中的\"
,要写成:
java
//前面4个反斜杠表示一个不具备转义能力的反斜杠字符,
//第5个反斜杠和后边的双引号表示字符串内容中的双引号
Pattern pat = Pattern.compile("\\\\\"");
为什么Java中的正则会有转义字符满天飞,不好读懂
造成这个现象的原因,就是Java中不支持raw string这种字符串,比如有的编程语言通过三个双引号或者三个单引号来表示raw string,这样在raw string中有双引号之类的就不用再转义一下了。比如Rust中的raw string:
如果用Rust的正则来匹配字符串中的反斜杠本身,则简单的多:
rust
//用于匹配字符串中的反斜杠,注意这儿之所以还需要写两个反斜杠,是正则表达式本身就需要两个
//因为在正则表达式中,反斜杠也具有转义的功能,如果用Java写,则要写4个,多一倍的反斜杠是Java字符串造成的
let regex = Regex::new(r"\\").unwrap();
英文词汇:
- 转义字符: escape character
- 反斜杠(
\
): backslash - 字符串字面量: string literals