Một cặp thay thế của người Viking trong Java là gì?


149

Tôi đã đọc tài liệu cho StringBuffer, đặc biệt là phương thức Reverse () . Tài liệu đó đề cập một vài điều về các cặp thay thế . Một cặp thay thế trong bối cảnh này là gì? Và những người thay thế thấpcao là gì?


3
Đó là thuật ngữ UTF-16, được giải thích tại đây: download.oracle.com/javase/6/docs/api/java/lang/
mẹo

1
Phương thức đó có lỗi: cần đảo ngược các ký tự đầy đủ points điểm mã - không tách rời các phần của chúng, units đơn vị mã. Lỗi là phương pháp kế thừa cụ thể đó chỉ hoạt động trên các đơn vị char riêng lẻ thay vì trên các điểm mã, đó là những gì bạn muốn String được tạo thành, không chỉ các đơn vị char. Quá tệ Java không cho phép bạn sử dụng OO để sửa lỗi đó, nhưng cả Stringlớp và các StringBufferlớp đã bị đóng băng final. Nói, đó không phải là một uyển ngữ cho giết? :)
tchrist

2
@tchrist Tài liệu (và nguồn) nói rằng nó đảo ngược như một chuỗi các điểm mã. (Có lẽ 1.0.2 đã không làm điều đó và bạn sẽ không bao giờ có sự thay đổi hành vi như vậy trong những ngày này.)
Tom Hawtin - tackline

Câu trả lời:


128

Thuật ngữ "cặp thay thế" dùng để chỉ một phương tiện mã hóa các ký tự Unicode có điểm mã cao trong sơ đồ mã hóa UTF-16.

Trong mã hóa ký tự Unicode, các ký tự được ánh xạ tới các giá trị trong khoảng từ 0x0 đến 0x10FFFF.

Trong nội bộ, Java sử dụng sơ đồ mã hóa UTF-16 để lưu trữ các chuỗi văn bản Unicode. Trong UTF-16, các đơn vị mã 16 bit (hai byte) được sử dụng. Vì 16 bit chỉ có thể chứa phạm vi ký tự từ 0x0 đến 0xFFFF, một số độ phức tạp bổ sung được sử dụng để lưu trữ các giá trị trên phạm vi này (0x10000 đến 0x10FFFF). Điều này được thực hiện bằng cách sử dụng các cặp đơn vị mã được gọi là đại diện thay thế.

Các đơn vị mã thay thế nằm trong hai phạm vi được gọi là "đại diện thay thế cao" và "đại diện thay thế thấp", tùy thuộc vào việc chúng được cho phép ở đầu hoặc cuối chuỗi đơn vị hai mã.


4
cái này có nhiều phiếu bầu nhất nhưng nó không cung cấp một ví dụ mã nào. Cũng không có bất kỳ câu trả lời nào về cách sử dụng nó. Đó là lý do tại sao điều này đang bị hạ thấp.
George Xavier

57

Các phiên bản Java ban đầu biểu thị các ký tự Unicode bằng cách sử dụng kiểu dữ liệu char 16 bit. Thiết kế này có ý nghĩa tại thời điểm đó, bởi vì tất cả các ký tự Unicode có giá trị nhỏ hơn 65.535 (0xFFFF) và có thể được biểu thị bằng 16 bit. Tuy nhiên, sau đó, Unicode đã tăng giá trị tối đa lên 1.114.111 (0x10FFFF). Do các giá trị 16 bit quá nhỏ để thể hiện tất cả các ký tự Unicode trong Unicode phiên bản 3.1, nên các giá trị 32 bit - được gọi là điểm mã - đã được áp dụng cho sơ đồ mã hóa UTF-32. Nhưng các giá trị 16 bit được ưa thích hơn các giá trị 32 bit để sử dụng bộ nhớ hiệu quả, do đó Unicode đã giới thiệu một thiết kế mới để cho phép tiếp tục sử dụng các giá trị 16 bit. Thiết kế này, được áp dụng trong sơ đồ mã hóa UTF-16, gán 1.024 giá trị cho các đại diện thay thế cao 16 bit (trong phạm vi U + D800 đến U + DBFF) và 1.024 giá trị khác cho các đại diện thấp 16 bit (trong phạm vi U + DC00 đến U + DFFF).


7
Tôi thích điều này hơn câu trả lời được chấp nhận, vì nó giải thích cách Unicode 3.1 dành riêng các giá trị 1024 + 1024 (cao + thấp) trong số 65535 ban đầu để đạt được 1024 * 1024 giá trị mới, không có yêu cầu bổ sung nào mà trình phân tích cú pháp bắt đầu khi bắt đầu chuỗi.
Eric Hirst

