2013-04-15 35 views
13

Câu hỏi của tôi khá đơn giản nhưng khó hiểu. Nó có thể là có một chuyển đổi đơn giản mà sửa lỗi này nhưng tôi không có nhiều kinh nghiệm trong regexes Java ...Strange Java Unicode Regular Expression StringIndexOutOfBoundsException

String line = ""; 
line.replaceAll("(?i)(.)\\1{2,}", "$1"); 

Điều này bị treo. Nếu tôi tháo khóa (?i), nó sẽ hoạt động. Ba ký tự unicode không phải ngẫu nhiên, chúng được tìm thấy giữa một văn bản lớn của Hàn Quốc, nhưng tôi không biết chúng có hợp lệ hay không.

Điều kỳ lạ là regex hoạt động cho tất cả văn bản khác nhưng điều này. Tại sao tôi gặp lỗi?

Đây là ngoại lệ tôi nhận được

Exception in thread "main" java.lang.StringIndexOutOfBoundsException: String index out of range: 6 
    at java.lang.String.charAt(String.java:658) 
    at java.lang.Character.codePointAt(Character.java:4668) 
    at java.util.regex.Pattern$CIBackRef.match(Pattern.java:4846) 
    at java.util.regex.Pattern$Curly.match(Pattern.java:4125) 
    at java.util.regex.Pattern$GroupTail.match(Pattern.java:4615) 
    at java.util.regex.Pattern$CharProperty.match(Pattern.java:3694) 
    at java.util.regex.Pattern$GroupHead.match(Pattern.java:4556) 
    at java.util.regex.Pattern$Start.match(Pattern.java:3408) 
    at java.util.regex.Matcher.search(Matcher.java:1199) 
    at java.util.regex.Matcher.find(Matcher.java:592) 
    at java.util.regex.Matcher.replaceAll(Matcher.java:902) 
    at java.lang.String.replaceAll(String.java:2162) 
    at tokenizer.Test.main(Test.java:51) 
+0

