Làm thế nào để tìm bộ mã / mã hóa mặc định trong Java?


92

Câu trả lời rõ ràng là sử dụng Charset.defaultCharset()nhưng gần đây chúng tôi phát hiện ra rằng đây có thể không phải là câu trả lời đúng. Tôi đã được thông báo rằng kết quả khác với bộ ký tự mặc định thực được sử dụng bởi các lớp java.io trong một số trường hợp. Có vẻ như Java giữ 2 bộ ký tự mặc định. Có ai có bất kỳ hiểu biết về vấn đề này?

Chúng tôi đã có thể tái tạo một trường hợp thất bại. Đó là một loại lỗi của người dùng nhưng nó vẫn có thể cho thấy nguyên nhân gốc rễ của tất cả các vấn đề khác. Đây là mã,

public class CharSetTest {

    public static void main(String[] args) {
        System.out.println("Default Charset=" + Charset.defaultCharset());
        System.setProperty("file.encoding", "Latin-1");
        System.out.println("file.encoding=" + System.getProperty("file.encoding"));
        System.out.println("Default Charset=" + Charset.defaultCharset());
        System.out.println("Default Charset in Use=" + getDefaultCharSet());
    }

    private static String getDefaultCharSet() {
        OutputStreamWriter writer = new OutputStreamWriter(new ByteArrayOutputStream());
        String enc = writer.getEncoding();
        return enc;
    }
}

Máy chủ của chúng tôi yêu cầu bộ ký tự mặc định bằng tiếng Latin-1 để xử lý một số mã hóa hỗn hợp (ANSI / Latin-1 / UTF-8) trong một giao thức kế thừa. Vì vậy, tất cả các máy chủ của chúng tôi đều chạy với thông số JVM này,

-Dfile.encoding=ISO-8859-1

Đây là kết quả trên Java 5,

Default Charset=ISO-8859-1
file.encoding=Latin-1
Default Charset=UTF-8
Default Charset in Use=ISO8859_1

Ai đó cố gắng thay đổi thời gian chạy mã hóa bằng cách đặt tệp.encoding trong mã. Tất cả chúng ta đều biết điều đó không hiệu quả. Tuy nhiên, điều này dường như ném ra defaultCharset () nhưng nó không ảnh hưởng đến bộ ký tự mặc định thực được sử dụng bởi OutputStreamWriter.

Đây là lỗi hay tính năng?

CHỈNH SỬA: Câu trả lời được chấp nhận cho thấy nguyên nhân gốc rễ của vấn đề. Về cơ bản, bạn không thể tin tưởng defaultCharset () trong Java 5, đây không phải là kiểu mã hóa mặc định được sử dụng bởi các lớp I / O. Có vẻ như Java 6 khắc phục sự cố này.


Điều đó thật kỳ lạ, vì defaultCharset sử dụng một biến tĩnh chỉ được đặt một lần (cộng dồn vào tài liệu - khi khởi động VM). Bạn đang sử dụng Nhà cung cấp VM nào?
Bozho

Tôi đã có thể tái tạo điều này trên Java 5, cả trên Sun / Linux và Apple / OS X.
ZZ Coder

Điều đó giải thích tại sao defaultCharset () không lưu kết quả vào bộ nhớ đệm. Tôi vẫn cần tìm ra bộ ký tự mặc định thực sự được sử dụng bởi các lớp IO. Phải có một bộ ký tự mặc định khác được lưu trong bộ nhớ cache ở một nơi khác.
ZZ Coder

@ZZ Coder, tôi vẫn đang nghiên cứu về điều đó. Điều duy nhất tôi biết là Charset.defaulyCharset () không được gọi từ sun.nio.cs.StreamEncoder trong JVM 1.5. Trong JVM 1.6, phương thức Charset.defaulyCharset () được gọi là đưa ra kết quả mong đợi. Việc triển khai JVM 1.5 của StreamEncoder đang lưu vào bộ nhớ đệm mã hóa trước đó.
bruno conde

Câu trả lời:


62

Điều này thực sự kỳ lạ ... Sau khi được thiết lập, Bộ mã mặc định sẽ được lưu vào bộ nhớ đệm và nó không bị thay đổi khi lớp nằm trong bộ nhớ. Đặt thuộc "file.encoding"tính với System.setProperty("file.encoding", "Latin-1");không làm gì cả. Mỗi khi Charset.defaultCharset()được gọi, nó sẽ trả về bộ mã được lưu trong bộ nhớ cache.

