CodeKitHub
Tiếng Việt
Regex khớp tham lam (greedy) và lười (lazy): vì sao mẫu của bạn khớp quá nhiều

Regex khớp tham lam (greedy) và lười (lazy): vì sao mẫu của bạn khớp quá nhiều

Đăng ngày 24 thg 7, 2026

Bạn viết <b>.*</b> để lấy nội dung bên trong một thẻ in đậm, thử nghiệm trên <b>Hello</b>, nó chạy hoàn hảo. Sau đó bạn chạy nó trên HTML thực với hai thẻ in đậm trên cùng một dòng — <b>Hello</b> and <b>World</b> — và thay vì nhận được hai kết quả khớp, bạn nhận được một kết quả khớp khổng lồ trải dài qua cả hai thẻ. Đây không phải lỗi của bộ máy regex; đó là hành vi mặc định của *, +, và {n,m}, và nó có cách sửa chỉ bằng một ký tự một khi bạn hiểu rõ chuyện gì đang xảy ra.

Vì sao .* khớp nhiều hơn bạn mong đợi

Theo mặc định, các bộ định lượng trong regex là tham lam (greedy).* không có nghĩa là “khớp một số ký tự nào đó,” mà có nghĩa là “khớp càng nhiều ký tự càng tốt, rồi chỉ nhả bớt ra khi phần còn lại của mẫu thực sự bắt buộc phải như vậy.”

Hãy đi từng bước qua <b>.*</b> với chuỗi <b>Hello</b> and <b>World</b>:

  1. <b> khớp với thẻ <b> đầu tiên.
  2. .* bắt đầu bằng cách nuốt trọn toàn bộ phần còn lại của chuỗi — mọi thứ cho đến hết, bao gồm cả <b>World</b> thứ hai.
  3. Sau đó bộ máy cần tìm </b> để hoàn tất kết quả khớp, nên nó bắt đầu nhả bớt từ cuối .* ra, từng ký tự một.
  4. Vị trí đầu tiên (quét ngược từ cuối) mà </b> khớp vừa vặn chính là </b> cuối cùng trong chuỗi — chứ không phải vị trí “hợp lý” mà nó lẽ ra nên dừng lại.

Vì vậy kết quả khớp cuối cùng là toàn bộ <b>Hello</b> and <b>World</b>, bởi vì khớp tham lam luôn thử chuỗi dài nhất có thể trước tiên và chỉ thu nhỏ lại vừa đủ mức cần thiết để phần còn lại của mẫu thành công.

Cách sửa: biến bộ định lượng thành lười với ?

Thêm dấu ? ngay sau một bộ định lượng sẽ chuyển nó từ tham lam sang lười (lazy, còn gọi là “không tham lam” hay “reluctant”): *?, +?, ??, {n,m}?.

Một bộ định lượng lười làm điều ngược lại: nó bắt đầu bằng cách khớp càng ít ký tự càng tốt, rồi chỉ mở rộng khi phần còn lại của mẫu chưa thể thành công.

<b>.*?</b> với cùng chuỗi đó:

  1. <b> khớp với thẻ <b> đầu tiên.
  2. .*? bắt đầu bằng cách khớp không ký tự nào.
  3. Bộ máy kiểm tra: </b> có khớp ngay tại đây không? Không (chúng ta đang ở “Hello…”, chưa tới </b>) — vậy nó mở rộng .*? thêm đúng một ký tự rồi kiểm tra lại.
  4. Điều này lặp lại từng ký tự một cho đến khi .*? nuốt trọn đúng chữ Hello, thời điểm đó </b> khớp ngay lập tức.

Kết quả: <b>Hello</b><b>World</b> trả về thành hai kết quả khớp riêng biệt, đây gần như luôn là điều bạn thực sự muốn khi phân tích các cấu trúc kiểu thẻ hoặc dấu phân cách.

Khi nào bạn thực sự muốn dùng tham lam (không chỉ là “mặc định sai”)

Tham lam không phải là một sai lầm của ngôn ngữ regex — nó đúng cho một trường hợp khác, cũng phổ biến không kém: khớp ranh giới ngoài cùng của một thứ gì đó, chứ không phải đơn vị nhỏ nhất bên trong nó. Nếu bạn đang trích xuất “mọi thứ giữa dấu { đầu tiên và dấu } cuối cùng” trong một đoạn văn bản giống JSON (chẳng hạn để lấy trọn một object bất kể độ lồng nhau), tham lam là lựa chọn đúng đắn, còn lười sẽ dừng lại ở dấu } bên trong đầu tiên thay vào đó, cho bạn một đoạn kết quả bị cắt cụt, không hợp lệ.

Quy tắc chung: dùng lười cho các đoạn nhỏ được phân cách lặp lại (thẻ, chuỗi trong dấu nháy, các mục trong danh sách); dùng tham lam để “lấy trọn cả khoảng ngoài cùng.”

Bảng tra nhanh

Mẫu Hành vi Dùng khi
.*, .+, {n,m} Tham lam — khớp dài nhất có thể Bạn muốn khoảng ngoài cùng, hoặc chuỗi chỉ có một kết quả khớp duy nhất
.*?, .+?, {n,m}? Lười — khớp ngắn nhất có thể Bạn có nhiều đoạn được phân cách tương tự nhau và muốn lấy riêng từng đoạn

Nếu bạn không chắc một mẫu thực sự đang làm gì trên dữ liệu đầu vào thực của mình thay vì chuỗi thử nghiệm, hãy chạy nó trên đoạn văn bản có nhiều kết quả khớp thực tế trong một công cụ kiểm tra regex với tính năng làm nổi bật kết quả khớp trực tiếp — bạn sẽ thấy ngay sự khác biệt: một khối được tô sáng khổng lồ duy nhất cho tham lam so với nhiều khối riêng biệt cho lười, cách này thường nhanh hơn nhiều so với việc suy luận từng ký tự một.

← Quay lại Blog