从我们学习编程之始,我们就无时无刻的不与字符串打交道,我们在互联网上看到的绝大部分文字信息,都是字符串类型,小到标题,大道几万字的文章,都是字符串类型的数据。学过C或者C++的博友们应该知道,对字符串的操作是很直观的,和其他数组类型几乎没有什么区别,利用角标都可以进行大量操作。但是JAVA里面的String类型相对于其他语言来说,是很不一样的。
String类
在JAVA中,String是作为一个类存在的。声明格式为:String 对象 = new String();
为了保持准确性,我们打开API文档。
属于lang包,使用的时候不需要导包,在我们使用String字符串的时候,我们发现String字符串只能够覆盖而不能够改变的。所以这里介绍一下String类的具体性质:
- String就是一个字符串类型,属于java.lang包,不需导包
- 所有的字符串常量(“hello” “jack”)都属于字符串类型的对象
- 字符串字面值属于常量,存储在方法区的常量池中。
- 字符串常量在创建完毕之后,就不可再次修改,因为类内部没有提供set方法。
上面性质我们慢慢来验证,我们跟着技术文档往下面看String类的其他性质
构造方法:
方法1
这个就是最常规的字符串构造方法。不做过多演示。
方法2
我们来演示一下下面的这个方法,看能够输出什么:
public class String方法 {
public static void main(String[] args) {
byte[] b = {66,64,63,74,75,66,77};
String s = new String(b);
System.out.println(s);
}
}
输出结果:
B@?JKBM
我们可以看到输出了一串字符,那么细心的博友可以发现,这些字符全都是原来byte数组中元素的ASCII码转码。
而这个过程我们可以发现,很类似像我们在老电影中看到的电报,用几个几个的数字来进行播报,而最后转过来的字符就像是翻译过来的码文,所以这个过程我们经常称之为“解码”。
(如果看过我上面一篇介绍Object类的博文的博友可以发现,String作为一个类,直接输出的时候不应该继承toString()方法来输出所属位置和内存地址么,但是现在直接输出了字母,所以我们可以想到,这里也是重写了toString()方法的。)
方法3
这里和方法2很相似,不过这里多了一个参数,这个参数是由Charset类所定义的对象,里面存储的是一套新的编码格式,也就是说,这里是一个利用自定义的编码格式来进行解码。通俗来说就是ASCII码是一套电报界通用的明码,如果我们的电报不想让敌人破译,我们就要有自己的密码本。而后面的Charset就充当着一个自己独有的密码本的角色。(由于Charact博主还不太深入了解,为了不误人子弟,就不举例了)
方法4
代码说明:
public class String方法 {
public static void main(String[] args) {
byte[] b = {66,64,63,74,75,66,77};
String s = new String(b,3,4);
System.out.println(s);
}
}
输出为:
JKBM
对比第二个方法里面的输出结果,我们可以发现,里面的参数offer就是开始解码的位置,length就是解码的长度。(还是举上面的例子,这里就像我们接受到同伴传来的报文,为了不被敌人所察觉,我们的重要信息商量好隐藏在报文的第几位,一共多长。解码的时候也直接开始从重要信息开始解码都行了。)
方法5
这个就像是方法3和方法4的结合,用自己的编码格式,然后从中间我们想要的位置开始翻译,双重保险。
方法6
代码演示:
public class String方法 {
public static void main(String[] args) {
char[] c = {'好','吃','的','都','给','你'};
String s = new String(c);
System.out.println(s);
}
}
输出结果:
好吃的都给你
这个作用就是和方法2类似,不过这个是直接就是字符形式,所以也就是:把字符数组中的内容链接为字符串。
方法7
代码演示:
public class String方法 {
public static void main(String[] args) {
char[] c = {'好','吃','的','都','给','你'};
String s = new String(c,0,3);
System.out.println(s);
}
}
输出结果:
好吃的
这里还是和上面的类似:从指定位置进行指定的长度的转字符串
方法8
代码示例:
public class String方法 {
public static void main(String[] args) {
int[] a = {66,67,68,88,99};
String s = new String(a,0,3);
System.out.println(s);
}
}
输出结果:
BCD
这个就是类似上面方法4,利用ASCII码进行“解码”。
方法9
这个也就是我们之前 学习的常用的构造形式,参数里直接就是一个字符串。我们来从内存层面来进行分析
不过这里虽然简单,但是这里的内涵还是有很多的。我们先来段代码进行实例:
public class String方法 {
public static void main(String[] args) {
String str = "abc";
System.out.println(str);
String str1 = new String("abc");
System.out.println(str1);
}
}
结果输出:
abc
abc
这里的输出结果相同。但是这里还是隐含着很多小知识点:
先引入一个常量池的概念:
常量池
任何好的编程语言的关键目标之一是高效的使用内存。随着应用程序的增长,String字面值占用大量的内存非常常见。对程序而言,全部String字面值中往往有大量的冗余,为了使Java更高效地使用内存,JVM留出一块特殊的内存区域,称为“String常量池”。当编译器遇到String字面值时,它检查该池内是否已经存在相同的String字面值。如果找到,则将新的字面值的引用指向现有的String,而不创建任何新的String字面值对象。
我们在上面性质2中说道:“abc”,“adfasdf”都属于字符串类型的对象,这些都是在方法区中的常量池存放,在代码的编译和运行中,动态生成,我们来从内存层面来介绍这两个对象的生成过程。
从方法的解释中,我们知道,如果用new来创建对象的话会在堆内存中开辟一块空间来存储从常量池中复制的副本。所以,我们两个同样的abc,其实是不一样的,但是如果我们对两个abc进行equals方法比较:
public static void main(String[] args) {
String str = "abc";
System.out.println(str);
String str1 = new String("abc");
System.out.println(str1);
System.out.println(str.equals(str1));
}
输出结果:
abc
abc
true
这里又返回了true,从Object类继承的equals方法应该是对地址进行对比,如果两个地址不一样,即使内容一样,也会返回false的。但是按内存分析的来说,应该又不是一块内存,所以我们推断得知:这里是对equals方法进行了重写的。只要内容一样,结果就放回true。
我们想到了hashCode()方法可以获取两个对象在内存中的地址。我们来试验一下。
public static void main(String[] args) {
String str = "abc";
System.out.println(str);
System.out.println(str.hashCode());
String str1 = new String("abc");
System.out.println(str1);
System.out.println(str1.hashCode());
System.out.println(str.equals(str1));
}
输出结果:
abc
96354
abc
96354
true
结果。。。。竟然又是一样,所以我们再次推断得知,这里存储的地址,其实还是常量池中那个字符串常量的地址。
兜兜转转,我们发现只要是字符串常量,只要是内容相等,那么他们里面所最终指向的都是常量池中那个不会变的“abc”。
这里总结两张内存分析图:
方法10:
方法11:
这两个为什么要放一起呢,因为这两者的方法几乎一样,只是在一些性质上的不同。
预知后事如果,请看下回分解(不是吊胃口,是有点长真的来回翻看比较类,不如重新开一个)——————————————————————————