本章目录
温馨提示
大家好我是Cbiltps,在我的博客中如果有难以理解的句意,难以用文字表达的重点,我会有配图。所以我的博客配图非常重要!!!
而且很多知识在代码的注释里,所以代码注释也非常重要!!!
这篇文章首先跟大家讲一下String的一些简单法则,
然后后面会针对String不可改变再写一篇文章作为这篇文章的补充!
所以可以把这篇文章和下面一篇文章当成一个大类:String类!
开篇介绍
其实本节的内容高校几乎是不讲的,也很少有人对其深究,
但是为了提升自己,了解 String 这个东西底层是如何实现的,就有了今天的文章。
本章的内容是你完全可以当做是对于内功的修炼,百利无一害!
今天画的内存图比较的烧脑,大家仔细看!!!
本章及下一篇文章的重点
- 认识 String 类
- 认识字符串常量池
- 了解 String 类的基本用法
- 熟练掌握 String 类的常见操作
- 认识 StringBuffer 和 StringBuilder
正文开始
1. 深度剖析字符串
1.1 了解字符串
C语言中没有
字符串类型的,在C++
和Java
中有,叫String
!
简单的理解两个问题:
什么是字符串
:使用双引号的" "
,可以若干字符,就是字符串常量
什么是字符
:使用单引号的' '
只能有一个字符,就是字符常量
注意
:在Java中没有字符串以\0结尾
的说法!
紧接着,直接打开String源码,进去看里面的代码,发现是 final修饰 的,说明不能被继承!
1.2 创建字符串
//方式1:
String str = "祥子";//字符串常量
//方式2:
String str2 = new String("花花");//调用构造方法定义
//方式3:把数组变成字符串
char[] chars = {'a', 'b', 'c'};
String str3 = new String(chars);
System.out.println(str3);
在官方文档上还有很多String的构造方法,大家可以区看看,博主只举了最多的例子!
1.3 String代码的内存布局
首先写一段举例的代码:
public class Main {
public static void main(String[] args) {
String str = "abcdef";
String str2 = str;
System.out.println(str);
System.out.println(str2);
/*打印出来是一样的!画一下内存图!*/
/*其实这里的str和str2两个引用都是指向"abcdef"的,然后想一个问题,可以用str2修改"abcdef"吗?
答案是不可以的,因为:它是字面值常量不能修改的!*/
System.out.println("==============");
str = "hello";//但是这里的修改是修改的指向!
System.out.println(str);
System.out.println(str2);
/*打印出来不一样!画一下内存图!*/
}
}
上面图解就是打印一样的原因,再看一下变化后(修改指向)的内存图:
然后,重新写一段代码:
public class Main {
public static void func(String s,char[] array) {
s = "xiangzi";
array[0] = 'p';
}
public static void main(String[] args) {
String str = "abcdef";
char[] chars = {'b','i','t'};
func(str,chars);
System.out.println(str);
System.out.println(Arrays.toString(chars));
}
/*上面的问题,一定要画草图了解一下!里面牵扯较复杂的指向问题!*/
/*所以说,不是 传引用 就是可以改变实的值!你要看这个引用到底干啥了!!*/
}
上面一段代码的内存图及指向的改变请看下面的图:
所以说,不是 传引用
就是可以改变实的值!你要看这个引用到底干啥了!!
2. 字符串比较相等
2.1 内容比较
内容的比较用的就是 equals方法 进行比较的,大家直接去看源代码或者是文档找怎么用就可以了!
下面说一下 equals方法 要注意的点:
public static void main(String[] args) {
String str1 = null;
/*使用equals的时候要注意:一定要预防空指针异常!*/
String str2 = "11";//如果想要修改的话,就必须通过反射修改
// System.out.println(str1.equals(str2));//这样子就是空指针异常
System.out.println(str2.equals(str1));//这里的str2不是空指针,就不会报错的
}
2.2 地址比较
直接上代码(包括知识点以及拓展的内容):
public class Main {
public static void main4(String[] args) {
String str1 = "hello";
String str2 = new String("hello");
System.out.println(str1 == str2);
/*其实这里比较的不是内容,而是地址! 地址不一样,运行的结果就是false!*/
/**然后下面做一点铺垫(涉及到JVM的知识):
*
* Class文件常量池:Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量
* 池表(Constant Pool Table),用于存放编译期生成的各种字面量与符号引用,这部分内容将在类加
* 载后存放到方法区的运行时常量池中。
*
* 运行时常量池:当程序把编译好的字节码文件加载到JVM当中后,会生成一个运行时常量池(在方法区,就是从磁盘到内存),
* Class文件常量池会变成运行时常量池。
*
* 字符串常量池:本质是一个哈希表(StringTable),JDK1.8开始放在了堆里面,里面存的是驻留字符串的引用,
* 在堆中的字符串实例被这个哈希表引用之后就等同被赋予了”驻留字符串”的身份,
* 在JVM中字符串常量池被所有类共享。
*
* 什么是哈希表:其实就是一个数据结构,描述和组织数据的一种方式,非常的块!
* 如果你想知道StringTable如何实现的,可以直接去看JVM的源代码,但是使用C++写的!
*/
来看一下上面代码的内存图:
然后看下面一段代码:
public static void main(String[] args) {
String str1 = "hello";
String str2 = "hello";
System.out.println(str1 == str2);
/*这里肯定就是true了!*/
}
它的的内存图是这样的:
这里我画图的时候,str2 的右边的地址是 0x334,大家看的时候注意一下,改的话比较麻烦!
关于上面的知识点,如果想验证的话可以打开反汇编代码进行查看:
到了这里,字符串是如何创建的都知道了吧,下面就来欣赏一下源代码是怎样实现的!
在JVM的源代码里就可以看到:
一个元素是如何放进去的;
StringTable是如何创建的;
数据是如何转变成hash的等等。
这一切的一切使用C++写的,博主有的也看不懂!
看以看出JAVA是站在巨人的肩膀上的!
为了加强理解,继续往下看:
public class Main {
public static void main(String[] args) {
String str1 = "hello";
String str2 = "he" + "llo";//此时 他两都是常量,编译的时候,就已经确定好了是"hello"
String str3 = "he";
String str4 = str3 + "llo";//此时str3是一个变量 -> 编译的时候,不知道是啥? str4不是一个完整的对象!
/*拼接的hello并不是在常量池里面,拼接的是一个单独的对象!所以运行结果就是false!*/
System.out.println(str1 == str4);
}
}
上面的代码中 str2 的时候就是 “hello”,看下面的反汇编代码:
看一下内存图:
下面一个例子比较麻烦(重要):
public static void main(String[] args) {
String str1 = "11";
String str2 = new String("1") + new String("1");//这里创建的对象是StringBuilder对象!
System.out.println(str1 == str2);
}
直接上内存图:
而且,为了验证上面的图去看反汇编代码也是一样的:
那如果是这样的:
//代码1
public static void main(String[] args) {
String str2 = new String("1")+new String("1");
String str1 = "11";
System.out.println(str1 == str2);//如果反过来依旧是false
}
在看下面:
//代码2
public static void main(String[] args) {
String str2 = new String("1")+new String("1");
str2.intern();//手动入池
String str1 = "11";
System.out.println(str1 == str2);//这里就是true了
}
到了这里,我就不画图了,我快废了,再也不想画图了…
其实原理解释简单的:
代码1和上面的演示的代码是一样的,
代码2会将拼成的 “11” 手动入池,
然后再创建字符串的时候就会检查是否有这个字符串直接指向即可!
到这里字符串比较的问题就写完了,大家好好看,好好学!
全文结束
其实写到这里就是可以的,但是,我想写的更全面一点,
但是篇幅太长不是一个好的解决方法!
后面我还会在写一篇文章,依旧是关于String的!
是关于String不可改变和让其改变的内容!
大家敬请期待!再次感谢大家的点赞和关注!