精通正则表达式

第六章 打造高效正则表达式

多选结构的代价很高

Page.231

多选结构或许是回溯的主要原因,字符组一般只是进行简单的测试,所以效率要比对应的多选结构高。

\cx 匹配control + 控制字符,就是说 匹配由x指明的控制字符

这里的X是一个控制字符。匹配一个字符串的控制字符。

比如:
\cI 匹配 control + I,等价于 \t,
\cJ匹配 control + J,等价于 \n,
\cM匹配 control + M,等价于 \r\ca \cb \cc … 又分别匹配 control+a, control+b,control+c….,具体他们等价于什么,看运行的程序了

Java 中的正则表达式

  • 单词分界符 \b \B 能够识别 Unicode 字符

  • 顺序环视中支持任意长度的表达式,逆序环视则不允许(可以出现?, 不能出现 *, +

  • \Q…\E 消除之间所有元字符的特殊含义,当作普通字符来看(不包括\E, 所以如果原始字符串中包含\E, 就不能直接在首位加\Q和\E来直接使用,具体参见 Pattern.quote 方法)

1
2
// 也可以使用此方法,返回 \Q.\E
Pattern.quote(".")
  • anchoring bounds 标志位,如果检索范围不等于整个目标字符串,设定是否将检索范围的边界设置为 “文本起始位置” 和 “文本结束位置”, 影响文本行边界元字符 (\z \Z \A ^ $),Reset 方法不修改此标志位

  • transparent bounds 标志位, 如果检索范围是整个目标字符串的一段文本,设置为true,允许 look 结构(环视,单词分界符)超越检索范围的边界,检查外部的文本,默认为 false, Reset 方法不修改此标志位

  • find 方法,replaceAll, replaceFirst 方法,在内部都会调用 Reset, 即检索范围重置为整个文本, 将匹配的位置指向文本的开头,弃用前一次成功匹配的所有信息

  • 匹配结果中的偏移值 (start end) 与 检索范围没有关系, 是在整个文本中的位置

一个值设置为transient,然后初始化,加锁

如: Files.lines(new File(strPath).toPath, StandardCharsets.UTF_8)

速查表

# cheatsheet

tiny regex cheatsheet

expr usage
/hello/ looks for the string between the forward slashes (case-sensitive)
/hello/i looks for the string between the forward slashes (case-insensitive)
/hello/g looks for multiple occurrences of string between the forward slashes
/h.llo/ the ‘.’ matches any one character other than a new line character… matches ‘hello’, ‘hallo’ but not ‘h llo’
/h.*llo/ the “*” matches any character(s) zero or more times… matches “hello”, “heeeeeello”, “hllo”, “hwarwareallo”
/\d/ matches any digit
/\D/ matches any non-digit
/\w/ matches any word character (a-z, A-Z, 0-9, _)
/\W/ matches any non-word character
/\s/ matches any white space character (\r (carriage return),\n (new line), \t (tab), \f (form feed))
/\S/ matches any non-white space character
/[abcd]/ matches any character in square brackets
/[ch]at/ matches cat or hat
/[^abcd]/ matches anything except the characters in square brackets
/[a-z]/ matches all lowercase letters (a to z)
/[A-Z]/ matches all uppercase letters (A to Z)
/[0-9]/ matches all digits
/[a-zA-Z]/ matches all lowercase and uppercase letters
/[^a-zA-Z]/ matches non-letters
/[a-zA-Z0-9]/ matches all lowercase, uppercase letters and numbers
/(hello){4}/ matches “hellohellohellohello”
/hello{3}/ matches “hellooo” and “helloooo” but not “helloo”
/(hello){1,3}/ matches “hello” that occur between 1 and 3 times (inclusive)
/(hello){3,}/ matches “hello” that occur atleast 3 times
/ab*c/ matches zero or more repetitions of “b” (matches “abc”, “abbbbc”, “ac”)
/ab+c/ matches one or more repetitions of “b” (matches “abc”, “abbbbc”, but not “ac”)
/^/ matches beginning of a line
/$/ matches end of a line
/(hard)?work/ matches “work” or “hardwork”
/(?:hard)?work/ matches “work” or “hardwork” but is a non-capturing group
/i am a (cat|dog|whale) person/ matches “i am a cat person”, “i am a dog person” and “i am a whale person”
/z(?=a)/ positive lookahead… matches the “z” before the “a” in pizza but not the first “z”
/z(?!a)/ negative lookahead… matches the first “z” but not the “z” before the “a”
/(?<=[aeiou])\w/ positive lookbehind… matches any word character that is preceded by a vowel
/(?<![aeiou])\w/ negative lookbehind… matches any word character that is not preceded by a vowel

Chapter 2 Fist Class Function

Chapter 2 First Class Function

First-class functions are functions treated as objects themselves, meaning we can pass a function as a parameter to another function, return a function from a function, or store a function in a variable.

– Becoming Functional

一级函数 高阶函数?

First-Class Function (暂时不知道怎么样翻译较为妥当) 或者 High Order Function 是被视作为对象的函数,也就是说本质上还是一个对象,因此可以作为另一个函数的参数或者在一个函数中返回一个函数,或者将一个函数保存在一个对象中。

函数与对象的区别:函数,顾名思义,就是实现一定功能,是对操作的封装,是有别于存储数据的对象的, 但并不等同于一个类,类中有一个方法。比如定义一个 Student 类,其定义中必定包含相关的属性,如学号、姓名等。 它的对象是存储了数据的,而一个函数仅仅是对输入作相应的操作,并返回一个结果,在 Java 中就可以通过定义一个接口,而这个接口中有一个抽象的方法,作为这个函数相关操作封装的地方。

上述理解我觉得只是在 Java 中的理解,或者面向对象的语言中的理解,运用接口来实现接受和返回 “函数”。在完全的函数式编程语言中的理解留作后续的研究。 纯粹的 First-Class Function 或者 High Order Function 定义就是函数可以作为另一个函数的参数,或者返回值。

在 Java 中, 比如定义了一个 Function 接口:

1
2
3
public interface Function<T, R> {
R apply(T t)
}

那么实现这个接口的任何类或者类的对象都是一个 First-Class Function。 可以作为参数,可以作为一个返回值,可以存储在一个变量中。

1
Function<T, R> function = t -> r;

The DRY (Don’t Repeat Yourself) principle has been around for many years; the concept is that we should not be duplicating lines of code. This makes the code harder to maintain. But why is that the case?
Think about what happens if you duplicate a function multiple times. Now imagine that you just found a bug in one of those functions; you’ll have to go through however many other functions to see if that same bug exists.

不要写重复的代码,如果重复的代码出现在多处,那么后来发现有错误,就要进行多处的检查,不好维护,而且没有本质的意义。

匿名函数

Lambda functions are unnamed functions that contain a parameter list, a body, and a return.

作者定义 Lambda 函数包含三部分: 参数列表、函数体、返回值, 并且是没有名称的函数(unnamed)

Closures are much like lambdas, except they reference variables outside the scope of the function. The body references a variable that doesn’t exist in either the body or the parameter list.

Closure 和 lambda function 很像,但区别在于它们引用了函数范围以外的变量,这个变量既不存在参数列表中,也不存在函数体中。

1
2
3
4
5
6
7
public static List<String> getEnabledCustomerSomeoneEmail(final String someone) {
return Customer.getEnabledCustomerField(new Function1<Customer, String>() {
public String call(Customer customer) {
return someone + "@" + customer.domain;
}
});
}

Using closures, you can build functions and pass them to other functions while referencing local variables.

使用 Closure,就可以将函数作为一个参数传至另一个函数,并且引用局部变量。

1
2
3
4
5
6
7
8
9
10
11
//Example 2-18. getField with test function

public static <B> List<B> getField(Function1<Customer,Boolean> test,
Function1<Customer,B> func) {
ArrayList<B> outList = new ArrayList<B>();
for (Customer customer : Customer.allCustomers) {
if (test.call(customer)) {
outList.add(func.call(customer));
}
}
return outList;

Chapter 8 Pattern Matching

Chapter 8 Patter Matching

函数式编程下的模式识别

主要是为了避免 if/else 结构的维护性差,可读性差。通过匹配对象,以及对对象的成员进行解析(Extract)进行匹配,来观察对象的类型或者执行一定的操作。文中主要用 Scala 进行演示,主要通过 case match 关键字来完成模式匹配。

例如有一个需求,Customer 对象的成员有 name, state, domain 都是 String 类型的,要求有一个构造方法,若其中任何一个字段为空字符串,返回 NULL。

最简单直接的方式就是用 if/else

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
if(name.isEmpty) {
println("Name cannot be blank")
null
} else if(state.isEmpty) {
println("State cannot be blank")
null
} else if(domain.isEmpty) {
println("Domain cannot be blank")
null
} else {
new Customer(
0,
name,
state,
domain,
true,
new Contract(Calendar.getInstance, true),
List()
)
}

在通过 case match 进行重构之前,自认为可以简单的通过卫语句来进行重写,可以让逻辑稍微清晰一点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
if(name.isEmpty) {
println("Name cannot be blank")
return null
}

if(state.isEmpty) {
println("State cannot be blank")
return null
}

if(domain.isEmpty) {
println("Domain cannot be blank")
return null
}

new Customer( 0, name, state, domain, true, new Contract(Calendar.getInstance, true), List())

现在使用 case 和 match 来重构,完成模式匹配。主要做法是将三个属性封装在一个对象或者数据结构里,如 Tuple 元组 (name, state, domain), 然后对这元祖进行匹配。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def createCustomer(name:String, state:String, domain:String)
: Customer = {
(name, state, string) match {
case ("", _, _,) => {
println("Name cannot be blank")
null
}

cae (_, "", _,) => {
println("State cannot be blank")
null
}

case (_, _, "") => {
println("Domain cannot be blank")
null
}

case _ => new Customer( 0, name, state, domain, true, new Contract(Calendar.getInstance, true), List())
}
}

继续来重构之前用尾递归写的一个方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def updateCustomerByIdList(initialIds : List[Customer],
ids : List[Integer],
cls : Customer => Customer) : List[Customer] = {
if(ids.size <= 0) {
initialIds
} else if(initialIds.size <= 0) {
initialIds
} else {
val precust = initialIds.find(cust => cust.customer_id == ids(0))
val cust = if(precust.isEmpty) { List() } else { List(cls(precust.get)) }
cust ::: updateCustomerByIdList(
initialIds.filter(cust => cust.customer_id == ids(0)),
ids.tail,
cls
)
}
}

同样使用元祖、case、match 来进行重构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def updateCustomerByIdList(initialIds : List[Customer],
ids : List[Integer],
cls : Customer => Customer) : List[Customer] = {
(initialIds, ids) match {
case (List(), _) => initialIds
case (_, List()) => initialIds
case _ => {
val precust = initialIds.find(cust => cust.customer_id == ids(0))
val cust = if(precust.isEmpty) {List()} else {
List(cls(precust.get))}
cust ::: updateCustomerByIdList(initialIds.filter(cust => cust.customer_id == ids(0)), ids.drop(1), cls)
}
}
}
}

但是,能否进一步减少这个方法的复杂程度呢?通过 Extracting Lists 来实现。

x::y 操作符出现在 case 语句中,告诉 Scala, 匹配的对象应该是一个 list, 这个 list 的首元素将被赋值给 x, 剩下的元素 (也是一个list) tail 将赋值给 y

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def updateCustomerByIdList(initialIds : List[Customer],
ids : List[Integer],
cls : Customer => Customer) : List[Customer] = {
(initialIds, ids) match {
case (List(), _) => initialIds
case (_, List()) => initialIds
case (_, id::tailIds)=> {
val precust = initialIds.find(cust => cust.customer_id == id)
val cust = if(precust.isEmpty) {List()} else {
List(cls(precust.get))}
cust ::: updateCustomerByIdList(initialIds.filter(cust => cust.customer_id == id), tailIds, cls)
}
}
}
}

