Java String部分内容详解

时间:2023-02-16 10:37:05

   在我们的不论是应用还是面试中这个String的详细概念总是一道绕不过去的坎,在这里我想就String的这个类和对象进行比较详细的归纳和整理,以期望在今后的工作或者是面试用得到的时候能够少走点弯路。

关于String的存储形式以及相关问题

   我们都知道String的存储是通过一个对象的形式存储的,我们声明String的时候存在一个String对象的引用以及引用地址的数据组成的。

   并且由于Java的设计者认为共享静态字符串带来的高校路远远胜过于提取并借字符串带来的低效率,所以我们的String变量的数据是存储于静态变量区,这个东西造成了我们后续很多问题的发生。

String引用的传递

   由于我们之前说明的String的引用指向的是我们在静态变量区内部的静态字符串内容,以及我们修改String对象str的内容的时候其实并不是修改了str所指向的静态区的内存内容,而是将str的指向进行了修改,所以造成了这个差异,即String的引用str的展现形式将和我们的基本变量int long类似

   具体的例子如下:

public class StringTest {

@Test
public void test001(){
String str = "123";
testFcuntion(str);
System.out.println("str == "+str);
}
private void testFcuntion(String str){
str = "456";
}
}

String引用的指向

   之前我们讲到了String是引用指向的是静态区里面的内存地址,而且内容一样的String指向的就是同一个内存地址,那么我们可不可以这么认为,即我们的两个String如果内容一致那么引用的地址也是一致的,那么我们就可以使用== 来进行相同的判断。

   但是事实上我们还是使用equals进行字符串内容的判断,这个是为什么呢,因为在有些时候String对象的指针并不会像期望的那样指向同一个地址内容。

@Test
public void StringTest() {
String a = "1111";
String b = "1111";
String d = new String("1111");

System.out.println(" a == b = " + (a == b) + " equals " + a.equals(b));

System.out.println(" a == d = "+(a==d)+" equals "+a.equals(d));

String c = "11";

System.out.println(" a == (c+c) = " + (a == (c + c)) + " equals " + a.equals(c + c));

StringBuffer sb = new StringBuffer("11");
sb.append("11");

System.out.println(" a == sb = " + (a == sb.toString()) + " equals " + a.equals(sb.toString()));

StringBuilder sbd = new StringBuilder("11");
sbd.append("11");
String e = sbd.toString();

System.out.println(" a == sbd = " + (a == sbd.toString()) + " equals " + a.equals(sbd.toString()));

System.out.println(" a == e = "+(a==e)+" equals "+a.equals(e));
}

结果如下

 a == b = true  equals  true

 a == d = false  equals  true

 a == (c+c) = false  equals  true

 a == sb = false  equals  true

 a == sbd = false  equals  true

 a == e = false  equals  true

   结果是大大的出乎我们的意料的,不是说好的都指向同一个静态变量1111”吗,和说好的不一样啊!

   这个结果我们可以明确的看到这个情况,其实指向同一个静态变量指的是在java代码中以String str =“1111”这种形式声明的对象,而其他的对象事实上并不会共享同一个内存块的内容。


String中编码格式的问题

   在我们平时的开发工作中使用String处理中文数据的时候总是出现乱码的问题来使我们一阵的心烦,所以我在这里也总结一下关于我们在java程序中的String的编码格式的各种情况。

   PS:在下面的内容中我们只以几个比较常见的编码类型UTF-8,ISO-8859-1,GBK作为我们测试和讨论的内容,其他我们暂时不做讨论。

相关String编码格式分析

   我们要解决乱码问题,我们首先要分析中文乱码的问题是怎么产生的。编码的概念想必大家也都清楚我就不再赘述。问题是我们在代码中到底是如何编码的?

首先我们要明确这一点,就是在JVM

   我们以一个实例来看一看:

@Test
public void chineseTest() throws Exception{

String str = "孙正方";
System.out.println(""+Charset.defaultCharset().name());
System.out.println(str);
System.out.println(new String(str.getBytes()));
System.out.println(new String(str.getBytes(),"GBK"));
System.out.println(new String(str.getBytes(),"UTF-8"));
System.out.println(new String(str.getBytes(),"ISO-8859-1"));
}

我们测试下发现返回的结果是这个

UTF-8

孙正方

孙正方

瀛欐鏂�

孙正方

孙正方

然后我们将我们的测试用例进行改动再进行执行,看看结果是不是有所变化

@Test
public void chineseTest() throws Exception{

String str = "孙正方";
System.out.println(""+Charset.defaultCharset().name());
System.out.println(str);
System.out.println(new String(str.getBytes()));
System.out.println(new String(str.getBytes("GBK"),"GBK"));
System.out.println(new String(str.getBytes("UTF-8"),"UTF-8"));
System.out.println(new String(str.getBytes("ISO-8859-1"),"ISO-8859-1"));
}

结果如下:

UTF-8

孙正方

孙正方

孙正方

孙正方

???

我们对于代码再进行一次的改动

@Test
public void chineseTest() throws Exception{

String str = "sunsun314";
System.out.println(""+Charset.defaultCharset().name());
System.out.println(str);
System.out.println(new String(str.getBytes()));
System.out.println(new String(str.getBytes("GBK"),"GBK"));
System.out.println(new String(str.getBytes("UTF-8"),"UTF-8"));
System.out.println(new String(str.getBytes("ISO-8859-1"),"ISO-8859-1"));
}

