一、 不可变特性,内部存储结构
-
说到String字符串,可以语义的理解为将一系列字符(char),然后将串在一起,就是字符串了。可以猜测到String的底层也许就是这样构建出来的。 如下:进入源码发现内部结构其实就是一个字符型数组(char[])呈现的,所以字符串中的字符都是拆分成单个字符存入数组的。
-
String字符串又称为不可变字符串;相对应的有可变字符串StringBuilder(线程非安全,效率高) 和 StringBuffer(StringBuilder的早期版本,线程安全,效率相对较StringBuilder低);不可变主要体现在两个方面:长度和内容。
长度: value数组一旦定义长度之后,长度是不可变的。其次数组为引用数据类型,被final修饰后,其地址引用是不无法改变的;
内容: value数组被private修饰后,外部是无法修改其内容的。 -
也许我们会这么想,为啥我感觉不到它的不可变特点呢?比如:
String str = "hello"; str = "hello,world";
这不就改变了吗?下面来理解一下:
1,首先我们得知道,字符串虽然是引用数据类型,但是有一点特点不同于正常的引用数据类型,正常情况下,构建一个类的对象,它需要通过构造方法构建的,但是String可以像基本数据类型将一个串直接赋值给一个变量引用(我们习以为常的这样创建,如上面得例子;不过也可以通过构造方法来构建)。2,比如在执行上面那个语句的时候,"hello"其实相当于一个常量,但是它是存在常量池的一个String对象;String str = “hello” 就是将引用指向了一个在常量区的这个对象,而str = “hello,word”;这一步其实是在常量池新创建一个String对象,并将地址存到原来的变量中,并不是直接通过改变其内部数组的内容和地址的。
-
下面通过一个例子来进一步理解
String a = "Code"; String b = "once"; String d = ","; String d = "Think"; String e = "twice"; String str = a + b + c + d + e;
问:上面这个过程共创建了多少个对象?9个
由于a, b, c, d, e 这里本身创建了5个对象;在进行a + b的过程中,String底层做的是将a ,b两个字符串中的value数组的值进行合并成一个新数组,然后创建一个新对象,并且新对象的底层数组就指向这个新数组,此时创建了第6个String对象;
当a + b 的结果再加上c的时候,a + b的计算结果对应的那个字符串对象的value数组在和c的value数组进行合并成一个新数组,再创建一个新对象,将该对象的底层数组就指向这个新数组,这个时候创建了第7个String对象;后面依次类推。
大致理解如下:a: {"Code"} 1 b: {"once"} 2 a+b: {"Code once"} ==》6 中间过程产生的对象 c: {","} 3 a+b+c: {"Code once,"} ==》7 中间过程产生的对象 d: {"Think"} 4 a+b+c: {"Code once,Think"} ==》8 中间过程产生的对象 e: {"twice"} 5 a+b+c+d+e: {"Code once,Think twice"} ==》9
总结:
1,由于String的内部结构特点,从而有了不可变特性,在字面量创建字符串时本身会创建一个对象,在使用“+”运算符进行字符串拼接的过程中也会产生第三个新的字符串对象,并且将原来字符串的引用指向了新的字符串对象,表面上的拼接运算,实则却是String对象的创建,只是我们看不到底层操作罢了。
2,假设我们拼接次数非常的频繁,可以想象到对象的频繁创建,想必是非常消耗内存的,且影响性能;所有后来有了可变字符串(StringBuffer和StringBuilder),适用于频繁拼接字符串的这一个过程。
二、重写至Object的几个方法
-
在Java官方API文档中可以看到,String类它存在于java.lang包下,它直接继承于Object;实现了Serializable, CharSequence,Comparable 三个接口。
下面我们就来探讨一下重写至Object父类的方法:equals(),hashCode(),toString() 和实现的Comparable接口的 compareTo() 方法。
1,equals()方法:equals()方法默认比较的是引用。而String进行了重写,如果引用相同,则表明同一对象,内容必然相同,返回true;引用不同则比较内容,也就是value数组的每一位字符进行依次遍历比较,如果都相同则也返回true。
一般最容易出现的笔试问题就是对于String,"==" 和 equals()方法的比较过程。
eg: 判断输出结果String a = "java中的String"; String b = "java中的String"; String c = new String("java中的String"); String d = new String("java中的String"); System.out.println(a==b); //true System.out.println(a==c); //fasle System.out.println(c==d); //fasle System.out.println(a.equals(b)); //true System.out.println(a.equals(c)); //true System.out.println(c.equals(d)); //true
a、b指向的是常量池中的同一个对象;而通过new 创建出来的对象是存在堆内存中的,并且每次创建出来的都是不同对象,所以c、d表示不同的对象。
由于"=="比较的是引用,所以:a == b 结果为true,a == c结果为 false,c == d 结果为fasle。
由于equals()重写了,同一对象则为true,不同对象则比较内部的值,所以结果都为true。在这里虽然a与c是不同对象但是由于值相同,所以也为true。2,hashCode()方法:hashCode()方法默认是调用了底层本地其他语言的方法,经过一系列算法求出以一个整型数值。而String进行了重写,将字符串的每一位进行遍历进行相应的计算后并且累加,最 后返回结果。如下:
3,toString()方法:toString()方法默认获取类名后中间通过“@”与hashCode()方法结果对应转化的16进制数进行拼接作为结果返回。而String进行了重写,返回字符串本身。
4,compareTo()方法:compareTo()实现至Comparable接口。默认将每一个获取两个字符串长度最小的那一个,并且将它的长度作为遍历次数进行遍历比较,如果发现其中两位字符不同,则返回这两位的code码值返回;如果循环完,如果每个轮次都相等,则返回长度之差,可以发现,如果两个字符串相等则返回0,其他情况都不为0。
-