Đây là kết quả của tôi:

Default Charset=ISO-8859-1
file.encoding=Latin-1
Default Charset=ISO-8859-1
Default Charset in Use=ISO8859_1

Tôi đang sử dụng JVM 1.6.

(cập nhật)

Đồng ý. Tôi đã tạo lại lỗi của bạn với JVM 1.5.

Nhìn vào mã nguồn 1.5, bộ ký tự mặc định được lưu trong bộ nhớ cache không được đặt. Tôi không biết đây có phải là lỗi hay không nhưng 1.6 thay đổi việc triển khai này và sử dụng bộ ký tự được lưu trong bộ nhớ cache:

JVM 1.5:

public static Charset defaultCharset() {
    synchronized (Charset.class) {
        if (defaultCharset == null) {
            java.security.PrivilegedAction pa =
                    new GetPropertyAction("file.encoding");
            String csn = (String) AccessController.doPrivileged(pa);
            Charset cs = lookup(csn);
            if (cs != null)
                return cs;
            return forName("UTF-8");
        }
        return defaultCharset;
    }
}

JVM 1.6:

public static Charset defaultCharset() {
    if (defaultCharset == null) {
        synchronized (Charset.class) {
            java.security.PrivilegedAction pa =
                    new GetPropertyAction("file.encoding");
            String csn = (String) AccessController.doPrivileged(pa);
            Charset cs = lookup(csn);
            if (cs != null)
                defaultCharset = cs;
            else
                defaultCharset = forName("UTF-8");
        }
    }
    return defaultCharset;
}

Khi bạn đặt mã hóa tệp file.encoding=Latin-1vào lần gọi tiếp theo Charset.defaultCharset(), điều gì sẽ xảy ra, bởi vì bộ ký tự mặc định được lưu trong bộ nhớ cache không được đặt, nó sẽ cố gắng tìm bộ ký tự thích hợp cho tên Latin-1. Không tìm thấy tên này vì nó không chính xác và trả về giá trị mặc định UTF-8.

Về lý do tại sao các lớp IO OutputStreamWriterlại trả về một kết quả không mong muốn, thì
việc triển khai sun.nio.cs.StreamEncoder(phù thủy được các lớp IO này sử dụng) cũng khác đối với JVM 1.5 và JVM 1.6. Việc triển khai JVM 1.6 dựa trên Charset.defaultCharset()phương thức để lấy mã hóa mặc định, nếu một phương thức không được cung cấp cho các lớp IO. Việc triển khai JVM 1.5 sử dụng một phương pháp khácConverters.getDefaultEncodingName(); để lấy bộ ký tự mặc định. Phương thức này sử dụng bộ đệm ẩn riêng của bộ ký tự mặc định được đặt khi khởi tạo JVM:

JVM 1.6:

public static StreamEncoder forOutputStreamWriter(OutputStream out,
        Object lock,
        String charsetName)
        throws UnsupportedEncodingException
{
    String csn = charsetName;
    if (csn == null)
        csn = Charset.defaultCharset().name();
    try {
        if (Charset.isSupported(csn))
            return new StreamEncoder(out, lock, Charset.forName(csn));
    } catch (IllegalCharsetNameException x) { }
    throw new UnsupportedEncodingException (csn);
}

JVM 1.5:

public static StreamEncoder forOutputStreamWriter(OutputStream out,
        Object lock,
        String charsetName)
        throws UnsupportedEncodingException
{
    String csn = charsetName;
    if (csn == null)
        csn = Converters.getDefaultEncodingName();
    if (!Converters.isCached(Converters.CHAR_TO_BYTE, csn)) {
        try {
            if (Charset.isSupported(csn))
                return new CharsetSE(out, lock, Charset.forName(csn));
        } catch (IllegalCharsetNameException x) { }
    }
    return new ConverterSE(out, lock, csn);
}

Nhưng tôi đồng ý với các ý kiến. Bạn không nên dựa vào tài sản này . Đó là một chi tiết triển khai.


Để tạo lại lỗi này, bạn phải sử dụng Java 5 và mã hóa mặc định JRE của bạn phải là UTF-8.
ZZ Coder

2
Đây là văn bản để thực hiện, không phải trừu tượng. Nếu bạn dựa vào những thứ không có giấy tờ, đừng ngạc nhiên nếu mã của bạn bị hỏng khi bạn nâng cấp lên phiên bản mới hơn của nền tảng.
McDowell

