2009-05-18 35 views
20

Tôi cần mã hóa/giải mã mảng byte UTF-16 đến và từ java.lang.String. Các mảng byte được gán cho tôi với một Byte Order Marker (BOM) và tôi cần mã hóa các mảng byte với một BOM.Làm cách nào để mã hóa/giải mã mảng byte UTF-16LE bằng BOM?

Ngoài ra, vì tôi đang giao dịch với máy khách/máy chủ Microsoft, tôi muốn phát ra mã hóa ở dạng cuối nhỏ (cùng với LE BOM) để tránh bất kỳ sự hiểu lầm nào. Tôi nhận ra rằng với hội đồng quản trị nó nên làm việc lớn endian, nhưng tôi không muốn bơi ngược dòng trong thế giới Windows.

Như một ví dụ, đây là một phương pháp mã hóa một java.lang.String như UTF-16 trong little endian với BOM:

public static byte[] encodeString(String message) { 

    byte[] tmp = null; 
    try { 
     tmp = message.getBytes("UTF-16LE"); 
    } catch(UnsupportedEncodingException e) { 
     // should not possible 
     AssertionError ae = 
     new AssertionError("Could not encode UTF-16LE"); 
     ae.initCause(e); 
     throw ae; 
    } 

    // use brute force method to add BOM 
    byte[] utf16lemessage = new byte[2 + tmp.length]; 
    utf16lemessage[0] = (byte)0xFF; 
    utf16lemessage[1] = (byte)0xFE; 
    System.arraycopy(tmp, 0, 
        utf16lemessage, 2, 
        tmp.length); 
    return utf16lemessage; 
} 

cách tốt nhất để làm điều này trong Java là gì? Lý tưởng nhất là tôi muốn tránh sao chép toàn bộ mảng byte vào một mảng byte mới có hai byte bổ sung được cấp phát lúc đầu.

Cũng vậy với giải mã một chuỗi như vậy, nhưng đó là nhiều hơn nữa đơn giản bằng cách sử dụng các java.lang.String constructor:

public String(byte[] bytes, 
       int offset, 
       int length, 
       String charsetName) 

Trả lời

27

Các "UTF-16" tên charset sẽ luôn mã hóa với một BOM và sẽ giải mã dữ liệu bằng cách sử dụng lớn/ít endianness, nhưng "UnicodeBig" và "UnicodeLittle" rất hữu ích cho mã hóa theo thứ tự byte cụ thể. Sử dụng UTF-16LE hoặc UTF-16BE không có BOM - see this post để biết cách sử dụng "\ uFEFF" để xử lý các BOM theo cách thủ công. Xem here để đặt tên chuẩn của tên chuỗi ký tự hoặc (tốt hơn) là lớp Charset. Cũng lưu ý rằng chỉ cần limited subset of encodings được hỗ trợ tuyệt đối.

+1

Cảm ơn! Một vấn đề khác mặc dù ... Sử dụng "UTF-16" mã hóa dữ liệu như Big Endian, mà tôi nghi ngờ sẽ không đi quá tốt với dữ liệu Microsoft (mặc dù BOM tồn tại). Bất kỳ cách nào để mã hóa UTF-16LE với BOM với Java? Tôi sẽ cập nhật câu hỏi của mình để phản ánh những gì tôi thực sự đang tìm kiếm ... –

+0

Nhấp vào liên kết "xem bài đăng này" mà anh ấy đã cung cấp. Về cơ bản, bạn có một ký tự \ uFEFF ở đầu chuỗi của bạn, và sau đó mã hóa thành UTF-16LE, và kết quả sẽ có một BOM phù hợp. –

+0

Sử dụng "UnicodeLittle" (giả sử JRE của bạn hỗ trợ nó - ("\ uEFFF" + "chuỗi của tôi"). GetBytes ("UTF-16LE") nếu không). Mặc dù tôi sẽ ngạc nhiên nếu Microsoft API mong đợi một BOM nhưng không thể xử lý dữ liệu lớn về cuối - họ có xu hướng thích sử dụng BOM hơn các nền tảng khác. Thử nghiệm với các chuỗi rỗng - bạn có thể nhận được các mảng trống nếu không có dữ liệu. – McDowell

2
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(string.length() * 2 + 2); 
    byteArrayOutputStream.write(new byte[]{(byte)0xFF,(byte)0xFE}); 
    byteArrayOutputStream.write(string.getBytes("UTF-16LE")); 
    return byteArrayOutputStream.toByteArray(); 