Xem: [Hướng dẫn về Java Regex] (http://www.vogella.com/articles/JavaRegularExpressions/article.html) – Justin

+1

Sự cố xảy ra như thế nào? Có ngoại lệ nào không? – Thilo

+0

Vui lòng xác định ý của bạn bằng cách gặp lỗi hoặc "lỗi"? Bạn đã không cho chúng tôi thấy bất kỳ lỗi nào. – eis

Trả lời

1

Giải thích bởi Santosh trong this answer không chính xác. Điều này có thể được chứng minh bằng cách chạy

String str = ""; 
System.out.println("code point: " + .codePointAt(0)); 

đó sẽ ra (ít nhất là đối với tôi) giá trị 128.149, được xác nhận bởi this page như chính xác. Vì vậy, Java không diễn giải chuỗi theo cách sai. Nó đã giải thích nó sai khi sử dụng phương thức getBytes().

Tuy nhiên, như được giải thích bởi OP, có vẻ như biểu thức chính quy bị treo trên đó. Tôi không có lời giải thích nào khác vì nó là một lỗi trong java. Hoặc là, hoặc sau đó nó không hỗ trợ đầy đủ UTF-16 theo thiết kế.

Edit:

dựa trên this answer:

vít biên dịch

regex lên trên UTF-16. Một lần nữa, điều này không bao giờ có thể là cố định hoặc nó sẽ thay đổi các chương trình cũ. Bạn thậm chí không thể nhận được xung quanh lỗi bằng cách sử dụng giải pháp thay thế bình thường đối với sự cố mã hóa Unicode mã nguồn của Java bằng cách biên dịch với mã hóa java UTF-8 java, bởi vì điều ngu ngốc lưu trữ các chuỗi là khó chịu UTF-16, phá vỡ chúng trong các lớp nhân vật. OOPS!

Dường như đây là giới hạn của cụm từ thông dụng trong java.


Vì bạn nhận xét rằng

nó sẽ là tốt nhất nếu tôi chỉ đơn giản là có thể bỏ qua các ký tự UTF-16 và áp dụng regex chứ không phải ném một ngoại lệ.

Điều này chắc chắn có thể được thực hiện. Một cách đơn giản là chỉ áp dụng regex của bạn vào một phạm vi nhất định. Lọc dải ký tự unicode đã được giải thích trong this answer. Dựa trên câu trả lời đó, ví dụ mà dường như không bị sốc nhưng chỉ để lại các nhân vật vấn đề một mình:

line.replaceAll("(?Ui)([\\u0000-\\uffff])\\1{2,}", "$1")  

// "" -> "" 
// "foo foo" -> "foo foo" 
// "foo aAa foo" -> "foo a foo" 
+0

line.replaceAll ("(? Ui) ([\\ u0000 - \\ uffff]) \\ 1 {2,}", "$ 1"); Điều này dường như là cách để đi và bỏ qua lỗi. Cảm ơn. – binit

+0

@binit không thành vấn đề. Trên thực tế, như thông tin bổ sung, [liên kết này] (http://www.oracle.com/technetwork/articles/javase/supplementary-142654.html) nói rằng java regex sẽ có thể xử lý các ký tự bổ sung, vì vậy tôi nghĩ rằng điều này xác nhận bạn đang xử lý lỗi. – eis

4

Các ký tự mà bạn đề cập là thực sự "Double byte characters". Có nghĩa là hai byte tạo thành một ký tự. Nhưng đối với Java để giải thích điều này, thông tin mã hóa (khi thông tin khác với mã hóa nền tảng mặc định) cần được chuyển một cách rõ ràng (hoặc mã hóa nền tảng mặc định khác sẽ được sử dụng).

Để chứng minh điều này, hãy xem xét sau

String line = ""; 
System.out.println(line.length()); 

này in theo chiều dài như 6! Trong khi đó, chúng ta chỉ có ba nhân vật,

tại đoạn mã sau

String line1 = new String("".getBytes(),"UTF-8"); 
System.out.println(line1.length()); 

in dài như 3 mà dự định.

nếu bạn thay thế dòng

String line = ""; 

với

String line1 = new String("".getBytes(),"UTF-8"); 

nó hoạt động và regex không thất bại. Tôi đã sử dụng UTF-8 ở đây. Vui lòng sử dụng mã hóa thích hợp của nền tảng dự định của bạn.

Thư viện regex Java phụ thuộc nhiều vào số Character Sequence do đó phụ thuộc vào lược đồ mã hóa. Đối với các chuỗi có mã hóa ký tự khác với mã hóa mặc định, các ký tự không thể được giải mã chính xác (nó cho thấy 6 ký tự thay vì 3!) Và do đó regex không thành công.

+0

Hey Santosh, bản sửa lỗi của bạn không hoạt động ở phần cuối của tôi. Tôi đã thử: Chuỗi mới ("". GetBytes(), "UTF-8"). ReplaceAll ("(? I) (.) \\ 1 {2,}", "$ 1"); và nó vẫn treo ... cũng mới String ("". GetBytes(), "UTF-8"). Length() hiển thị cho tôi 6 (bạn đã đề cập 3)! – binit

+0

Trên máy tính của tôi (Win XP SP2, jdk1.6.0_14) nó hiển thị 3 ký tự. Hệ điều hành/JDK bạn đang sử dụng là gì? Bạn có thể thử một số mã hóa khác (e.q. UTF-16) không? Bộ ký tự mặc định của máy của bạn là gì? – Santosh

+0

'line1.length()' chỉ có thể là '3' nếu mã hóa mặc định nền tảng của bạn không hỗ trợ các ký tự và do đó mã hóa'? 'Thay cho chúng. Vì vậy, bạn đang nhìn thấy chiều dài của chuỗi '" ??? "', không biết làm thế nào đó là dự định. Nếu mã hóa nền tảng của bạn là 'UTF-8', bạn sẽ nhận được chuyến đi khứ hồi vô dụng. – Esailija

0

Trên thực tế, nó chỉ là một lỗi.

Đây là dấu vết ngăn xếp và nguồn mở dành cho.

Khi CIBackRef (đối với tham chiếu ngược không phân biệt chữ hoa chữ thường) so sánh với nhóm, nó sẽ không làm chính xác chỉ số vòng lặp. Phần này hiển thị bản sửa lỗi:

 // Check each new char to make sure it matches what the group 
     // referenced matched last time around 
     int x = i; 
     for (int index=0; index<groupSize;) { 
      int c1 = Character.codePointAt(seq, x); 
      int c2 = Character.codePointAt(seq, j); 
      if (c1 != c2) { 
       if (doUnicodeCase) { 
        int cc1 = Character.toUpperCase(c1); 
        int cc2 = Character.toUpperCase(c2); 
        if (cc1 != cc2 && 
         Character.toLowerCase(cc1) != 
         Character.toLowerCase(cc2)) 
         return false; 
       } else { 
        if (ASCII.toLower(c1) != ASCII.toLower(c2)) 
         return false; 
       } 
      } 
      int n = Character.charCount(c1); 
      x += n; 
      index += n; // was index++ 
      j += Character.charCount(c2); 
     } 

groupSize là tổng số charCount của nhóm. j là chỉ mục cho nhóm được tham chiếu.

Các thử nghiệm

//9ff0 9592 9ff0 9592 9ff0 9592 
    val line = "\ud83d\udc95\ud83d\udc95\ud83d\udc95" 
    Console println Try(line.replaceAll("(?ui)(.)\\1{2,}", "$1")) 

thất bại thường

[email protected]:~/tmp$ skalac kcharex.scala ; skala kcharex.Test 
Failure(java.lang.StringIndexOutOfBoundsException: String index out of range: 6) 

nhưng thành công với việc sửa chữa

[email protected]:~/tmp$ skala -J-Xbootclasspath/p:../bootfix kcharex.Test 
Success() 

Các lỗi khác trong mẫu mã ban đầu là những lá cờ inline nên bao gồm ?ui . Các javadoc trên Pattern.CASE_INSENSITIVE nói:

Theo mặc định, phù hợp với case-insensitive giả định rằng chỉ các nhân vật trong charset US-ASCII đang được khớp lệnh. Không thể nhận biết được kết quả phù hợp với trường hợp nhạy cảm với mã Unicode bằng cách chỉ định cờ UNICODE_CASE ở số kết hợp với cờ này.

Như bạn có thể thấy từ đoạn mã, không có u, nó sẽ không thành công nếu ASCII.toLower không so sánh bằng nhau, không được dự định. Tôi không đủ tinh vi để biết về một nhân vật bổ sung có thể thất bại trong bài kiểm tra đó mà không cần viết mã để tìm ra nó.

Các vấn đề liên quan