24

Đây là lỗi hay tính năng?

Có vẻ như hành vi không xác định. Tôi biết rằng, trên thực tế, bạn có thể thay đổi mã hóa mặc định bằng cách sử dụng thuộc tính dòng lệnh, nhưng tôi không nghĩ điều gì xảy ra khi bạn làm điều này được xác định.

ID lỗi: 4153515 về sự cố thiết lập thuộc tính này:

Đây không phải là một lỗi. Thuộc tính "file.encoding" không được yêu cầu bởi đặc tả nền tảng J2SE; đó là một chi tiết nội bộ về các triển khai của Sun và không nên bị mã người dùng kiểm tra hoặc sửa đổi. Nó cũng được thiết kế ở chế độ chỉ đọc; Về mặt kỹ thuật, không thể hỗ trợ thiết lập thuộc tính này thành các giá trị tùy ý trên dòng lệnh hoặc bất kỳ lúc nào khác trong quá trình thực thi chương trình.

Cách ưa thích để thay đổi mã hóa mặc định được sử dụng bởi VM và hệ thống thời gian chạy là thay đổi ngôn ngữ của nền tảng cơ bản trước khi bắt đầu chương trình Java của bạn.

Tôi quặn lòng khi thấy mọi người đặt mã hóa trên dòng lệnh - bạn không biết mã sẽ ảnh hưởng gì.

Nếu bạn không muốn sử dụng mã hóa mặc định, hãy đặt mã hóa bạn muốn một cách rõ ràng thông qua phương thức / hàm tạo thích hợp .


4

Đầu tiên, Latin-1 giống với ISO-8859-1, vì vậy, mặc định đã được chấp nhận cho bạn. Đúng?

Bạn đã đặt thành công mã hóa thành ISO-8859-1 với tham số dòng lệnh của mình. Bạn cũng đặt nó theo lập trình thành "Latin-1", nhưng đó không phải là giá trị được công nhận của mã hóa tệp cho Java. Xem http://java.sun.com/javase/6/docs/technotes/guides/intl/encoding.doc.html

Khi bạn làm điều đó, có vẻ như Charset đặt lại thành UTF-8, từ việc xem xét nguồn. Điều đó ít nhất giải thích hầu hết các hành vi.

Tôi không biết tại sao OutputStreamWriter hiển thị ISO8859_1. Nó ủy quyền cho các lớp sun.misc. * Nguồn đóng. Tôi đoán rằng nó không hoàn toàn xử lý mã hóa thông qua cùng một cơ chế, điều này thật kỳ lạ.

Nhưng tất nhiên, bạn phải luôn chỉ rõ ý nghĩa của bạn trong mã này. Tôi không bao giờ dựa vào mặc định của nền tảng.


4

Các hành vi không thực sự là lạ. Nhìn vào việc thực hiện các lớp, nó là do:

  • Charset.defaultCharset() không lưu vào bộ nhớ đệm của bộ ký tự đã xác định trong Java 5.
  • Đặt thuộc tính hệ thống "file.encoding" và gọi Charset.defaultCharset()lại gây ra đánh giá thứ hai của thuộc tính hệ thống, không tìm thấy bộ ký tự có tên "Latin-1", do đó, Charset.defaultCharset()mặc định là "UTF-8".
  • Các OutputStreamWritertuy nhiên là bộ nhớ đệm bộ ký tự mặc định và có lẽ sử dụng đã trong VM khởi, do đó ký tự mặc định chuyển hướng từ Charset.defaultCharset()nếu hệ thống sở hữu "file.encoding" đã được thay đổi khi chạy.

Như đã chỉ ra, không có tài liệu nào về việc VM phải hành xử như thế nào trong tình huống như vậy. Các Charset.defaultCharset()tài liệu API không phải là rất chính xác về cách bộ ký tự mặc định được xác định, chỉ đề cập rằng nó thường được thực hiện trên máy ảo khởi động, dựa trên các yếu tố như thiết lập hệ điều hành mặc định nhân vật hay địa phương mặc định.


3

Tôi đã đặt đối số vm trong máy chủ WAS là -Dfile.encoding = UTF-8 để thay đổi bộ ký tự mặc định của máy chủ.


1

kiểm tra

System.getProperty("sun.jnu.encoding")

nó có vẻ giống với kiểu mã hóa được sử dụng trong dòng lệnh hệ thống của bạn.

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.