EDIT: Đọc lại câu hỏi của bạn, tôi thấy bạn muốn tránh phân bổ mảng kép hoàn toàn. Rất tiếc, API không cung cấp cho bạn điều đó, theo như tôi biết. (Có một phương pháp, nhưng nó không được chấp nhận và bạn không thể chỉ định mã hóa với nó).

Tôi đã viết ở trên trước khi tôi thấy nhận xét của bạn, tôi nghĩ câu trả lời để sử dụng các lớp học nio đang đi đúng hướng. Tôi đã nhìn vào điều đó, nhưng tôi không đủ quen thuộc với API để biết được cách bạn hoàn thành công việc đó.

+0

Cảm ơn. Ngoài ra những gì tôi có thể thích ở đây là không phân bổ toàn bộ mảng byte với string.getBytes ("UTF-16LE") - có lẽ bằng cách gói luồng như là một InputStream, đó là điểm của câu hỏi trước đây của tôi: http://stackoverflow.com/questions/837703/how-can-i-get-a-java-io-inputstream-from-a-java-lang-string –

+0

Lưu ý rằng mã này thực sự phân bổ các mảng đủ lớn cho Chuỗi ba lần, vì bạn có mảng nội bộ của ByteArrayOutputStream được sao chép trong cuộc gọi .toByteArray(). Một cách để làm cho nó quay trở lại để chỉ phân bổ hai là bọc ByteArrayOutputStream trong một OutputStreamWriter và viết chuỗi đó. Sau đó, bạn vẫn có trạng thái bên trong của ByteArrayOutputStream và bản sao được tạo bởi .toByteArray(), nhưng không phải là giá trị trả về từ .getBytes –

+0

Có vẻ như bạn đang trao đổi một mảng char cho một mảng byte nếu bạn làm điều đó, như các đại biểu OutputStreamWriter đến lớp StreamEncoder, tạo bộ đệm char [] để truy xuất dữ liệu Chuỗi. Chuỗi là bất biến, và kích thước của một mảng là không thay đổi, do đó, bản sao có vẻ không thể tránh khỏi. Tôi nghĩ rằng nio là nghĩa vụ phải giúp đỡ với việc tạo ra đôi trên ByteArrayOutputStream – Yishai

6

Trước hết, để giải mã, bạn có thể sử dụng bộ ký tự "UTF-16"; tự động phát hiện BOM ban đầu. Để mã hóa UTF-16BE, bạn cũng có thể sử dụng bộ ký tự "UTF-16" - nó sẽ viết một BOM phù hợp và sau đó xuất ra các công cụ cuối lớn.

Để mã hóa cho ít người dùng cuối cùng với BOM, tôi không nghĩ mã hiện tại của bạn quá tệ, ngay cả với phân bổ kép (trừ khi chuỗi của bạn thật sự quái dị). Những gì bạn có thể muốn làm nếu họ không phải là đối phó với một mảng byte mà là một java.nio ByteBuffer, và sử dụng lớp java.nio.charset.CharsetEncoder. (Bạn có thể lấy từ Charset.forName ("UTF-16LE"). NewEncoder()).

+0

Cảm ơn, lời khuyên tốt. –

7

Đây là cách bạn làm điều đó trong nio:

return Charset.forName("UTF-16LE").encode(message) 
      .put(0, (byte) 0xFF) 
      .put(1, (byte) 0xFE) 
      .array(); 

Nó chắc chắn được coi là nhanh hơn, nhưng tôi không biết có bao nhiêu mảng nó làm theo bao, nhưng sự hiểu biết của tôi về quan điểm API là nó được cho là để giảm thiểu điều đó.

+0

Điều này thực sự không hoạt động. Các cuộc gọi put (0) và put (1) sẽ ghi đè lên hai byte đầu tiên của ByteBuffer của tin nhắn được mã hóa. – hopia

0

Đây là câu hỏi cũ nhưng tôi vẫn không thể tìm thấy câu trả lời có thể chấp nhận được cho tình huống của mình. Về cơ bản, Java không có bộ mã hóa tích hợp cho UTF-16LE với BOM. Và như vậy, bạn phải triển khai thực hiện của riêng bạn.

Đây là những gì tôi đã kết thúc với:

private byte[] encodeUTF16LEWithBOM(final String s) { 
    ByteBuffer content = Charset.forName("UTF-16LE").encode(s); 
    byte[] bom = { (byte) 0xff, (byte) 0xfe }; 
    return ByteBuffer.allocate(content.capacity() + bom.length).put(bom).put(content).array(); 
} 
Các vấn đề liên quan