
一见未必钟情
这是第一次遇到 .rtf 格式文件乱码问题,之前也处理过类似的问题:从 .rtf 文件中读取字符串,但一直都没有遇到乱码直到今天。
原始文件内容如下:(在 Windows 下用 Word 和 写字板就可以打开)
大小便失禁及心悸、胸闷、发热,亦无头痛,视物旋转,复视,黑曚,意识障碍等症。
原始文件对应的 .rtf 格式字符串序列(用记事本直接打开就可以查看):
{\rtf1\ansi\ansicpg936\deff0\nouicompat\deflang1033\deflangfe2052{\fonttbl{\f0\fnil\fcharset134 \’cb\’ce\’cc\’e5;}}
{*\generator Riched20 6.3.9600}\viewkind4\uc1
\pard\nowidctlpar\qj\kerning2\f0\fs24\’b4\’f3\’d0\’a1\’b1\’e3\’ca\’a7\’bd\’fb\’bc\’b0\’d0\’c4\’bc\’c2\’a1\’a2\’d0\’d8\’c3\’c6\’a1\’a2\’b7\’a2\’c8\’c8\’a3\’ac\’d2\’e0\’ce\’de\’cd\’b7\’cd\’b4\’a3\’ac\’ca\’d3\’ce\’ef\’d0\’fd\’d7\’aa\’a3\’ac\’b8\’b4\’ca\’d3\’a3\’ac\’ba\’da\’95\’e4\’a3\’ac\’d2\’e2\’ca\’b6\’d5\’cf\’b0\’ad\’b5\’c8\’d6\’a2\’a1\’a3\par
}
一般 Java 从 .rtf 文件读取字符串的处理方法如下:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
 100
 101
 102
 103
 104
 105
 106
 107
 108
 109
 
 | import java.io.ByteArrayInputStream;import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 
 import javax.swing.text.BadLocationException;
 import javax.swing.text.DefaultStyledDocument;
 import javax.swing.text.rtf.RTFEditorKit;
 
 public class TestRtf
 {
 
 
 * 从 .rtf 文件中读取字符串。
 *
 * @param rtfFile
 *            文件
 *
 * @return 获得的字符串
 *
 * @see {@link #getTextFromRtfFile(File, String)} outputCharset="GB2312"
 */
 public static String readFromRtfFile( File rtfFile )
 {
 return readFromRtfFile( rtfFile, "GB2312" );
 }
 
 
 * 从 .rtf 文件中读取字符串。
 *
 * @param rtfFile
 *            带读取的文件
 * @param outputCharset
 *            输出字符串的编码
 *
 * @return 读取到的字符串
 */
 public static String readFromRtfFile( File rtfFile, String outputCharset )
 {
 String result = null;
 try
 {
 DefaultStyledDocument styledDoc = new DefaultStyledDocument();
 InputStream is = new FileInputStream( rtfFile );
 new RTFEditorKit().read( is, styledDoc, 0 );
 result = new String( styledDoc.getText( 0, styledDoc.getLength() ).getBytes( "ISO8859_1" ), outputCharset );
 
 }
 catch( IOException e )
 {
 e.printStackTrace();
 }
 catch( BadLocationException e )
 {
 e.printStackTrace();
 }
 return result;
 }
 
 
 * 从 Rtf 格式的字符序列中读取字符串。
 *
 * @param rftString
 *            Rtf 格式的字符序列
 * @param outputCharset
 *            输出字符串的编码
 *
 * @return 输出的字符串
 */
 public static String readFromRtfString( String rftString, String outputCharset )
 {
 String result = null;
 try
 {
 DefaultStyledDocument styledDoc = new DefaultStyledDocument();
 InputStream inStream = new ByteArrayInputStream( rftString.getBytes() );
 
 new RTFEditorKit().read( inStream, styledDoc, 0 );
 
 result = new String( styledDoc.getText( 0, styledDoc.getLength() ).getBytes( "ISO8859_1" ), outputCharset );
 }
 catch( IOException e )
 {
 e.printStackTrace();
 }
 catch( BadLocationException e )
 {
 e.printStackTrace();
 }
 return result;
 }
 
 
 * 从 Rtf 格式的字符序列中读取字符串。
 *
 * @param rftString
 *            Rtf 格式的字符序列
 *
 * @return 输出的字符串
 *
 * @see {@link #readFromRtfString(String, String)}
 */
 public static String readFromRtfString( String rftString )
 {
 return readFromRtfString( rftString, "GB2312" );
 }
 
 }
 
 | 
然后加上 main 方法进行测试:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 
 | public static void main( String[] args ){
 String rtfString = "{\\rtf1\\ansi\\ansicpg936\\deff0\\nouicompat\\deflang1033\\deflangfe2052{\\fonttbl{\\f0\\fnil\\fcharset134 \\'cb\\'ce\\'cc\\'e5;}}"
 + "{\\*\\generator Riched20 6.3.9600}\\viewkind4\\uc1 "
 + "\\pard\\nowidctlpar\\qj\\kerning2\\f0\\fs24\\'b4\\'f3\\'d0\\'a1\\'b1\\'e3\\'ca\\'a7\\'bd\\'fb\\'bc\\'b0\\'d0\\'c4\\'bc\\'c2\\'a1\\'a2\\'d0\\'d8\\'c3\\'c6\\'a1\\'a2\\'b7\\'a2\\'c8\\'c8\\'a3\\'ac\\'d2\\'e0\\'ce\\'de\\'cd\\'b7\\'cd\\'b4\\'a3\\'ac\\'ca\\'d3\\'ce\\'ef\\'d0\\'fd\\'d7\\'aa\\'a3\\'ac\\'b8\\'b4\\'ca\\'d3\\'a3\\'ac\\'ba\\'da\\'95\\'e4\\'a3\\'ac\\'d2\\'e2\\'ca\\'b6\\'d5\\'cf\\'b0\\'ad\\'b5\\'c8\\'d6\\'a2\\'a1\\'a3\\par"
 + "}";
 
 System.out.println( readFromRtfString( rtfString ) );
 System.out.println( readFromRtfString( rtfString, "GBK" ) );
 System.out.println( "===============================================" );
 System.out.println( readFromRtfFile( new File( "C:\\Users\\Leo\\Desktop\\1.rtf" ) ) );
 System.out.println( readFromRtfFile( new File( "C:\\Users\\Leo\\Desktop\\1.rtf" ), "GBK" ) );
 }
 
 
 
 
 
 
 
 
 
 
 
 
 | 
哎呀,天呐,乱码出现了!My God!
山重水复疑无路
细心的你也许看到了方法中的注释,读取中文的时候要使用 ISO8859_1 编码读取,实验证明确实如此(哎呀,原来是中文大神!)。输出编码也许你会说我只用了 GBK、GB2312 编码,还没有使用 UTF-8 编码,那么我告诉你 UTF-8 更惨(惨目忍睹啊!)。
比较原始文件和输出日志,你应该发现了,原来是“曚”这个字捣的鬼。思路有了,查看下“曚”的 GBK 编码,发现编码为 95E4 (为啥不查 GB2312,额 (⊙o⊙)… 因为 GB2312 编码中不包含这个字,^O^ 。),那我们只要根据 rtf 文件编码格式来核查下“曚”这个字的编码(嘿嘿 ~~~),就知道问题出现在哪儿。在【原始文件对应的 .rtf 格式字符串序列】中确定了这个字的编码为 \'95\'e4,尼玛,这不就是 GBK 编码格式吗,为啥还乱码。
凌乱中。。。。。。
柳暗花明又一村
凌乱了 n 久,已经接近要放弃治疗了的那一刻,我突然想到了我们是以 ISO8859_1 编码来读取的,以 GBK 或 GB2312 编码输出的。那是不是在以 ISO8859_1 读取的过程中出现了问题?
敢想敢干才是真本事,我们将“曚”的 16 进制编码 \'95\'e4 中的 95 转换成十进制数,转换后是 149 。到维基百科 ISO_8859-1 上面一查,发现了为【未建立】(英文为:nicht belegt)。啊哈哈,问题就在这里了。那对于未建立的编码,str.getBytes( "ISO8859_1" ) 会如何处理呢,答案是忽略掉,对,直接忽略掉 \'95 , 下面我们验证下,怎么验证,只需要修改下 readFromRtfString( String rftString, String outputCharset ) 方法即可:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 
 | * 从 Rtf 格式的字符序列中读取字符串(输出中间态)。
 *
 * @param rftString
 *            Rtf 格式的字符序列
 * @param outputCharset
 *            输出字符串的编码
 *
 * @return 输出的字符串
 */
 public static String readFromRtfString( String rftString, String outputCharset )
 {
 String result = null;
 try
 {
 DefaultStyledDocument styledDoc = new DefaultStyledDocument();
 InputStream inStream = new ByteArrayInputStream( rftString.getBytes() );
 
 new RTFEditorKit().read( inStream, styledDoc, 0 );
 
 try
 {
 for( byte b : styledDoc.getText( 0, styledDoc.getLength() ).getBytes( "ISO8859_1" ) )
 {
 System.out.printf( "\\\'%x", b );
 
 }
 }
 catch( UnsupportedEncodingException e )
 {
 e.printStackTrace();
 }
 
 System.out.println();
 
 result = new String( styledDoc.getText( 0, styledDoc.getLength() ).getBytes( "ISO8859_1" ), outputCharset );
 }
 catch( IOException e )
 {
 e.printStackTrace();
 }
 catch( BadLocationException e )
 {
 e.printStackTrace();
 }
 return result;
 }
 
 
 public static void main( String[] args )
 {
 String rtfString = "{\\rtf1\\ansi\\ansicpg936\\deff0\\nouicompat\\deflang1033\\deflangfe2052{\\fonttbl{\\f0\\fnil\\fcharset134 \\'cb\\'ce\\'cc\\'e5;}}"
 + "{\\*\\generator Riched20 6.3.9600}\\viewkind4\\uc1 "
 + "\\pard\\nowidctlpar\\qj\\kerning2\\f0\\fs24\\'b4\\'f3\\'d0\\'a1\\'b1\\'e3\\'ca\\'a7\\'bd\\'fb\\'bc\\'b0\\'d0\\'c4\\'bc\\'c2\\'a1\\'a2\\'d0\\'d8\\'c3\\'c6\\'a1\\'a2\\'b7\\'a2\\'c8\\'c8\\'a3\\'ac\\'d2\\'e0\\'ce\\'de\\'cd\\'b7\\'cd\\'b4\\'a3\\'ac\\'ca\\'d3\\'ce\\'ef\\'d0\\'fd\\'d7\\'aa\\'a3\\'ac\\'b8\\'b4\\'ca\\'d3\\'a3\\'ac\\'ba\\'da\\'95\\'e4\\'a3\\'ac\\'d2\\'e2\\'ca\\'b6\\'d5\\'cf\\'b0\\'ad\\'b5\\'c8\\'d6\\'a2\\'a1\\'a3\\par"
 + "}";
 
 System.out.println( rtfString );
 readFromRtfString( rtfString, "GB2312" );
 
 }
 
 
 
 
 
 
 
 
 
 
 
 
 | 
可以在输出中清楚的看到 \'95 被直接被忽略掉了,这也难怪后面持续显示乱码了。
究极还是纠结
怎么处理呢?
方法一:替换生僻字为常用字,比如将“曚”替换成“蒙”,对应的编码替换为将 \'95\'e4 替换成 \'c3\'c9。
缺点:太明显了,那么多生僻字怎么替换的完。
方法二:自己解析 .rtf 格式的字符序列,然后存为 byte 数组。然后用 GBK 编码解析。
缺点:可能要多花一点时间。