Chúng tôi xin giới thiệu với bạn bản dịch hướng dẫn ngắn gọn về các biểu thức chính quy trong Java, do Jeff Friesen viết cho trang web
javaworld . Để dễ đọc, chúng tôi chia bài viết thành nhiều phần.
Biểu thức chính quy trong Java, Phần 1
Hợp nhất nhiều phạm vi |
Bạn có thể hợp nhất nhiều phạm vi thành một lớp ký tự phạm vi duy nhất bằng cách đặt chúng cạnh nhau. Ví dụ: lớp [a-zA-Z] khớp với tất cả các ký tự chữ cái Latinh ở dạng chữ thường hoặc chữ hoa. |
Hợp nhất nhiều phạm vi
Bạn có thể hợp nhất nhiều phạm vi thành một lớp ký tự phạm vi duy nhất bằng cách đặt chúng cạnh nhau. Ví dụ: lớp
[a-zA-Z]
khớp với tất cả các ký tự chữ cái Latinh ở dạng chữ thường hoặc chữ hoa.
Kết hợp các lớp nhân vật
Một liên kết lớp ký tự bao gồm một số lớp ký tự lồng nhau và khớp với tất cả các ký tự trong liên kết kết quả. Ví dụ: lớp
[a-d[m-p]]
khớp các ký tự từ
a
đến
d
và từ
m
tới
p
. Hãy xem xét ví dụ sau:
java RegexDemo [ab[c-e]] abcdef
Ví dụ này sẽ tìm các ký tự
a
,
b
, và , có các ký tự trùng khớp trong
c
:
d
e
abcdef
regex = [ab[c-e]]
input = abcdef
Found [a] starting at 0 and ending at 0
Found [b] starting at 1 and ending at 1
Found [c] starting at 2 and ending at 2
Found [d] starting at 3 and ending at 3
Found [e] starting at 4 and ending at 4
Giao điểm lớp nhân vật
Giao của các lớp ký tự bao gồm các ký tự chung cho tất cả các lớp lồng nhau và chỉ khớp với các ký tự chung. Ví dụ: lớp
[a-z&&[d-f]]
khớp với các ký tự
d
,
e
và
f
. Hãy xem xét ví dụ sau:
java RegexDemo "[aeiouy&&[y]]" party
Lưu ý rằng trên hệ điều hành Windows của tôi, cần có dấu ngoặc kép vì shell lệnh coi chúng
&
như dấu phân cách lệnh. Ví dụ này sẽ chỉ tìm thấy ký tự
y
khớp với
party
:
regex = [aeiouy&&[y]]
input = party
Found [y] starting at 4 and ending at 4
Trừ các lớp ký tự
Các lớp ký tự trừ bao gồm tất cả các ký tự ngoại trừ các ký tự có trong các lớp ký tự lồng nhau và chỉ khớp với các ký tự còn lại. Ví dụ: lớp
[a-z&&[^m-p]]
khớp các ký tự từ đến
a
và
l
từ
q
đến
z
:
java RegexDemo "[a-f&&[^a-c]&&[^e]]" abcdefg
Ví dụ này sẽ tìm các ký tự
d
và
f
có các ký tự khớp trong
abcdefg
:
regex = [a-f&&[^a-c]&&[^e]]
input = abcdefg
Found [d] starting at 3 and ending at 3
Found [f] starting at 5 and ending at 5
Các lớp ký tự được xác định trước
Một số lớp ký tự xuất hiện đủ thường xuyên trong
các biểu thức chính quy để biện minh cho việc sử dụng ký hiệu tốc ký. Lớp này
Pattern
cung cấp các lớp ký tự được xác định trước dưới dạng viết tắt như vậy. Bạn có thể sử dụng chúng để đơn giản hóa biểu thức chính quy và giảm thiểu lỗi cú pháp. Có một số loại lớp ký tự được xác định trước: thuộc tính tiêu chuẩn, POSIX
java.lang.Character
và Unicode như tập lệnh, khối, danh mục và nhị phân. Danh sách sau đây chỉ hiển thị danh mục các lớp tiêu chuẩn:
\d
: Con số. Tương đương [0-9]
.
\D
: Ký tự không phải số. Tương đương [^0-9]
.
\s
: Ký tự khoảng trắng. Tương đương [ \t\n\x0B\f\r]
.
\S
: Không phải là ký tự khoảng trắng. Tương đương [^\s]
.
\w
: Ký hiệu tạo thành từ. Tương đương [a-zA-Z_0-9]
.
\W
: Không phải là ký tự tạo thành từ. Tương đương [^\w]
.
Ví dụ sau sử dụng lớp ký tự được xác định trước
\w
để mô tả tất cả các ký tự từ trong văn bản đầu vào:
java RegexDemo \w "aZ.8 _"
Hãy xem xét kỹ các kết quả thực thi sau đây, cho thấy các ký tự dấu chấm và dấu cách không được coi là ký tự từ:
regex = \w
input = aZ.8 _
Found [a] starting at 0 and ending at 0
Found [Z] starting at 1 and ending at 1
Found [8] starting at 3 and ending at 3
Found [_] starting at 5 and ending at 5
Dấu phân cách dòng |
Tài liệu SDK lớp Pattern mô tả siêu ký tự dấu chấm là một lớp ký tự được xác định trước khớp với bất kỳ ký tự nào ngoại trừ dấu phân cách dòng (chuỗi một hoặc hai ký tự đánh dấu sự kết thúc của một dòng). Ngoại lệ là chế độ dotall (chúng ta sẽ thảo luận tiếp theo), trong đó các dấu chấm cũng khớp với dấu phân cách dòng. Lớp Pattern phân biệt các dấu phân cách dòng sau:
- ký tự trả về vận chuyển (
\r );
- ký tự dòng mới (ký hiệu để nâng cấp giấy lên một dòng) (
\n );
- một ký tự trả về đầu dòng ngay sau đó là một ký tự dòng mới (
\r\n );
- ký tự dòng tiếp theo (
\u0085 );
- ký tự phân cách dòng (
\u2028 );
- ký hiệu phân cách đoạn văn (
\u2029 )
|
Nhóm bị bắt
Nhóm chụp được sử dụng để lưu bộ ký tự tìm thấy để sử dụng tiếp khi tìm kiếm theo mẫu. Cấu trúc này là một chuỗi các ký tự được bao bọc trong các siêu ký tự bằng dấu ngoặc đơn (
( )
). Tất cả các ký tự trong nhóm đã chụp được coi là một tổng thể duy nhất khi tìm kiếm theo mẫu. Ví dụ: nhóm chụp (
Java
) kết hợp các chữ cái
J
,
a
và
v
thành
a
một đơn vị. Nhóm chụp này tìm thấy tất cả các lần xuất hiện của mẫu
Java
trong văn bản đầu vào. Với mỗi trận đấu, các ký tự được lưu trữ trước đó
Java
sẽ được thay thế bằng các ký tự tiếp theo. Các nhóm đã chụp có thể được lồng trong các nhóm đã chụp khác. Ví dụ: trong biểu thức chính quy,
(Java( language))
một nhóm
(language)
được lồng bên trong một nhóm
(Java)
. Mỗi nhóm chụp lồng nhau hoặc không lồng nhau được gán một số, bắt đầu từ 1 và đánh số từ trái sang phải. Trong ví dụ trước,
(Java( language))
khớp với nhóm thu thập số 1 và
(language)
khớp với nhóm thu thập số 2. Trong biểu thức chính quy
(a)(b)
,
(a)
khớp với nhóm thu thập số 1 và
(b)
nhóm thu thập số 2.
Sau này, có thể truy cập các kết quả trùng khớp được lưu trữ bởi các nhóm thu thập bằng cách sử dụng tham chiếu ngược. Được chỉ định là ký tự dấu gạch chéo ngược, theo sau là ký tự số tương ứng với số lượng của nhóm được thu thập, phản hồi ngược cho phép bạn tham chiếu đến các ký tự trong văn bản được nhóm thu thập. Việc có liên kết ngược khiến trình so khớp tham chiếu đến kết quả tìm kiếm được lưu trữ của nhóm đã thu thập dựa trên số từ kết quả đó, sau đó sử dụng các ký tự từ kết quả đó để thử tìm kiếm thêm. Ví dụ sau đây cho thấy việc sử dụng phản hồi ngược để tìm lỗi ngữ pháp trong văn bản:
java RegexDemo "(Java( language)\2)" "The Java language language"
Ví dụ này
(Java( language)\2)
sử dụng biểu thức chính quy để tìm lỗi ngữ pháp có từ trùng lặp
language
ngay sau
Java
trong văn bản đầu vào
"The Java language language"
. Biểu thức chính quy này chỉ định hai nhóm cần nắm bắt: số 1 –
(Java( language)\2)
, tương ứng với
Java language language
và số 2 –
(language)
, tương ứng với ký tự khoảng trắng theo sau là
language
. Tham chiếu ngược
\2
cho phép xem lại kết quả được lưu trữ của nhóm số 2 để trình so khớp có thể tìm kiếm lần xuất hiện thứ hai của khoảng trắng theo sau là
language
, ngay sau lần xuất hiện đầu tiên của khoảng trắng và
language
. Kết quả của matcher
RegexDemo
như sau:
regex = (Java( language)\2)
input = The Java language language
Found [Java language language] starting at 4 and ending at 25
Công cụ so khớp ranh giới
Đôi khi bạn cần thực hiện khớp mẫu ở đầu dòng, ở ranh giới từ, ở cuối văn bản, v.v. Bạn có thể thực hiện việc này bằng cách sử dụng một trong các trình so khớp cạnh lớp
Pattern
, là các cấu trúc biểu thức chính quy tìm kiếm các kết quả khớp ở các vị trí sau:
^
: Bắt đầu dòng;
$
: Kết thúc dòng;
\b
: Ranh giới từ;
\B
: Ranh giới giả;
\A
: Bắt đầu văn bản;
\G
: Kết thúc trận đấu trước;
\Z
: Kết thúc văn bản, không bao gồm dấu phân cách dòng cuối (nếu có);
\z
: Kết thúc văn bản
Ví dụ sau đây sử dụng
^
siêu ký tự so khớp ranh giới để tìm các dòng bắt đầu bằng
The
, theo sau là 0 hoặc nhiều ký tự từ:
java RegexDemo "^The\w*" Therefore
Ký tự này
^
chỉ định rằng ba ký tự đầu tiên của văn bản đầu vào phải khớp với các ký tự mẫu liên tiếp
T
và
h
,
e
có thể được theo sau bởi bất kỳ số nào của các ký hiệu tạo thành từ. Đây là kết quả của việc thực hiện:
regex = ^The\w*
input = Therefore
Found [Therefore] starting at 0 and ending at 8
Điều gì xảy ra nếu bạn thay đổi dòng lệnh thành
java RegexDemo "^The\w*" " Therefore"
? Sẽ không tìm thấy kết quả trùng khớp nào vì
Therefore
văn bản đầu vào được đặt trước ký tự khoảng trắng.
Trận đấu có độ dài bằng 0
Đôi khi, khi làm việc với các công cụ so khớp cạnh, bạn sẽ gặp phải các kết quả khớp có độ dài bằng 0.
Совпадение нулевой длины
là một kết quả khớp không chứa bất kỳ ký tự nào. Chúng có thể xuất hiện trong văn bản đầu vào trống, ở đầu văn bản đầu vào, sau ký tự cuối cùng của văn bản đầu vào và giữa hai ký tự bất kỳ của văn bản đầu vào. Các trận đấu có độ dài bằng 0 rất dễ nhận biết vì chúng luôn bắt đầu và kết thúc ở cùng một vị trí. Hãy xem xét ví dụ sau:
java RegExDemo \b\b "Java is"
Ví dụ này tìm kiếm hai ranh giới từ liên tiếp và kết quả trông như thế này:
regex = \b\b
input = Java is
Found [] starting at 0 and ending at -1
Found [] starting at 4 and ending at 3
Found [] starting at 5 and ending at 4
Found [] starting at 7 and ending at 6
Chúng tôi thấy một số kết quả trùng khớp có độ dài bằng 0 trong kết quả. Các vị trí kết thúc ở đây ít hơn một vị trí so với các vị trí bắt đầu, vì
RegexDemo
tôi đã chỉ định trong mã nguồn trong Liệt kê 1
end() – 1
.
định lượng
Bộ định lượng là một cấu trúc biểu thức chính quy liên kết rõ ràng hoặc ngầm định một mẫu với một giá trị số. Giá trị số này xác định số lần tìm kiếm mẫu. Các định lượng được chia thành tham lam, lười biếng và siêu tham lam:
- Bộ định lượng tham lam (
?
, *
hoặc +
) được thiết kế để tìm kết quả khớp dài nhất. Tôi có thể hỏi không X
? để tìm một hoặc ít lần xuất hiện X
, X*
tìm không hoặc nhiều lần xuất hiện X
, X+
tìm một hoặc nhiều lần xuất hiện X
, X{n}
tìm n
lần xuất hiện X
, X{n,}
tìm ít nhất (và có thể nhiều hơn) n
lần xuất hiện , X
và X{n,m}
tìm ít nhất nhưng không nhiều lần xuất hiện n
hơn .m
X
- Bộ định lượng lười biếng (
??
, *?
hoặc +?
) được thiết kế để tìm kết quả khớp ngắn nhất. Bạn có thể chỉ định X??
tìm kiếm một hoặc ít lần xuất hiện của X
, X*
? để tìm không hoặc nhiều lần xuất hiện X
, X+?
tìm một hoặc nhiều lần xuất hiện X
, X{n}?
tìm n
lần xuất hiện X
, X{n,}?
tìm ít nhất (và có thể nhiều hơn) n
lần xuất hiện X
, và X{n,m}?
tìm ít nhất n
nhưng không nhiều hơn m
số lần xuất hiện X
.
- Bộ định lượng siêu tham lam (
?+
, *+
hoặc ++
) tương tự như bộ định lượng tham lam, ngoại trừ bộ định lượng siêu tham lam chỉ thực hiện một nỗ lực để tìm ra kết quả khớp dài nhất, trong khi bộ định lượng tham lam có thể thực hiện nhiều lần thử. Có thể được đặt X?+
để tìm một hoặc ít lần xuất hiện X
, X*+
tìm không hoặc nhiều lần xuất hiện X
, X++
tìm một hoặc nhiều lần xuất hiện X
, X{n}+
tìm n
lần xuất hiện của X
, X{n,}+
tìm ít nhất (và có thể nhiều hơn) n
lần xuất hiện X
và X{n,m}+
tìm ít nhất n
nhưng không nhiều hơn m
số lần xuất hiện X
.
Ví dụ sau minh họa việc sử dụng bộ lượng hóa tham lam:
java RegexDemo .*ox "fox box pox"
Đây là kết quả:
regex = .*ox
input = fox box pox
Found [fox box pox] starting at 0 and ending at 10
Bộ định lượng tham lam (
.*
) tìm chuỗi ký tự dài nhất kết thúc bằng
ox
. Nó tiêu thụ toàn bộ văn bản đầu vào và sau đó cuộn lại cho đến khi phát hiện văn bản đầu vào kết thúc bằng các ký tự này. Bây giờ hãy xem xét bộ định lượng lười biếng:
java RegexDemo .*?ox "fox box pox"
Kết quả của nó:
regex = .*?ox
input = fox box pox
Found [fox] starting at 0 and ending at 2
Found [ box] starting at 3 and ending at 6
Found [ pox] starting at 7 and ending at 10
Bộ định lượng lười biếng (
.*?
) tìm chuỗi ký tự ngắn nhất kết thúc bằng
ox
. Nó bắt đầu bằng một chuỗi trống và dần dần tiêu thụ các ký tự cho đến khi tìm thấy kết quả khớp. Và sau đó tiếp tục làm việc cho đến khi hết văn bản đầu vào. Cuối cùng, chúng ta hãy nhìn vào bộ định lượng siêu tham lam:
java RegexDemo .*+ox "fox box pox"
Và đây là kết quả của nó:
regex = .*+ox
input = fox box pox
Bộ định lượng cực kỳ tham lam (
.*+
) không tìm thấy kết quả khớp vì nó tiêu thụ tất cả văn bản đầu vào và không còn gì để khớp
ox
ở cuối biểu thức chính quy. Không giống như bộ định lượng tham lam, bộ định lượng siêu tham lam không quay trở lại.
Trận đấu có độ dài bằng 0
Đôi khi khi làm việc với bộ định lượng, bạn sẽ gặp phải các kết quả khớp có độ dài bằng 0. Ví dụ: việc sử dụng bộ định lượng tham lam sau đây sẽ dẫn đến nhiều kết quả khớp có độ dài bằng 0:
java RegexDemo a? abaa
Kết quả khi chạy ví dụ này:
regex = a?
input = abaa
Found [a] starting at 0 and ending at 0
Found [] starting at 1 and ending at 0
Found [a] starting at 2 and ending at 2
Found [a] starting at 3 and ending at 3
Found [] starting at 4 and ending at 3
Có năm kết quả phù hợp trong kết quả thực hiện. Mặc dù chữ thứ nhất, thứ ba và thứ tư khá được mong đợi (chúng tương ứng với vị trí của ba chữ cái
a
trong
abaa
), nhưng chữ thứ hai và thứ năm có thể làm bạn ngạc nhiên. Có vẻ như họ chỉ ra những gì
a
tương ứng
b
với phần cuối của văn bản, nhưng thực tế không phải vậy. Biểu thức chính quy
a?
không tìm kiếm
b
ở cuối văn bản. Nó tìm kiếm sự hiện diện hay vắng mặt
a
. Khi
a?
nó không tìm thấy
a
, nó báo cáo nó là kết quả khớp có độ dài bằng 0.
Biểu thức cờ lồng nhau
Trình so khớp đưa ra một số giả định mặc định có thể bị ghi đè khi biên dịch biểu thức chính quy thành một mẫu. Chúng ta sẽ thảo luận vấn đề này sau. Biểu thức chính quy cho phép bạn ghi đè bất kỳ giá trị mặc định nào bằng cách sử dụng biểu thức cờ lồng nhau. Cấu trúc biểu thức chính quy này được chỉ định dưới dạng siêu ký tự trong dấu ngoặc đơn xung quanh siêu ký tự dấu chấm hỏi (
?
), theo sau là một chữ cái Latinh viết thường. Lớp này
Pattern
hiểu các biểu thức cờ lồng nhau sau đây:
(?i)
: Cho phép khớp mẫu không phân biệt chữ hoa chữ thường. Ví dụ: khi sử dụng lệnh, java RegexDemo (?i)tree Treehouse
chuỗi ký Tree
tự khớp với mẫu tree
. Mặc định là tìm kiếm mẫu phân biệt chữ hoa chữ thường.
(?x)
: Cho phép sử dụng các ký tự khoảng trắng và nhận xét bắt đầu bằng siêu ký tự trong mẫu #
. Người so khớp sẽ bỏ qua cả hai. Ví dụ: đối với java RegexDemo ".at(?x)#match hat, cat, and so on" matter
một chuỗi ký tự mat
khớp với mẫu .at
. Theo mặc định, các ký tự khoảng trắng và nhận xét không được phép và trình so khớp coi chúng là các ký tự liên quan đến tìm kiếm.
(?s)
: Bật chế độ dotall, trong đó siêu ký tự dấu chấm khớp với dấu phân cách dòng ngoài bất kỳ ký tự nào khác. Ví dụ: lệnh java RegexDemo (?s). \n
sẽ tìm ký tự dòng mới. Giá trị mặc định ngược lại với dotall: không tìm thấy dấu phân cách dòng. Ví dụ: lệnh Java RegexDemo . \n
sẽ không tìm thấy ký tự dòng mới.
(?m)
: Kích hoạt chế độ nhiều dòng, trong đó nó ^
khớp với phần đầu và $
phần cuối của mỗi dòng. Ví dụ: java RegexDemo "(?m)^abc$" abc\nabc
tìm cả hai chuỗi trong văn bản đầu vào abc
. Theo mặc định, chế độ một dòng được sử dụng: ^
khớp với phần đầu của toàn bộ văn bản đầu vào và $
khớp với phần cuối của văn bản đó. Ví dụ: java RegexDemo "^abc$" abc\nabc
trả về phản hồi không có kết quả trùng khớp.
(?u)
: Cho phép căn chỉnh chữ hoa chữ thường theo Unicode. Cờ này, khi được sử dụng cùng với (?i)
, cho phép khớp mẫu không phân biệt chữ hoa chữ thường theo tiêu chuẩn Unicode. Cài đặt mặc định là chỉ tìm kiếm các ký tự phân biệt chữ hoa chữ thường và US-ASCII.
(?d)
: Bật chế độ chuỗi kiểu Unix, trong đó trình so khớp nhận dạng siêu ký tự trong ngữ cảnh .
và chỉ dấu phân cách dòng . Mặc định là chế độ chuỗi kiểu không phải Unix: trình so khớp nhận ra, trong ngữ cảnh của các siêu ký tự ở trên, tất cả các dấu phân cách dòng.^
$
\n
Các biểu thức cờ lồng nhau giống với các nhóm đã bắt vì các ký tự của chúng được bao quanh bởi các siêu ký tự dấu ngoặc đơn. Không giống như các nhóm đã nắm bắt, các biểu thức cờ lồng nhau là một ví dụ về các nhóm không được nắm bắt, là một cấu trúc biểu thức chính quy không nắm bắt các ký tự văn bản. Chúng được định nghĩa là chuỗi các ký tự được bao quanh bởi các siêu ký tự trong dấu ngoặc đơn.
Chỉ định nhiều biểu thức cờ lồng nhau |
Có thể chỉ định nhiều biểu thức cờ lồng nhau trong một biểu thức chính quy bằng cách đặt chúng cạnh nhau ( (?m)(?i)) ) hoặc đặt các chữ cái xác định chúng một cách tuần tự ( (?mi) ). |
Phần kết luận
Như bạn có thể đã nhận ra cho đến bây giờ, biểu thức chính quy cực kỳ hữu ích và thậm chí còn trở nên hữu ích hơn khi bạn nắm vững các sắc thái cú pháp của chúng. Cho đến nay tôi đã giới thiệu cho bạn những kiến thức cơ bản về biểu thức chính quy và
Pattern
. Trong Phần 2, chúng ta sẽ tìm hiểu sâu hơn về API Regex và khám phá các phương thức của
Pattern
,
Matcher
và
PatternSyntaxException
. Tôi cũng sẽ chỉ cho bạn hai ứng dụng thực tế của API Regex mà bạn có thể sử dụng ngay trong các chương trình của mình.
Biểu thức chính quy trong Java, Phần 3 Biểu thức chính quy trong Java, Phần 4 Biểu thức chính quy trong Java, Phần 5
GO TO FULL VERSION