xdpsee / yoyoplayer

Automatically exported from code.google.com/p/yoyoplayer
0 stars 0 forks source link

看了一点源代码,我觉得作者对于java字符串理解还是不深刻。 #11

Open GoogleCodeExporter opened 9 years ago

GoogleCodeExporter commented 9 years ago
在一个Util类里面我看到这样一个函数
public static String convertString(String source, String encoding) {
            try {
                byte[] data = source.getBytes("ISO8859-1");
                return new String(data, encoding);
            } catch (UnsupportedEncodingException ex) {
                return source;
            }
}
这个函数的意图似乎叫做转换,但是它的输入是String类型的��
�西,输出也是String
类型的东西,这个过程你是转不了什么的,这是一个没意义��
�操作。
当我们定义String a 
="天下";请问a变量指向的String对象里面存储的二进制形式是
什么?答案必然是对"天下"这两个字符以某种编码方案的编码
后的二进制内容。那么
是什么编码方案呢?正如java宣称的那样是Unicode。没错,所谓
java支持Unicode就
是这个意思。现在我们来看这个函数,传进来一个source,然��
�执行这个
source.getBytes("ISO8859-1")。这个什么意思呢?就是说将source指向�
��String对
象内容以这个ISO8859-1编码方案编码输出,但是请注意source指��
�的String对象本身
的内容还是原来的Unicode的形式,只是输出符合ISO8859-1编码的�
��堆byte。接下来
做了这个return new String(data, 
encoding);这个干什么呢?以某种编码方案对某
堆byte进行解析,解完后构造的String对象内部还是要以Unicode形
式存储的。然后来
说说这个“某种编码方案对某堆byte的解析”如果匹配那自然�
��有问题,如果不匹配那
就可能出现问题。现在从这一句byte[] data = 
source.getBytes("ISO8859-1");来看
这堆byte是ISO8859-1编码方案生成的,那么如果以非ISO8859-1的编�
��方案去解这堆
byte很可能出问题。
所以如果我这样
String test = "安西四镇";
String rs = convertString(test,"GBK");
System.err.println(rs);
rs打印出来是四个????

现在假如我把你的那个函数改成
public static String convertString(String source, String encoding) {
            try {
                byte[] data = source.getBytes("UTF-8");
                return new String(data, encoding);
            } catch (UnsupportedEncodingException ex) {
                return source;
            }
}

String test = "安西四镇";
String rs = convertString(test,"UTF-8");
System.err.println(rs);
这样打印出"安西四镇",没错,但是这是在做无用功。

String test = "安西四镇";
String rs = convertString(test,"GBK");
System.err.println(rs);
这样打印出"瀹夎タ鍥涢晣",乱码。

String对象到String对象的转换时没有意义的,要么瞎折腾,无��
�功,要么反而折腾
的出错了。因为String对象对于字符一直是以Unicode形式存储的�
��它可以按某个编码
方案输出一堆byte,也可以按某个编码方案从一堆byte解析,但
是最终在内存的存储
都是Unicode形式。只有当你要与外围(网络,文件,。。。)�
��交道的时候才会考虑
转码,解码问题,两个String对象之间这不是瞎折腾吗。

Original issue reported on code.google.com by carl...@gmail.com on 4 Aug 2009 at 4:32

GoogleCodeExporter commented 9 years ago
非常高兴能看到你这样的,对于源代码读得如此仔细的朋友��
�我当时写这个Util方法是为了读MP3
文件的标签,因为在读取MP3标签的时候,有可能是以ISO的格��
�读出来的,所以可能这个时候,
这个字符串就是不可读的,这个时候我会尝试再转换一下。
这个方法只在
com.hadeslee.yoyoplayer.tag.MpegInfo里面的

 private String getChineseString(String source){
        String temp=Util.convertString(source);
        if(temp.indexOf("??")==-1&&temp.indexOf("�")==-1){
            return temp;
        }else{
            return source;
        }
    }
这个方法用的,主要是判断是不是把乱码给读出来的,如果��
�的话,就尝试再转码一次,可能这
样考虑的并不周到,但是也是没有办法的办法。
当然,一个字符串,如果以特定的构造在内存里面造出来了��
�并且是可读的话,那就根本不需要
转码的,需要转码的前提是这个String对象可能是以错误的byte[
]的组织形式组织起来的,这个
时候,就需要再对它进行一次转码了。
再次感谢你的意见。

Original comment by hadeslee...@gmail.com on 5 Aug 2009 at 2:06