1
Tôi không thích câu trả lời này vì ngụ ý UTF-16 là mã hóa Unicode hiệu quả nhất về bộ nhớ. UTF-8 tồn tại và không hiển thị hầu hết văn bản dưới dạng hai byte. UTF-16 chủ yếu được sử dụng ngày nay bởi vì Microsoft đã chọn nó trước UTF-32 là một thứ, không phải vì hiệu quả bộ nhớ. Về lần duy nhất bạn thực sự muốn UTF-16 là khi bạn thực hiện nhiều thao tác xử lý tệp trên Windows, và do đó cả đọc viết nó rất nhiều. Mặt khác, UTF-32 cho tốc độ cao (bù trừ không đổi b / c) hoặc UTF-8 cho bộ nhớ thấp (b / c tối thiểu 1 byte)
Vụ kiện của Quỹ Monica

23

Điều mà tài liệu này nói là các chuỗi UTF-16 không hợp lệ có thể trở nên hợp lệ sau khi gọi reversephương thức vì chúng có thể là sự đảo ngược của các chuỗi hợp lệ. Một cặp thay thế (được thảo luận ở đây ) là một cặp giá trị 16 bit trong UTF-16 mã hóa một điểm mã Unicode duy nhất; các chất thay thế thấp và cao là hai nửa của mã hóa đó.


6
Làm rõ. Một chuỗi phải được đảo ngược trên các ký tự "thật" (còn gọi là "biểu đồ" hoặc "thành phần văn bản"). Một điểm mã "ký tự" có thể là một hoặc hai khối "char" (cặp thay thế) và một biểu đồ có thể là một hoặc nhiều điểm mã đó (nghĩa là mã ký tự cơ sở cộng với một hoặc nhiều mã ký tự kết hợp, mỗi mã có thể là một hoặc hai đoạn 16 bit hoặc "ký tự" dài). Vì vậy, một biểu đồ duy nhất có thể là ba ký tự kết hợp, mỗi ký tự dài hai "ký tự", tổng cộng 6 "ký tự". Tất cả 6 "ký tự" phải được giữ cùng nhau, theo thứ tự (nghĩa là không đảo ngược), khi đảo ngược toàn bộ chuỗi ký tự.
Triynko

4
Do đó kiểu dữ liệu "char" khá sai lệch. "Nhân vật" là một thuật ngữ lỏng lẻo. Kiểu "char" thực sự chỉ là kích thước khối UTF16 và chúng tôi gọi nó là ký tự vì sự hiếm có tương đối của các cặp thay thế xảy ra (nghĩa là nó thường đại diện cho toàn bộ điểm mã ký tự), vì vậy "ký tự" thực sự đề cập đến một điểm mã unicode duy nhất , nhưng sau đó với các ký tự kết hợp, bạn có thể có một chuỗi các ký tự hiển thị dưới dạng một "phần tử ký tự / biểu đồ / văn bản". Đây không phải là khoa học tên lửa; các khái niệm là đơn giản, nhưng ngôn ngữ là khó hiểu.
Triynko

Vào thời điểm Java đang được phát triển, Unicode đang ở giai đoạn sơ khai. Java đã tồn tại khoảng 5 năm trước khi Unicode có các cặp thay thế, do đó, một char 16 bit khá phù hợp vào thời điểm đó. Bây giờ, bạn tốt hơn nhiều khi sử dụng UTF-8 và UTF-32 so với UTF-16.
Jonathan Baldwin

23

Thêm một số thông tin cho các câu trả lời trên từ bài viết này .

Đã thử nghiệm trong Java-12, nên hoạt động trong tất cả các phiên bản Java trên 5.

Như đã đề cập ở đây: https://stackoverflow.com/a/47505451/2987755 ,
bất kỳ ký tự nào (có Unicode nằm trên U + FFFF) được biểu diễn dưới dạng một cặp thay thế, mà Java lưu trữ dưới dạng một cặp giá trị char, tức là một Unicode ký tự được biểu diễn dưới dạng hai ký tự Java liền kề.
Như chúng ta có thể thấy trong ví dụ sau.
1. Chiều dài:

"🌉".length()  //2, Expectations was it should return 1

"🌉".codePointCount(0,"🌉".length())  //1, To get the number of Unicode characters in a Java String  

2. Bình đẳng:
Biểu thị "" cho Chuỗi bằng Unicode \ud83c\udf09như bên dưới và kiểm tra tính bằng.

"🌉".equals("\ud83c\udf09") // true

Java không hỗ trợ UTF-32

