
【入门】Java数据类型 | String基础
Java String 初识
自从Java发布以来,基本数据类型就是Java语言的一部分,分别是***byte, short, int, long, char, float, double, boolean***.当前前面我们也学习了基本类型的包装类,知道了每种基本类型都有它的包装类型,JAVA是面向对象的语言,很多类和方法中的参数都需使用对象(例如集合),但基本数据类型却不是面向对象的,这就造成了很多不便,所以有了包装类型。
String可以说是Java中使用最多最频繁、最特殊的类,因为同时也是字面常量,而字面常量包括基本类型、String类型、空类型。其实如何判断一个类型是不是基本类型,其实只要看该类型名称首字母是否是大写的(这是因为java 的类库严格遵循了驼峰命名的习惯,如果你对enum 有疑问,请查看Java枚举—枚举初识)
因为String 的广泛使用,所以Java也针对String 做了很多的优化,例如线程安全的StingBuffer,快速拼接的StringBulider,还有Jvm 的串池,正是因为如此,也衍生出了很多关于String类型的面试题,因为事出必有妖,谁让它特殊呢
一. String 的说明书
还是按照国际惯例,先看一下String 的说明书,其实往往很多时候你的困惑都在说明书里写着呢,但是在此之前我们还是先看一下它的继承关系,因为我们说了它是对象类型的
从这个继承关系我们看出它是可以序列化的,也是可以相互比较的,好的,接下来开启我们的String 之旅,林尽水源,便得一山,山有小口,仿佛若有光。便舍船,从口入。初极狭,才通人。复行数十步,豁然开朗,String 的说明书就是桃花源的入口
1. String 的主要内部构成
下面的变量value和hash其实很简单,但是这里单独提出来还是希望大家能留意一下,早年前出去面试,别人问看过java 的源代码吗,那我肯定回答看过啊,那别人又说既然看过源代码那就很多看过String 的源代码了,那你讲讲,当时那尴尬 😄
value
这就是string 的真实存储,也就是说String 其实是以字符数组进行存储的
hash
字符串的hash值
2. String 的创建方法
- 构造方法:我们知道String 不是基本类型而是对象类型,那么我们自然可以按照创建对象类型的方式去创建一个字符串对象,也就是使用new 关键字,其实就是 这就是String的构造方法,例如String str = new String(“abc”),String 提供了十来种构造方法
- 字面量:如果你对什么是字面量不清楚的话,没有关系,但是你可以想一下,你是如何创建基础数据类型变量的,例如int i=10,对于String 类型我们也可以这样创建例如String str = “abc”;
但是你要是认为字面量的这种方式就是创建String 对象的目的话,那你就错了,Java 提供的这种方式不单单是为了简化String 的创建,更主要的目的是为了和通过构造方法创建的这种方式进行区分,那区分出来的目的是什么呢?就是我们后面说的串池,因为它将这两种方式区分出来之后,让通过字面量的这种方式会走串池的这个设计,因为通过new 创建出来的String 会存储在堆里,并且有自己的空间,通过字面量的这种方式创建的字符串会被区别对待吗,会被放到公共的串池中
这也就说,如果两个字面量有相同的内容(字符串),那么它们会占用串池中同一块存储空间,也就是指向同一个内容
当你通过分配一个字符串字面量的方式创建一个字符串对象的时候,串池会先检测是否存在这样的字符串内容,如果存在则返回已经存在的字符串的引用给变量,如果不存在则该字面量加入到串池然后返回其引用,所以上面两个字符串变量共享了同一个在串池中的字面量,文章后面还会简单介绍一下串池,后面针对串池我还会单独写一篇文章
二 . String的使用
1. String的不可变性
不可变性在注释中已经说明了,下面我们具体看一下
String对象一旦在堆中创建出来,就无法再修改。那是因为String对象放在char数组中,该数组由final关键字修饰,不可变。这里有一点需要注意的是不可变的原因不是因为String类被final 修饰了,而是因为它的底层存储是一个不可变的数组,程序无法对它做出修改
像下面这个字符串类BuerSting它就是可变的,这里的可变是指的是它原来的值变掉了
通过上面我们看到了,字符串其实是可以定义为可变的,但是java 为什么还是把它设计成不可变的呢,这也和串池有关,那是因为通过串池的这种设计方式,可能导致多个字符串变量,持有同一个字符串,那么如果是可变的话,通过一个变量改变了字符串的内容的话,就会导致其他字符串的变量的引用的内容发生变化,所以这就是字符串为什么设计成不可变的。
可以看出当你通过字符串的方法去修改字符串的时候,它只会返回新的字符串新的字符串包含了修改后的内容,原来的内容不会变
到这里你是不是觉得完了,其实还没有,因为这个时候你还可以通过继承的方式干点坏事
final class
你可通过继承String 类,然后提供可以修改内容的方法,这个时候String类型的不可变性就会被打破
还有就是你可以重写hashcode 方法和equals 方法,这个时候就会导致串池里的内容重复或者是不同内容有相同的hash值
线程安全
因为是不可变对象,所以String 是线程安全的
2. 定义一个字符串
上面三句代码怎么理解呢?这里需要先引入一个概念,字符串常量池。
字符串常量池是一块特殊的独立内存空间,放在Java Heap中 { 在Jdk7.0之前字符串常量池存放在PermGen中,Jdk7.0的时候移动到了Java Heap(在堆中仍然是独立的),Jdk8.0的时候去掉了PermGen,用Metaspace进行取代 } ,Java内存模型不是本章讨论的重点,关于字符串常量池在内存中的位置可以看关于JVM 的文章
str1和str2引用的字符串字面量就在字符串常量池中,而str3引用的对象在Java Heap中。怎么,还不太好理解?举个例子
工作一天,到下班时间了,准备看会儿金瓶.,算了,《三国演义》,打开小说网站,在线阅读;过了半个小时,女票回家了,看《三国演义》也是她想做的事儿,我看网址发给她,好,她也开始看了,再过半个小时,我爸回来了,他也是三国迷,但是他不喜欢在线看,因此在书店买了一本看。
上面提到的小说网站就是一个字符串常量池,包含了很多字符串字面量,如《三国演义》、《西游记》、《红楼梦》等,每个字符串字面量在常量池中保持独一份,无论谁进网站看《三国演义》都是同样的网址和同样的内容。
我和女票就是str1和str2,我们看的都是同一个网站的《三国演义》,不仅内容一样,引用的地址也一样(字符串常量池中保留了唯一的“helloworld”),因此str1 == str2 运行结果为true
而我爸就是str3,与我和女票都不一样,虽然看的内容也是《三国演义》,但是通过实体书籍来看,引用地址不一样,同时一本书籍不支持多个人同时看(字符串对象在java heap中,且每次new都会新建一个对象),因此str1 == str3 运行结果为false。
所以我们回过头再来看一下,一个字符串字面量总是引用String类的同一个实例,因为被String.intern()方法限定了,同样我们可以调用该方法将堆中的String对象放到字符串常量池中,这样做可以提升内存使用效率,同时可以让所用使用者共享唯一的实例。
那么该方法的实现逻辑是怎么样的呢,我们看一下源码
我们发现这是一个native方法,看一下注释,发现这个方法大致流程是:
1、当执行intern()时,会先判断字符串常量池中是否含有相同(通过equals方法)的字符串字面量,如果有直接返回字符串字面量;
2、如果不含,则将该字符串对象添加到字符串常量池中,同时返回该对象在字符串常量池的引用。返回的引用需要赋值才可,否则还是会指向堆中的地址,即:
下面我们看一下内存结构
3. 再次赋值给已定义的字符串
我们开始已经说了String是由final关键字修饰,不可变,那么此时在内存中如何体现呢,可以看出是不该改变原来的字符串helloZhonghua 而是创建了新的字符串helloHuaxia
4. String 对 “+” 的处理
通过编译工具后得到
因此我们可以发现编译器在编译期间就是进行变量合并,而不会在常量池中创建三个对象 “good good”,“ study”,“good good study”。str7 == str8 运行结果 true。但如果这样
这时运行结果为false,通过String变量 + 字符常量方式得到的结果会在堆中,不在常量池中,当然可以通过intern()方法放进常量池中,同时不仅“+”如此,调用substring(),toUpperCase(),trim()等返回的都是String在堆中的地址。
5. String常用的方法
总结
本文从String的不可变性,String创建时字面量和String对象的不同,字符串字面量常量池,字符串的内存结构,常用的String相关方法的描述,若有不对之处,请批评指正,望共同进步,谢谢!
- Java 语言针对字符串提供了特殊的支持
- 字符串的拼接和将其他对象转化成字符串提供了特殊的支持
- 创建String 对象提供了特殊支持,那就是通过字面量的这种方式,而这种方式是为了实现串池的设计与使用
- String 对象是不可变的,所以是线程安全的
- “+” 操作符被重写了,用来拼接字符串
- Java7 之后 String 对象可以被用在switch case 中
- Java为了优化字符串的性能,提供了字符串常量池
- String 不可变的原因是底层存储的字节数组是final 修饰的
- 提供了一个文档The Java Language Specification 大家有需要可以去查看