GoogleCodeExporter commented 9 years ago
如果你的那个字符串 source 
本身已经解错了,那是无法通过对source的再操作把它捣鼓好��
�。
我们来看个实验:
String test = "安西四镇";
 try {
    byte[] data = test.getBytes("GBK");
    String p = new String(data,"UTF-8");
    System.err.println(p);
    byte[] data2 = p.getBytes("GBK");
    String p2 = new String(data2,"GBK");
    System.err.println(p2);
} catch (UnsupportedEncodingException e) {
    e.printStackTrace();
}
先构建了一个字符串"安西四镇",然后按照GBK编码输出一堆byt
e,接下来我故意以不匹配的编码
方案UTF-8去解这堆byte,得到一个p,然后打印p看看是什么,很
明显是乱码。此处的p就相当于
你的一个已经解错的source,接下我们尝试“转”它,我们将p�
��GBK输出一堆byte,然后再以GBK解
之,得到p2,打印P2看看是什么,很遗憾还是乱码。所以如果p
已经错了,后面你再对p做什么也
已经没用了,所以首先要保证p是以正确的编码方案解析了最�
��始的那堆byte,如果最开始的那堆
byte在某个外部文件,而其编码方案又不清楚,那确实比较麻�
��,即使要尝试多次解析,也是针
对最开始的那堆byte,而不是一个已经以某种不匹配编码方案�
��错的String。因为String解析某
堆byte后,它不是保存那堆byte的,而是翻成Unicode编码后再存��
�的。
假设一个字“天”在某一种编码方案中编成的二进制形式是��
�00110011”(只是假设),然后在
String的Unicode编码方案来看“天”假设应该是“11001100”,现��
�构造String并传正确的编码方案
解析这个“天",这时String解析当然能正确识别这个“00110011”�
��”天“,但是String不会内部存这
个“00110011”,而是去存”天“对应的Unicode编码,我这里假��
�是“11001100”。现在假设构造
String时传的编码方案不匹配,这时String解析对“00110011”也就
不理解成”天“了,至于到底理
解成什么就取决于所使用的编码方案,很可能是乱七八糟的��
�符假设是”瀹“,然后String会去保
存”瀹“对应的Unicode编码。这时候你再对这个String做什么操�
��也没用,因为这个String也就保
存了”瀹“对应的Unicode编码而已。

Original comment by carl...@gmail.com on 6 Aug 2009 at 6:09

GoogleCodeExporter commented 9 years ago
我可以写一个例子给你看一下。附件里面就是一段中文,用GB
K编码保存的“中华人民共和国”这七
个字,一共是14个字节。

运行测试的代码如下:

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;

/**
 *
 * @author binfeng.li
 */
public class Test4 {

    public static void main(String[] args) throws Exception {
        //以二制的方法把文件的字节数组读进来
        byte[] data = readBytes();
        //然后再以ISO的编码格式构造一个字符串,这个字符串肯定是乱码了
        String error = new String(data, "ISO-8859-1");
        System.out.println("errorString:" + error);
        //然后再尝试把这个字符串携带的byte[]读出来
        byte[] b1 = error.getBytes("ISO-8859-1");
        //再尝试用GBK的编码看一看
        String gbkString = new String(b1, "GBK");
        System.out.println("gbkString:" + gbkString);
    }

    private static byte[] readBytes() throws Exception {
        FileInputStream fin = new FileInputStream("D:/Test.txt");
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        byte[] buffer = new byte[8192];
        int length = -1;
        while ((length = fin.read(buffer)) != -1) {
            bout.write(buffer, 0, length);
        }
        return bout.toByteArray();
    }
}

***注意****:
Test.txt这个文件放在D盘的根目录下,然后再运行代码。

我们一开始是纯二进制的方法把它读入内存,保存成一个byte[
],这个时候,用ISO8859-1的编码
去把这一堆byte[]构造成一个String对象,这个时候,这个String��
�象肯定是不可读的。然后为
了让它可读,我尝试用这个String对象再次得到它的byte[],然��
�再用这个byte[]数组用GBK的编
码去构造一个String对象。这个时候,这个String对象就是可读��
�的。

com.hadeslee.yoyoplayer.tag.MpegInfo.getChineseString的方法它所能按受的
参数只是一个
String而已,这个String对象是第三方库提供的,因为我只实现��
�ID3v1和APEv2的标签实
现,ID3v2的标签是第三方实现的,它可能是以iso8859-1的编码去
读取MP3标签里面的那个字节
的,所以当里面有中文的时候,ISO的编码读出来的String肯定��
�乱码了,我这个时候再用上述的
方法折腾一下,就“可能”把原来读错的内容读正确成中文��
�。