"🌉".equals("\u1F309") // false  

3. Bạn có thể chuyển đổi ký tự Unicode thành Chuỗi Java

"🌉".equals(new String(Character.toChars(0x0001F309))) //true

4. String.sub chuỗi () không xem xét các ký tự bổ sung

"🌉🌐".substring(0,1) //"?"
"🌉🌐".substring(0,2) //"🌉"
"🌉🌐".substring(0,4) //"🌉🌐"

Để giải quyết điều này chúng ta có thể sử dụng String.offsetByCodePoints(int index, int codePointOffset)

"🌉🌐".substring(0,"🌉🌐".offsetByCodePoints(0,1) // "🌉"
"🌉🌐".substring(2,"🌉🌐".offsetByCodePoints(1,2)) // "🌐"

5. Lặp lại chuỗi Unicode bằng BreakIterator
6. Sắp xếp chuỗi bằng Unicode java.text.Collator
7. Các ký tự toUpperCase(), toLowerCase()không nên sử dụng các phương thức, thay vào đó, sử dụng chữ hoa và chữ thường của miền địa phương cụ thể.
8. Character.isLetter(char ch)không hỗ trợ, được sử dụng tốt hơn Character.isLetter(int codePoint), đối với mỗi methodName(char ch)phương thức trong lớp Nhân vật sẽ có loại methodName(int codePoint)có thể xử lý các ký tự bổ sung.
9. Chỉ định bộ ký tự trong String.getBytes(), chuyển đổi từ byte thành chuỗi InputStreamReader,,OutputStreamWriter

Tham chiếu:
https://coolsymbol.com/emojis/emoji-for-copy-and-paste.html#objects
https://www.online-toolz.com/tools/text-unicode-entities-convertor.php
https: //www.ibm.com/developerworks/l Library / j-unicode / index.html
https://www.oracle.com/technetwork/articles/javaee/supâyary-42654.html

Thông tin thêm về ví dụ image1 image2
Các thuật ngữ khác đáng để khám phá: Chuẩn hóa , BiDi


2
đã đăng nhập đặc biệt để bỏ phiếu cho câu trả lời này (ý tôi là đã thay đổi cửa sổ từ ẩn danh sang bình thường: P). Giải thích tốt nhất cho một người mới
N-JOY

1
Cảm ơn bạn!, Tôi rất vui vì nó đã giúp, nhưng tác giả bài viết gốc xứng đáng với tất cả sự đánh giá cao.
dkb

Những ví dụ tuyệt vời! Tôi đã đăng nhập để nâng cấp nó lên :) Và một lần nữa, nó khiến tôi nghĩ (một lần nữa) rằng tôi thực sự không hiểu tại sao Java giữ các lỗi KNOWN tồn tại trong mã của họ. Tôi hoàn toàn tôn trọng họ không muốn phá vỡ mã hiện có, nhưng thôi ... có bao nhiêu giờ đã mất khi làm việc xung quanh các lỗi này? Nếu nó bị hỏng, hãy sửa nó đi, chết tiệt!
Franz D.


6

Lời nói đầu nhỏ

  • Unicode đại diện cho các điểm mã. Mỗi điểm mã có thể được mã hóa thành các khối 8-, 16 hoặc - 32 bit theo tiêu chuẩn Unicode.
  • Trước Phiên bản 3.1, phần lớn được sử dụng là mã hóa 8 bit, được gọi là UTF-8 và mã hóa 16 bit, được gọi là Bộ ký tự phổ biến UCS-2 hoặc Mã hóa trong 2 octet. UTF-8 mã hóa các điểm Unicode dưới dạng một chuỗi các khối 1 byte, trong khi UCS-2 luôn lấy 2 byte:

    A = 41 - một khối 8 bit có UTF-8
    A = 0041 - một khối 16 bit có UCS-2
    = CE A9 - hai khối 8 bit có UTF-8
    = 03A9 - một khối 16 bit với UCS-2

Vấn đề

Hiệp hội nghĩ rằng 16 bit sẽ đủ để bao gồm bất kỳ ngôn ngữ nào con người có thể đọc được, điều này mang lại 2 ^ 16 = 65536 giá trị mã có thể. Điều này đúng với Mặt phẳng 0, còn được gọi là Mặt phẳng đa ngôn ngữ cơ bản, bao gồm 55.445 trong số 65536 điểm mã ngày nay. BPM bao gồm hầu hết mọi ngôn ngữ của con người trên thế giới, bao gồm cả các biểu tượng Trung Quốc-Nhật Bản-Hàn Quốc (CJK).