上面的代码中,find 方法返回的是一个Option 对象,可以将它转换为 list, 集训进行 Extracting Lists

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def updateCustomerByIdList(initialIds : List[Customer],
ids : List[Integer],
cls : Customer => Customer) : List[Customer] = {
(initialIds, ids) match {
case (List(), _) => initialIds
case (_, List()) => initialIds
case (_, id::tailIds)=> {
val precust = initialIds.find(cust => cust.customer_id == id).toList
precust match {
case List() => updateCustomerByIdList(initialIds, tailIds, cls)
case cust ::: updateCustomerByIdList(initialIds.filter(cust => cust.customer_id == id), tailIds, cls)
}
}
}
}

None 和 Some 是 Option 接口的两个实现类,None 对象不包含任何对象,Some 包含了实际存在的对象。因此可以对 find 返回的 Option 对象直接进行模式匹配,包含两种模式 case None case Some

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def updateCustomerByIdList(initialIds : List[Customer],
ids : List[Integer],
cls : Customer => Customer) : List[Customer] = {
(initialIds, ids) match {
case (List(), _) => initialIds
case (_, List()) => initialIds
case (_, id::tailIds)=> {
val precust = initialIds.find(cust => cust.customer_id == id)
precust match {
case None => updateCustomerByIdList(initialIds, tailIds, cls)
case Some(cust) ::: updateCustomerByIdList(initialIds.filter(cust => cust.customer_id == id), tailIds, cls)
}
}
}
}