这个方法是我后来加的,你可以运行YOYOPlayer看一下,把这个�
��法注释掉,或者直接在里面返
回原来的字符串本身,然后找一首MP3文件,用ID3V2的编码指定
标签,然后再在设置里面把ID3V2
的标签读取顺序排在最前面试试看,会不会有乱码的情况。

我在附件里面附上一首我在千千静听下用ID3V2标签注释的一首
歌,你先把
com.hadeslee.yoyoplayer.tag.MpegInfo.getChineseString这个方法的实现改成
直接返回传进来
的那个参数,打开这个MP3文件看看,会不会有乱码的情况发��
�。然后再用原来的实现再次打开这
首歌试试看。

再次感谢你的讨论:)

Original comment by hadeslee...@gmail.com on 6 Aug 2009 at 9:32

Attachments:

GoogleCodeExporter commented 9 years ago
或者把你上面的例子改成如下,也是可以的

  String test = "安西四镇";
        try {
            byte[] data = test.getBytes("GBK");
            String p = new String(data, "ISO-8859-1");
            System.err.println(p);
            byte[] data2 = p.getBytes("ISO-8859-1");
            String p2 = new String(data2, "GBK");
            System.err.println(p2);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }

先是用ISO的编码构造错的一个字符串,然后再用ISO的编码把��
�始的byte[]尝试还原回来,这个
时候再用GBK就可以得到对的对象了。也就是说弄过去的编码��
�要弄回来的编码一样,比如尝试用
ISO去生成一个字符串,发现不对,再尝试用ISO得到byte[],这��
�byte[]就是原来的那个了,然
后可以再做别的变换,比如再用Big5尝试把byte[]生成字符串,�
��果不行,那就再用Big5把字符
串再转成原始的byte[],这个byte[]一直都是原始的,没有被改��
�的。

但是我用UTF-8尝试转过去,再转回来以后,就发现转不回来了
。

Original comment by hadeslee...@gmail.com on 6 Aug 2009 at 9:40

GoogleCodeExporter commented 9 years ago
关于java字符串的机制我说的还是对的,byte[]--以某种编码方��
�解析后-->char[](unicode的形式)---以某种
编码方案解析出-->byte[]
但是我关于:“如果你的那个字符串 source 
本身已经解错了,那是无法通过对source的再操作把它捣鼓好��
�”这个
结论的确是错了。拿我上面假设的“天”字那个情况来说,��
�管编码方案不匹配时,解析“00110011”不理解成”天
“了,而是假设理解成”瀹“,然后存了”瀹“对应的Unicode�
��码。然后当你以这个编码方案getBytes时又将”瀹
“对应的Unicode编码转换成”瀹“的这个编码方案的编码,即0
0110011,然后再以一个匹配的编码方案来解这个
00110011,自然又能正确理解成“天”,当然字符串内部还是存
了“天”对应的Unicode编码.所以始终是符合这个机
制:byte[]--以某种编码方案解析后-->char[](字符串内部,unicode的��
�式)---以某种编码方案解析出-->byte
[]

Original comment by carl...@gmail.com on 6 Aug 2009 at 4:00

GoogleCodeExporter commented 9 years ago
既然读出来的就是byte[] 了,为什么要用String转来转去呢?
直接用读出来的byte[] 
尝试用不同的方式解码不就可以了吗。这样也就不会存在UTF-8
转不回去
带来的问题了。

Original comment by tianzho...@gmail.com on 19 Aug 2009 at 4:58

GoogleCodeExporter commented 9 years ago
差不多还是可以的,对于字符串得到byte[]后用不同的编码解��
�,如果没有??或�的话大概就是正确的,否则
还用原来的字符串,这样就没有乱码了。

Original comment by hwxi...@gmail.com on 7 Jan 2010 at 9:45

GoogleCodeExporter commented 9 years ago
这个问题是有的,在j2ee开发当中就有这个问题存在,本身一�
��字符串的编码是GB2312,但是使用了错误的编码,结果字符串
当中的字符就是乱码,然后就可以使用错误的编码解码成byte[
],然后使用正确的编码就可以正确读出来。

Original comment by scn...@gmail.com on 27 Sep 2012 at 1:20