结果如下:

UTF-8

sunsun314

sunsun314

sunsun314

sunsun314

sunsun314

   这个三个现象为什么都不同呢?

   于是我们对这个情况进行了详细的分析

   首先我们的字符串的原有数据的格式我们可以通过Charset.defaultCharset().name()获取到是UTF-8,这个一直没有改变,在我们的第一次测试中只有

System.out.println(new String(str.getBytes()));以及System.out.println(new String(str.getBytes(),"UTF-8"));对于字符串的从新组合是没有乱码的,我们详细调查我们的.getBytes()方法我们可以看到如下的代码。

 static byte[] encode(char[] ca, int off, int len) {
String csn = Charset.defaultCharset().name();
try {
// use charset name encode() variant which provides caching.
return encode(csn, ca, off, len);
} catch (UnsupportedEncodingException x) {
warnUnsupportedCharset(csn);
}
try {
return encode("ISO-8859-1", ca, off, len);
} catch (UnsupportedEncodingException x) {
// If this code is hit during VM initialization, MessageUtils is
// the only way we will be able to get any kind of error message.
MessageUtils.err("ISO-8859-1 charset not available: "
+ x.toString());
// If we can not find ISO-8859-1 (a required encoding) then things
// are seriously wrong with the installation.
System.exit(1);
return null;
}
}

然后我们再查看我们getBytes(“UTF-8”)这个方法

public byte[] getBytes(String charsetName)
throws UnsupportedEncodingException {
if (charsetName == null) throw new NullPointerException();
return StringCoding.encode(charsetName, value, 0, value.length);
}

   我们可以清晰的看到其实在实现的时候都是通过StringCoding.encode这个方法进行实现的,所以说我们在这里的测试情况中.getBytes()方法其实等于.getBytes(“UTF-8”)。

   接下来的大部分情况大概都能够比较说得清楚了,字符串格式的编码有点类似与一种密码进行加密的过程,而其实我们的”孙正方“这个内容则是最后的结果,在我们UTF-8的默认环境下String str = "孙正方";事实上一开始str的内容就可以看作”孙正方“这个字符串内容根据UTF-8的形式进行加密之后的密文。

   然而为什么使用GBK解密和加密之后也没有问题呢,这个是由于使用GBK解密之后我们所获得的内容其实是GBK格式下???的内容,但是由于我们之后又对于这个内容使用GBK进行加密了,所以最后加密的结果通过UTF-8查看也不会有乱码。

  (在这里我们打一个比方,有一个UTF-8的密文abcUTF-8的编码下这个对应123,但是在GBK下面这个对应的是890,然而我们在使用new String(str.getBytes("GBK"),"GBK")的时候我们的操作基本上就是类似于abc - > 890 -> abc虽然中间内容不正确,但是由于加密和解密都执行了一遍,反而最后的结果没有问题)。

   但是这个就没法解释ISO-8859-1为何会有乱码了啊,这个我们要从ISO-8859-1格式的来源说起。

   ISO-8859-1编码是单字节编码,向下兼容ASCII,其编码范围是0x00-0xFF0x00-0x7F之间完全和ASCII一致,0x80-0x9F之间是控制字符,0xA0-0xFF之间是文字符号。

   此字符集支持部分于欧洲使用的语言,包括阿尔巴尼亚语、巴斯克语、布列塔尼语、加泰罗尼亚语、丹麦语、荷兰语、法罗语、弗里西语、加利西亚语、德语、格陵兰语、冰岛语、爱尔兰盖尔语、意大利语、拉丁语、卢森堡语、挪威语、葡萄牙语、里托罗曼斯语、苏格兰盖尔语、西班牙语及瑞典语。

   以上内容可以清晰的看到我们乱码的原因,就是ISO-8859-1不支持中文,也就是说在中间过程中内容有丢失,所以最后没有方法进行还原。

   (这里也打个比方,一个UTF-8的密文abc,但是没有在ISO-8859-1中有对应的所有的解码,也就是说出现了abc - >??? - >???这种的情况,这个情况被有些人称为解码黑洞)

   综上我们也能够解释第三个缘由,就是事实上三种格式UTF-8,ISO-8859-1,GBK都能很好的支持英文,所以不会出现乱码的问题。

相关编码格式的解方案

   有了我们上文的编码格式的基本理解,我想在这里我们对于乱码问题的处理变得简单的多了,我们综合上面的看法发现我们需要知道几个情况

一、出现乱码的数据的编码格式

二、什么编码格式不会出现乱码

三、我们在LOG中哪一种格式的编码能够正常显示内容

   知道了这个三点情况之后我们只需要以如下步骤缓慢推进即可,
   譬如我们的控制台UTF-8能够正常显示内容

   我们的数据库以及信息传输需要使用的是GBK的编码

   那么我们尝试使用UTF-8,ISO-8859-1,GBK对于一个数据String进行getBytes解码,然后我们再根据UTF-8进行编码,看在哪种情况下字符串能正确的被显示在控制台中。

   譬如在这里我们的数据是UTF-8的,通过调试发现数据源是UTF-8的,我们就使用UTF-8进行解码,最后编码成为GBKStringnew String(x,”GBK”))即可

   以下是我根据我们例子的情况进行的转码:

public String trans() throws Exception{		String str = "孙正方";
return new String(str.getBytes("UTF-8"),"GBK");
}