可以让之前的 createCustomer 直接返回一个 Option 对象,更加函数化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def createCustomer(name : String,
state : String,
domain : String) : Option[Customer] = {
def error(message : String) : Option[Customer] = {
println(message)
None
}
(name, state, domain) match {
case ("", _, _) => error("Name cannot be blank")
case (_, "", _) => error("State cannot be blank")
case (_, _, "") => error("Domain cannot be blank")
case _ => new Some(new Customer(
0,
name,
state,
domain,
true,
new Contract(Calendar.getInstance, true),
List()
)
)
}
}

用 case 关键字修饰类

1
2
3
4
5
6
7
8
case class Customer(val customer_id : Integer,
val name : String,
val state : String,
val domain : String,
val enabled : Boolean,
val contract : Contract,
val contacts : List[Contact]) {
}
1
2
3
4
5
6
7
8
9
10
11
12
def countEnabledCustomersWithNoEnabledContacts(customers : List[Customer], sum : Integer) : Integer = {
customers match {
case List() => sum

case cust :: custs => {
if (cust.enabled && cust.contacts.exists({ contact => contact.enabled}))
countEnabledCustomersWithNoEnabledContacts(custs, sum + 1)
else
countEnabledCustomersWithNoEnabledContacts(custs, sum)
}
}
}
1
2
3
4
5
6
7
8
9
10
11
def countEnabledCustomersWithNoEnabledContacts(customers : List[Customer], sum : Integer) : Integer = {
customers match {
case List() => sum

case Customer(_,_,_,_,true,_,cont) :: custs
if cont.exists({ contact => contact.enabled}) =>
countEnabledCustomersWithNoEnabledContacts(custs, sum + 1)

case cust :: custs => countEnabledCustomersWithNoEnabledContacts(custs, sum)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
def countEnabledCustomersWithNoEnabledContacts(customers : List[Customer], sum : Integer) : Integer = {
customers match {
case List() => sum

case Customer(_,_,_,_,true,_,List()) :: custs =>
countEnabledCustomersWithNoEnabledContacts(custs, sum)

case Customer(_,_,_,_,true,_,cont) :: custs
if cont.exists({ contact => contact.enabled}) =>
countEnabledCustomersWithNoEnabledContacts(custs, sum + 1)

case cust :: custs => countEnabledCustomersWithNoEnabledContacts(custs, sum)
}
}

Tomcat JDBC

使用 DataSource 对象的优点

  • 便携性,可维护性强

    连接数据库的必要信息,如 url 等不必硬编码在代码中,可以直接通过 properties 文件进行设置和更新

  • DataSource 可以实现为支持数据库连接池、支持分布式事务(Distributed Transactions)

Tomcat JDBC

Config Property Default Value Description
maxIdle 100 如果 isPoolSweeperEnabled() 返回 false,空闲池(idle pool)中应该保持的最大连接数。
minIdle 10 任何时间,连接池中应该保持的已建立连接的最小数目。连接池中连接的数目可能会小于这个数字(由于验证查询失败或者连接被关闭),在清理程序运行时(during an evicition), 不会小于这个数字,介于 getMindle 和 getMaxIdle/getMaxActive 之间i
maxActive 100 连接池中同一时间可以分配的最大活跃连接数
timeBetweenEvictionRunsMillis 5000 (5秒) 空闲连接验证(idle connection validation)、弃用连接清理程序(abandoned cleaner)、idle pool resizing 运行的时间间隔(number of milliseconds to sleep)
initialSize 10 当启动连接池时,要建立的初始连接数,如果超过了 maxActive, 会自动减小这个数字
testWhileIdle false 期望连接处于空闲(idle)时,能够对其通过查询验证有效性的话,将其设置为 true
testOnBorrow false 表示从连接池获取连接之前,是否要对其进行验证。如果验证失败,该连接会被连接池丢弃(dropped from pool),并尝试重新获取连接。如果设置为 true 要生效的话,validationQuery 参数必须非空。为了提升性能, 默认 false。请参考 validationInterval 参数
validationInterval 3000(3秒) 为了避免频繁进行验证,如果连接应进行验证,但之前已在此时间间隔内已进行验证,则不会再次对其进行验证。
validationQuery 连接池用来对连接进行验证的 SQL 查询语句,这个查询不需要返回任何数据,例如: SELECT 1(mysql), select 1 from dual(oracle), SELECT 1(MS Sql Server)
validationQueryTimeout -1 连接验证查询失败前的超时时间 (The timeout in seconds before a connection validation queries fail),-1 表示不启用该特性
removeAbandoned false 表示如果 abandoned connections 超过了 removeAbandonedTimout 的值,是否要移除这些连接。如果设置为true,则如果连接的使用时间超过了getRemoveAbandonedTimeout()并且满足了getAbandonWhenPercentageFull()的条件,则认为该连接已被放弃并可以删除。 将此设置为true可以从无法关闭连接的应用程序恢复数据库连接。 另请参见isLogAbandoned()缺省值为false
removeAbandonedTimeout 60 s 如果一个连接使用超过了该时间,被认为是被弃用 (abandoned) 的连接。可以使用拦截器在查询后重置计时器。
abandonWhenPercentageFull 0 只有使用中的连接超过了该参数定义的百分比,被弃用的连接(abandoned/timed out)才会被关闭和报告。默认为0,表示只要连接超过了 removeAbandonedTimeout 的值,就可以关闭该连接
maxWait 30000(30秒) 当连接池中的连接数已经达到 maxActive,没有可用的连接时,连接池等待的超时时间,超过了该时间,就会抛出异常
minEvictableIdleTimeMillis 60000 (60 秒) 当连接处于空闲的时间超过了该值,才可以移除(eligible for eviction)

isPoolSweeperEnabled 如果连接池启用了 pool sweeper,返回 true

result = getTimeBetweenEvictionRunsMillis()>0;

result = result && (isRemoveAbandoned() && getRemoveAbandonedTimeout()>0);

result = result || (isTestWhileIdle() && getValidationQuery()!=null);

return result;

timeBetweenEvictionRunsMillis 参数涉及的三个方面

1. 空闲连接验证(idle connection validation)

对连接池中的空闲连接进行验证,如果验证失败,则丢弃该连接,参考 testWhileIdle,validationQuery,validationInterval,timeBetweenEvictionRunsMillis

**
**

2. 启用清理程序(abandoned cleaner)

对连接池中 abandoned connection 进行清理,参考 removeAbandoned,removeAbandonedTimeout,abandonWhenPercentageFull

**
**

3. 连接池大小调整 (idle pool resizing )

当连接池中的空闲连接空闲时间超过一定值时,可以被清理,最终保持连接池中的空闲连接至少为 minIdle, 参考 minIdle, minEvictableIdleTimeMillis

凡是有校验的机制,如 testOnBorrow, testOnReturn, testWhileIdle ,都使用 validationQuery 查询语句,受限于 validationInterval 参数,也就是限制对一个连接进行验证的频率,如同时设置了 testOnBorrow, testOnReturn, 在从连接池中取出连接时,会对该连接进行验证,使用并返回时(时间很短),期间的时间间隔如果小于 validationInterval ,是不会再次进行验证的。避免频繁进行验证,影响性能。

maxIdle

如果 isPoolSweeperEnabled() 返回 false,空闲池(idle pool)中应该保持的最大连接数。也就是空闲的连接数不会超过该数目,但是如果 isPoolSweeperEnabled 返回 true, 也就是启用了清理程序,最大的空闲连接数是可以达到 maxActive 的,且可以根据 getMinEvictableIdleTimeMillis() 的设置,逐渐减少,参考 minEvictableIdleTimeMillis 参数设置