上一篇:
【Java 基礎專題】編碼與亂碼(03)----String的toCharArray()方法測試 - package example.encoding;
- import java.io.BufferedWriter;
- import java.io.File;
- import java.io.FileNotFoundException;
- import java.io.FileOutputStream;
- import java.io.FileWriter;
- import java.io.IOException;
- import java.io.OutputStreamWriter;
- import java.io.PrintWriter;
- import java.io.UnsupportedEncodingException;
- import java.io.Writer;
- import java.nio.charset.Charset;
- import java.util.Iterator;
- import java.util.Set;
- import java.util.SortedMap;
- /** *//**
- * <pre>
- * The Class IOEncodeTest is a tester class for java encoding. Mainnaly contains
- * two parts:
- * 1.Test written by FileWriter, with or without given character encoding value
- * 2.Test written by OutputStreamWriter, with or without given character encoding value
- * </pre>
- *
- * @author Paul Lin
- * @version 1.0
- */
- public class OutputEncodingTest {
- private static String word = "Hello world! 中國";
- private static final String ENCODING_EN = "ISO-8859-1";
- private static final String ENCODING_CN = "GB2312";
- private static final String ENCODING_UTF = "UTF-8";
- private static final String DEFAULT_SYSTEM_ENCODING = System
- .getProperty("file.encoding");
- /** *//**
- * The main method.
- *
- * @param args the arguments
- */
- public static void main(String args[]) {
- OutputEncodingTest tester = new OutputEncodingTest();
- tester.testFileWriter();
- tester.testOutputStreamWriter();
- }
- /** *//**
- * Test file writer.
- */
- public void testFileWriter() {
- // Create test result folder
- String resultFolder = createResultFolder(System
- .getProperty("user.language"), getBasePath());
- // With default platform encoding
- writeByFileWriter(word, resultFolder);
- // With given system file.encoding property
- writeByFileWriter(word, ENCODING_EN, resultFolder);
- writeByFileWriter(word, ENCODING_CN, resultFolder);
- writeByFileWriter(word, ENCODING_UTF, resultFolder);
- }
- /** *//**
- * Test output stream writer.
- */
- public void testOutputStreamWriter() {
- // Create test result folder
- String resultFolder = createResultFolder(System
- .getProperty("user.language"), getBasePath());
- // With default platform encoding
- writeByOutputStreamWriter(word, resultFolder);
- // With given system file.encoding property
- writeByOutputStreamWriter(word, ENCODING_EN, resultFolder);
- writeByOutputStreamWriter(word, ENCODING_CN, resultFolder);
- writeByOutputStreamWriter(word, ENCODING_UTF, resultFolder);
- }
- /** *//**
- * Prints the available charset.
- */
- public void printAvailableCharset() {
- SortedMap<String, Charset> charsets = Charset.availableCharsets();
- Set<String> charsetKeys = charsets.keySet();
- System.out.println("\n<<<< Canonical name -- Display name -- "
- + " Can encode >>>>\n");
- Iterator<String> i = charsetKeys.iterator();
- while (i.hasNext()) {
- String key = (String) i.next();
- Charset charset = (Charset) charsets.get(key);
- String displayName = charset.displayName();
- boolean canEncode = charset.canEncode();
- System.out.println(key + " - " + displayName + " - " + canEncode);
- }
- }
- /** *//**
- * Write by file writer.
- *
- * @param content the content
- */
- private void writeByFileWriter(String content, String destination) {
- String defaultEncoding = System.getProperty("file.encoding");
- System.out.println("Using default system encoding: " + defaultEncoding);
- writeByFileWriter(content, defaultEncoding, destination);
- }
- /** *//**
- * Write by file writer.
- *
- * @param content the content
- * @param encoding the encoding
- */
- private void writeByFileWriter(String content, String encoding,
- String destination) {
- printDebugInformation("FileWriter", encoding, content);
- // Get system default encoding
- String defaultEncoding = System.getProperty("file.encoding");
- // Reset underlying platform character encoding
- if (!defaultEncoding.equalsIgnoreCase(encoding)) {
- System.setProperty("file.encoding", encoding);
- }
- // Save as file with given encoding value
- String file = returnFileName(destination, "write_by_filewriter_",
- encoding, ".txt");
- try {
- Writer writer = new BufferedWriter(new FileWriter(file));
- writer.write(content);
- writer.flush();
- writer.close();
- } catch (IOException ioe) {
- ioe.printStackTrace();
- }
- // Reset character encoding to system default value
- resetDefaultSystemEncoding();
- }
- /** *//**
- * Write by output stream writer.
- *
- * @param content the content
- */
- private void writeByOutputStreamWriter(String content, String destination) {
- String defaultEncoding = System.getProperty("file.encoding");
- System.out.println("Using default system encoding: " + defaultEncoding);
- writeByOutputStreamWriter(content, defaultEncoding, destination);
- }
- /** *//**
- * Write by output stream writer.
- *
- * @param content the content
- * @param encoding the encoding
- */
- private void writeByOutputStreamWriter(String content, String encoding,
- String destination) {
- printDebugInformation("OutputStreamWriter", encoding, content);
- // Save as file with given encoding value
- String file = returnFileName(destination,
- "write_by_outputStreamWriter_", encoding, ".txt");
- try {
- Writer writer = new PrintWriter(
- new BufferedWriter(new OutputStreamWriter(
- new FileOutputStream(file), encoding)));
- writer.write(content);
- writer.flush();
- writer.close();
- } catch (FileNotFoundException fnfe) {
- fnfe.printStackTrace();
- } catch (UnsupportedEncodingException uee) {
- uee.printStackTrace();
- } catch (IOException ioe) {
- ioe.printStackTrace();
- }
- // Reset character encoding to system default value
- resetDefaultSystemEncoding();
- }
- /** *//**
- * Gets the base path.
- *
- * @return the base path
- */
- private String getBasePath() {
- StringBuffer finalPath = new StringBuffer();
- String dir = System.getProperty("user.dir");
- finalPath.append(dir);
- finalPath.append((dir.endsWith("\\") || dir.endsWith("/")) ? "" : "/");
- finalPath.append("src").append("/");
- finalPath.append("example").append("/");
- finalPath.append("encoding").append("/");
- return finalPath.toString();
- }
- /** *//**
- * Return file name.
- *
- * @param basePath the base path
- * @param prefix the prefix
- * @param content the content
- * @param subfix the subfix
- *
- * @return the string
- */
- private String returnFileName(String basePath, String prefix,
- String content, String subfix) {
- StringBuffer name = new StringBuffer(basePath);
- if ((!basePath.endsWith("\\") && (!basePath.endsWith("/")))) {
- name.append("/");
- }
- name.append(prefix);
- name.append(content);
- name.append(subfix);
- return name.toString();
- }
- /** *//**
- * Creates the result folder.
- *
- * @param platform the platform
- * @param fullPath the full path
- *
- * @return the string
- */
- private String createResultFolder(String platform, String fullPath) {
- StringBuffer resultFolder = new StringBuffer();
- if (fullPath.endsWith("\\") || fullPath.endsWith("/")) {
- resultFolder.append(fullPath);
- } else {
- resultFolder.append(fullPath).append("/");
- }
- resultFolder.append("Test_Result_Of_").append(platform);
- File file = new File(resultFolder.toString());
- if (!file.exists()) {
- file = new File(resultFolder.toString());
- file.mkdir();
- return resultFolder.toString();
- } else {
- return file.getAbsolutePath();
- }
- }
- /** *//**
- * Prints the debug information.
- *
- * @param writerName the writer name
- * @param encoding the encoding
- */
- private void printDebugInformation(String writerName, String encoding,
- String content) {
- StringBuffer msg = new StringBuffer();
- msg.append("\n<<<<----------------------------------");
- msg.append(" Test written by ").append(writerName);
- msg.append(" with encoding ").append(encoding);
- msg.append(" ---------------------------------->>>>\n");
- msg.append(" \nOriginal string: ").append(content).append("\n");
- System.out.println(msg.toString());
- }
- /** *//**
- * Reset default system encoding.
- */
- private void resetDefaultSystemEncoding() {
- System.setProperty("file.encoding", DEFAULT_SYSTEM_ENCODING);
- }
- }
復制代碼 【1】中文平臺情況下,測試結(jié)果如下:1.如果采用FileWriter,并指定GBK編碼:編碼后字符長度為15,可以正常保存和讀取
2.如果采用FileWriter,并指定UTF-8編碼:編碼后字節(jié)長度為16,可以正常保存和讀取
3.如果采用FileWriter,并指定ISO8859-1編碼:編碼后字節(jié)長度為17,可以正常保存和讀取
4.如果采用OutputStreamWriter,并指定GBK編碼:編碼后字符長度為15,可以正常保存和讀取
5.如果采用OutputStreamWriter,并指定UTF-8編碼:編碼后字節(jié)長度為16,可以正常保存和讀取
6.如果采用OutputStreamWriter,并指定ISO-8859-1編碼:編碼后字節(jié)長度為17,變成?
【2】英文平臺情況下,測試結(jié)果如下:1.如果采用FileWriter,并指定GBK編碼:編碼后字符長度為15,變成?
2.如果采用FileWriter,并指定UTF-8編碼:編碼后字節(jié)長度為16,變成?
3.如果采用FileWriter,并指定ISO-8859-1編碼:編碼后字節(jié)長度為17,變成?
4.如果采用OutputStreamWriter,并指定GBK編碼:編碼后字符長度為15,可以正常保存和讀取
5.如果采用OutputStreamWriter,并指定UTF-8編碼:編碼后字節(jié)長度為16,可以正常保存和讀取
6.如果采用OutputStreamWriter,并指定ISO-8859-1編碼:編碼后字節(jié)長度為17,變成?
【結(jié)論】①在中文平臺下,如果使用FileWriter,不論你如何設置字符集都不會起作用。因為它采用的是默認的系統(tǒng)字符集。即便你設置了 System.setProperty("file.encoding", "ISO-8859-1"),或者在運行時給予參數(shù)-Dfile.encoding=UTF-8都不會起作用。你會發(fā)現(xiàn)它最終還是都已"GB2312"或者"GBK"的方式保存。
在中文平臺下,如果使用OutputStreamWriter,則在后臺寫入時會把字符流轉(zhuǎn)換成字節(jié)流,此時指定的編碼字符集就起作用了??梢钥吹皆谥付?GBK、UTF-8的情況下中文可以正常的保存和讀取,同時文件按照我們給定的方式保存了。而對于ISO-8859-1則變成了?,這再次證明了采用 ISO-8859-1是不能保存中文的,而且會因為中文編碼在ISO-8859-1的編碼中找不到對應的字符而默認轉(zhuǎn)換成?。
②在英文平臺下,如果使用FileWriter,不論你如何設置字符集同樣都不會起作用。所有的文件都將按照ISO-8859-1的編碼方式保存,毫無疑問地變成了?。在英文平臺下,如果使用OutputStreamWriter,則只有當我們把字符和文件的編碼方式正確設置為GBK、UTF-8的情況下,中文才能正確的保存并顯示。
③通過上述的實驗證明,為了確保在不同的平臺下,客戶端輸入的中文可以被正確地解析、保存、讀取。最好的辦法就是使用 OutputStreamWriter配合UTF-8編碼。
如果不想使用UTF-8編碼,那么可以考慮使用GB2312,不建議使用GBK、GB18030。因為對于某些老式的文本編輯器,甚至不支持GBK、 GB18030的編碼,但是對于GB2312則是一定支持的。因為前兩者都不是國標但后者是。
④關于String的getBytes(),getBytes(encoding)和new String(bytes, encoding)這三個方法,非常值得注意:
A.getBytes():使用平臺默認的編碼方式(通過file.encoding屬性獲取)方式來將字符串轉(zhuǎn)換成byte[]。得到的是字符串最原始的字節(jié)編碼值。
B.getBytes(NAME_OF_CHARSET):使用指定的編碼方式將字符串轉(zhuǎn)換成byte[],如果想要得到正確的字節(jié)數(shù)組,程序員必須給出正確的NAME_OF_CHARSET。否則得到的就不會得到正確的結(jié)果。
C.new String(bytes, encoding):如果我們在客戶端使用UTF-8編碼的JSP頁面發(fā)出請求,瀏覽器編碼后的UTF-8字節(jié)會以ISO-8859-1的形式傳遞到服務器端。所以要得到經(jīng)HTTP協(xié)議傳輸?shù)脑甲止?jié),我們需要先調(diào)用getBytes("ISO-8859-1")得到原始的字節(jié),但由于我們客戶端的原始編碼是UTF-8,如果繼續(xù)按照ISO-8859-1解碼,那么得到的將不是一個中文字符,而是3個亂碼的字符。所以我們需要再次調(diào)用new String(bytes,"UTF-8"),將字節(jié)數(shù)組按照UTF-8的格式,每3個一組進行解碼,才能還原為客戶端的原始字符。
D.String的getBytes()、getBytes(NAME_OF_CHARSET)方法都是比較微妙的方法,原則上:傳輸時采用的是什么編碼,我們就需要按照這種編碼得到字節(jié)。new String(bytes, NAME_OF_CHARSET)則更加需要小心,原則上:客戶端采用的是什么編碼,那么這里的NAME_OF_CHARSET就必須和客戶端保持一致。
例如JSP頁面是GBK,那么我們接收頁面?zhèn)鬟f而來的參數(shù)時就必須使用new String(parameter.getBytes("ISO-8859-1"), "GBK");如果使用了錯誤的解碼方式,如使用了UTF-8,那么得到的很有可能就是亂碼了。
也就是說:GBK--->ISO-8859-1--->GBK、UTF-8--->ISO-8859-1--->UTF- 8的轉(zhuǎn)換過程是沒有問題的。但是GBK--->ISO-8859-1--->UTF-8、UTF-8--->ISO- 8859-1--->GBK的字節(jié)直接轉(zhuǎn)碼則可能導致亂碼,需要另外的轉(zhuǎn)換過程。
記?。?/strong>
謹慎地使用getBytes(NAME_OF_CHARSET)和new String(bytes, NAME_OF_CHARSET),除非你很清楚的知道原始的字符編碼和傳輸協(xié)議使用的編碼。
推薦使用基于服務器的配置、過濾器設置request/response的characterEncoding、content type屬性。還有就是JSP頁面的pageEncoding屬性、HTML meta元素的content type屬性。盡量避免頻繁的在代碼中進行字符串轉(zhuǎn)碼,即降低了效率又增加了風險。(文/Paul Lin)
下一篇:【Java 基礎專題】編碼與亂碼(05)---GBK與UTF-8之間的轉(zhuǎn)換