TL;DR
转义字符不是天然存在的,它的实现需要各个应用程序自行实现字符串转义解释器来进行解释、替换。
概述
转义字符(Escape Character)
是指在ASCII码和Unicode等字符集中的无法被键盘录入的字符
、被当作特殊用途而需要转换回它原来的意义的字符
。而转义字符
的转义
是指字符已经被转换了意义。
一直到前段时间我都认为转义字符是公共的、统一的、规范的约定,然而当我深入的去探索后发现:转义字符不是天然存在的,它的实现需要各个引用程序自行实现字符串转义解释器来进行解释、替换。
作用
转义字符的作用是在声明的时候,通过特别的前缀把一些不可见的的字符转变成可见的、可读的字符。
比如制表符TAB是不可见字符,如果我们声明的字符串中有制表符,因为制表符和空格在显式层面的结果是一样的,都是空白,如果不使用转义字符来替换,肉眼很难区分出字符串中的空白到底是空格还是其他意义的字符。
因此,业界对于常见的不可见字符,约定了一些转义序列。通过可读可见的转义序列,我们可以区分出不可见字符,避免使用时出错。
实现
转义字符是给人使用的。通过约定的方式定义了转义序列比如\t
、\n
后,我们在使用时可以更加便捷、可读。但是操作系统底层渲染是不认识应用层代码定义的转义字符的。如果不加处理,最终渲染输出的还是\t
、\n
字符,则达不到我们想要的效果。
因此应用层软件需要对用户输入做特别的适配,将读取到的用户的输入中的转义字符解释、替换成真正的字符编码。
以Java中正则表达式的实现为例,我们在编写代码使用正则表达式时,其实是经过了两次解释、替换的过程,这也是Java正则表达式里面会使用很多/
的原因。
public static void main(String[] args) {
// 用于匹配\的正则表达式
Pattern pattern = Pattern.compile("\\\\");
// 和"\"匹配
Matcher matcher = pattern.matcher("\\");
System.out.println(matcher.find());
}
如上述代码示例,该段代码会在编译时,成为Java编译器javac的输入。javac在处理用户输入、解析、编译源码时,本身会处理一次转义字符,主要是处理由""
确定边界的字符串字面量。
javac会匹配、替换由/
开头的转义字符。详见javac源码
经过javac解释替换后,字节码中存在的两个字符串的值分别为:\\
和\
。
转义字符的由支持转义字符的软件自行匹配、替换。替换的结果体现在输出中。javac的输出就是字节码文件。
javac处理完一次转义字符后,将处理后的结果固定在字节码中。后续代码运行时,正则表达式编译器会再次进行一次转义字符的替换。
上述示例中的Pattern.compile("\\\\")
在运行中实际跑的应该是Pattern.compile("\\")
。
同理,正则编译器也会进行转义字符的处理。
java.util.regex.Pattern#escape
处理后,\\
被映射成内存中的、字符集中包含的\
以便进行后续的匹配。
案例
同样的处理过程在很多地方都有体现。
Properties解析
java.util.Properties#loadConvert
如图,Properties文件中的转义字符,也是由Properties文件解析程序自行处理的。
源码中的\t、\n等字符会被javac替换成正式的单字符。这是最容易迷惑的地方,希望读者自行好好体会。
Json解析
com.fasterxml.jackson.core.json.ReaderBasedJsonParser#_decodeEscaped
jackson在解析json的时候,也会自行处理转义字符。
echo程序
echo是linux上常用的输出命令,用于输出指定的字符串。
在使用时,可以通过-E
、-e
参数提示echo命令是否需要解释字符串参数中的转义字符。
如上图,echo程序默认会解释、替换参数中的转义字符,但是可以通过参数屏蔽转义字符。屏蔽转义字符的核心其实就是不遍历、替换参数中的转义序列。
这个示例就是想说明:转义字符的解释、替换是应用程序级别的代码逻辑,可以由引用程序自行控制是否解释转义字符、解释哪些转义字符。
总结
- 如果一个应用程序的输入是字符串,那么从易用性的角度考虑,它应该要提供转义字符解释、替换的功能。
- 但是我们需要理解:转义字符解释、替换的能力不是操作系统提供的,是应用程序自行解析、替换的。
- 每个应用程序支持的转义字符数量、格式都可能不一样。但是出于惯例,大家一般都是用
\
前缀来标识转义字符,一般都会支持基础的\n
、\t
等。这是惯性,是程序员心智;而不是因为\
在客观上有什么特别的优势。 - Java领域常见的以字符串为输入的应用程序,在处理输入时,都自行解释、处理了转义字符。
- 最重要的一点:javac(java源码编译器)也是处理字符串输入的应用程序,因此源码中也可以使用转义字符。
当我们用这种心智来理解Java正则表达式中的那么多\
时,整体感受就不会那么别扭了。 - 为什么javascript的正则表达式写起来不需要那么多
\
呢?
这是因为首先javascript没有编译的过程,然后是因为javascript中,正则是一等公民,有特殊的语法,和普通的字符串字面量没有共用声明语法。