Thời gian trôi qua và bộ ký tự châu Á mới được thêm vào, các biểu tượng Trung Quốc đã chiếm hơn 70.000 điểm một mình. Bây giờ, thậm chí còn có các điểm Emoji như một phần của tiêu chuẩn. 16 máy bay "bổ sung" mới đã được thêm vào. Phòng UCS-2 không đủ để chứa bất cứ thứ gì lớn hơn Máy bay-0.

Quyết định Unicode

  1. Giới hạn Unicode cho 17 mặt phẳng × 65 536 ký tự trên mỗi mặt phẳng = 1 114 112 điểm tối đa.
  2. Trình bày UTF-32, trước đây gọi là UCS-4, để giữ 32 bit cho mỗi điểm mã và bao gồm tất cả các mặt phẳng.
  3. Tiếp tục sử dụng UTF-8 làm mã hóa động, giới hạn tối đa UTF-8 đến 4 byte cho mỗi điểm mã, tức là từ 1 đến 4 byte mỗi điểm.
  4. Không dùng UCS-2
  5. Tạo UTF-16 dựa trên UCS-2. Làm cho UTF-16 động, do đó, cần 2 byte hoặc 4 byte cho mỗi điểm. Gán 1024 điểm U + D800 Nhận U + DBFF, được gọi là Surrogates cao, cho UTF-16; gán 1024 ký hiệu U + DC00 Nhận U + DFFF, được gọi là Surrogates thấp, cho UTF-16.

    Với những thay đổi đó, BPM được bao phủ với 1 khối 16 bit trong UTF-16, trong khi tất cả các "ký tự bổ sung" được bao phủ bởi các cặp Surrogate trình bày 2 khối mỗi 16 bit, hoàn toàn là 1024x1024 = 1 048 576 điểm.

    Một người thay thế cao trước một người thay thế thấp . Bất kỳ sai lệch so với quy tắc này được coi là một mã hóa xấu. Ví dụ, người thay thế không có cặp là không chính xác, người thay thế thấp đứng trước người thay thế cao là không chính xác.

    𝄞, 'MUSICAL SYMBOL G Clef', được mã hóa dưới dạng UTF-16 như một cặp những người đại diện 0xD834 0xDD1E (2 bởi 2 byte),
    trong UTF-8 như 0xF0 0x9D 0x84 0x9E (4 bởi 1 byte),
    trong UTF-32 như 0x0001D11E (1 x 4 byte).

Tình hình hiện tại

  • Mặc dù theo tiêu chuẩn, các chất thay thế chỉ được gán riêng cho UTF-16, nhưng trước đây, một số ứng dụng Windows và Java đã sử dụng các điểm UTF-8 và UCS-2 dành riêng cho phạm vi thay thế.
    Để hỗ trợ các ứng dụng cũ với mã hóa UTF-8 / UTF-16 không chính xác, một định dạng chuyển đổi Wobbly tiêu chuẩn mới, WTF-8 , đã được tạo. Nó hỗ trợ các điểm thay thế tùy ý, chẳng hạn như một đại diện thay thế không được ghép nối hoặc một chuỗi không chính xác. Ngày nay, một số sản phẩm không tuân thủ tiêu chuẩn và coi UTF-8 là WTF-8.
  • Giải pháp thay thế đã mở ra nhiều vấn đề bảo mật trong việc chuyển đổi giữa các bảng mã khác nhau, hầu hết chúng đều được xử lý tốt.

Nhiều chi tiết lịch sử đã bị loại bỏ để theo chủ đề.
Tiêu chuẩn Unicode mới nhất có thể được tìm thấy tại http://www.unicode.org/versions/latest


3

Một cặp thay thế là hai "đơn vị mã" trong UTF-16 tạo nên một "điểm mã". Tài liệu Java nói rằng các 'điểm mã' này sẽ vẫn hợp lệ, với 'đơn vị mã' của chúng được sắp xếp chính xác, sau khi đảo ngược. Nó nói thêm rằng hai đơn vị mã thay thế không ghép đôi có thể được đảo ngược và tạo thành một cặp thay thế hợp lệ. Điều đó có nghĩa là nếu có các đơn vị mã không ghép đôi, thì có khả năng đảo ngược của đảo ngược có thể không giống nhau!

Mặc dù vậy, lưu ý rằng tài liệu không nói gì về Biểu đồ - là nhiều điểm mã được kết hợp. Có nghĩa là e và dấu đi cùng với nó vẫn có thể được chuyển đổi, do đó đặt dấu trước e. Điều đó có nghĩa là nếu có một nguyên âm khác trước e thì nó có thể nhận được trọng âm trên e.

Rất tiếc!

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.