String相关介绍
String
- 字符串是常量,创建后不可改变。
- 字符串字面值存储在字符串池中,可以共享。
String s1 = "Runoob"; // String 直接创建
String s2 = "Runoob"; // String 直接创建
String s3 = s1; // 相同引用
String s4 = new String("Runoob"); // String 对象创建
String s5 = new String("Runoob"); // String 对象创建
- 字符串常量池中是不会存储相同内容的字符串的.
- String的String Pool是一个固定大小的HashTable,默认值大小是1009.如果放进String Pool的String非常多,就会造成Hash冲突严重,从而导致链表会很长,而链表长了后会直接造成的影响就是当调用String.intern时性能会大幅下降
- 使用
-XX:StringTableSize
可以设置StringTable的长度 - 在jdk6中的StringTable是固定的,就是1009的长度,所以如果常量池中的字符串过多就会导致效率下降很快.
- 在jdk7中,StringTable的长度默认是 60013,1009是可设置的最小值
在jdk1.7的时候把StringTable放到了堆空间中,为什么?
jdk7中将StringTable放到了堆空间中
- 因为永久代的回收效率很低,在full gc的时候才会触发.而full gc是老年代的空间不足,永久代不足时才会触发.
这就导致了StringTable回收效率不高.
- 而我们开发中会有大量的字符串被创建,回收效率低,导致永久代内存不足,放到堆里,能及时回收内存.
常用方法
public char charAt(int index);//根据下表获取字符
public boolean contains(String str);//判断当前字符串是否包含str
public char[] toCharArray;//将字符串转换成数组
public int indexOf(String str);//查找str首次出现的下标,存在,则返回该下标;不存在,则返回-1
public int length();//返回字符串长度
public String trim();//去掉字符串前后的空格
public String toUpperCase();//将小写转成大写
public String toLowerCase();//将大写转成小写
public boolean endWith(String str);//判断字符串是否以str结尾
public String replace(char oldChar,char newChar);//将字符串替换成新字符串
public String[] split(String str);//可根据str做拆分,拆分完不包含str
可变字符串
- StringBuffer: 可变长字符串,jdk1.0提供,运行效率慢,线程安全。
- StringBuilder: 可变长字符串,jdk5.0提供,运行效率快,线程不安全。
public static String appendEnds(String emilName){
StringBuilder buffer = new StringBuilder(emilName);
buffer.append("@baidu.com/");
return buffer.toString();
}
字符串拼接的底层
String s1 = "a";
String s2 = "b";
String s3 = "ab";
/*
*s1 + s2 执行细节
* 1. StringBuilder s = new StringBuilder();
* 2. s.append("a");
* 3. s.append("b");
* 4. s.toString(); -->约等于 new String("ab");
*
* 补充: 在jdk5之后使用的是StringBuilder,在jdk5之前使用的是StringBuffer
*/
String s4 = s1 + s2;
System.out.println(s3 == s4);//false
变形
final String s1 = "a";
final String s2 = "b";
String s3 = "ab";
/*
*s1 + s2 执行细节
* 被final修饰的是常量
* 这种情况等同于 s4 = "a" + "b";
*/
String s4 = s1 + s2;
System.out.println(s3 == s4);//true
总结
- 字符串拼接操作不一定使用的是StringBuilder,如果拼接符号左右两边都是字符串常量或常量引用,则仍然使用编译期优化,即非创建StringBuilder的方式
拼接操作与append方法的效率对比
通过StringBuilder的append()的方式添加字符串的效率要远高于使用String的字符串拼接方式!
原因:
StringBuilder的append()自始至终只创建一个StringBuilder的对象
使用String的字符串拼接方式创建过多个StringBuilder对象和String对象
使用String的字符串拼接方式:内存中由于创建了较多的StringBuilder和String对象,内存占用更大;如果进行GC需要花费更多的时间
改进空间:
实际开发中,如果基本确定要前前后后添加的字符串长度不超过某个限定值high的情况下,建议使用构造器构造
StringBuilder s = new StringBuilder(high);
new String("ab")创建了几个对象?
public class Test2 {
public static void main(String[] args) {
String str = new String("abc");
}
}
- 两个
- 一个是new的时候在堆空间中开辟的一个新对象
- 另一个是在常量池中新建的"ab"
如何证明?
通过看java编译后的字节码文件
变形
String s = new String("abc") + new String("def");
5个
- new StringBuilder()
- new String("abc")
- 常量池中的"abc"
- new String("def")
- 常量池中的"def"
深入剖析:StringBuilder的toString():
- new String("abcdef")
intern()的使用
如何保证变量s指向的是字符串常量池中的数据?
有两种方式:
String s = "lld";//字面量定义
//调用intern()方法
String s = new String("lld").intern();
String S = new StringBuilder("lld").toString().intern();
String.intern()使用原理
String.intern()是一个Native方法,底层调用C++的 StringTable::intern方法实现。
当通过语句str.intern()调用intern()方法后,JVM 就会在当前类的常量池中查找是否存在与str等值的String
若存在则直接返回常量池中相应Strnig的引用;
若不存在,则会在常量池中创建一个等值的String,然后返回这个String在常量池中的引用。
因此,只要是等值的String对象,使用intern()方法返回的都是常量池中同一个String引用.
JDK6中
Jdk6中常量池位于PermGen(永久代)中,PermGen是一块主要用于存放已加载的类信息和字符串池的大小固定的区域。
执行intern()方法时
若常量池中不存在等值的字符串,JVM就会在常量池中创建一个等值的字符串,然后返回该字符串的引用。
JDK7中
Jdk7将常量池从PermGen区移到了Java堆区.
执行intern操作时
如果常量池已经存在该字符串,则直接返回字符串引用,
否则复制该字符串对象的引用到常量池中并返回。
例如:
String s3 = new String("1") + new String("1");//s3变量记录的地址为: new String("11");
//执行完上一行代码以后,字符串常量池中并不存在"11"
s3.intern();
//在字符串常量池中生成"11".
//jdk6中创建了一个新对象"11",也就有了新地址
//jdk7中此时常量池中并没有创建"11",而是创建了一个指向堆空间中new String("11")的地址
String s4 = "11";
System.out.println(s3 == s4);
//在jdk6中为false 在jdk7/8中为true
BigDecimal
- 位置:java。math包中。
- 作用:精确计算浮点数。
- 创建方式:BigDecimal bd = new BigDecimal("1.2");
BigDecimal bg1 = new BigDecimal("2.2");
BigDecimal bg1 = new BigDecimal("1.2");
BigDecimal result = bg1.add(bg2);//加法
相关题目
1.判定定义为String类型的st1和st2是否相等,为什么
package string;
public class Demo2_String {
public static void main(String[] args) {
String st1 = "abc";
String st2 = "abc";
System.out.println(st1 == st2);
System.out.println(st1.equals(st2));
}
}
输出结果:
第一行:true
第二行:true
分析:
# 第一个打印语句
> 在Java中 == 这个符号是比较运算符,它可以基本数据类型和引用数据类型是否相等
如果是基本数据类型,== 比较的是值是否相等
如果是引用数据类型,== 比较的是两个对象的内存地址是否相等。
字符串不属于8中基本数据类型,字符串对象属于引用数据类型
在上面把“abc”同时赋值给了st1和st2两个字符串对象,指向的都是同一个地址,所以第一个打印语句中的==比较输出结果是 true
# 第二个打印语句中的equals的比较
> 我们知道,equals是Object这个父类的方法,在String类中重写了这个equals方法,在JDK API 1.6文档中找到String类下的equals方法,点击进去可以看到这么一句话“将此字符串与指定的对象比较。当且仅当该参数不为null,并且是与此对象表示相同字符序列的 String 对象时,结果才为 true。” 注意这个相同字符序列,在后面介绍的比较两个数组,列表,字典是否相等,都是这个逻辑去写代码实现。
由于st1和st2的值都是“abc”,两者指向同一个对象,当前字符序列相同,所以第二行打印结果也为true。
下面我们来画一个内存图来表示上面的代码,看起来更加有说服力。
内存过程大致如下:
1)运行先编译,然后当前类Demo2_String.class文件加载进入内存的方法区
2)第二步,main方法压入栈内存
3)常量池创建一个“abc”对象,产生一个内存地址
4)然后把“abc”内存地址赋值给main方法里的成员变量st1,这个时候st1根据内存地址,指向了常量池中的“abc”。
5)前面一篇提到,常量池有这个特点,如果发现已经存在,就不在创建重复的对象
6)运行到代码 Stringst2 =”abc”, 由于常量池存在“abc”,所以不会再创建,直接把“abc”内存地址赋值给了st2
7)最后st1和st2都指向了内存中同一个地址,所以两者是完全相同的。
\2. 下面这句话在内存中创建了几个对象
String st1 = new String(“abc”);
答案是:在内存中创建两个对象,一个在堆内存,一个在常量池,堆内存对象是常量池对象的一个拷贝副本。
分析:
我们下面直接来一个内存图。
当我们看到了new这个关键字,就要想到,new出来的对象都是存储在堆内存。然后我们来解释堆中对象为什么是常量池的对象的拷贝副本。“abc”属于字符串,字符串属于常量,所以应该在常量池中创建,所以第一个创建的对象就是在常量池里的“abc”。第二个对象在堆内存为啥是一个拷贝的副本呢,这个就需要在JDK API 1.6找到String(String original)这个构造方法的注释:初始化一个新创建的 String 对象,使其表示一个与参数相同的字符序列;换句话说,新创建的字符串是该参数字符串的副本。所以,答案就出来了,两个对象。
3.判定以下定义为String类型的st1和st2是否相等
package string;
public class Demo2_String {
public static void main(String[] args) {
String st1 = new String("abc");
String st2 = "abc";
System.out.println(st1 == st2);
System.out.println(st1.equals(st2));
}
}
答案:false 和 true
由于有前面两道提内存分析的经验和理论,所以,我能快速得出上面的答案。==
比较的st1和st2对象的内存地址,由于st1指向的是堆内存的地址,st2看到“abc”已经在常量池存在,就不会再新建,所以st2指向了常量池的内存地址,所以==判断结果输出false,两者不相等。第二个equals比较,比较是两个字符串序列是否相等,由于就一个“abc”,所以完全相等。内存图如下
\4. 判定以下定义为String类型的st1和st2是否相等
package string;
public class Demo2_String {
public static void main(String[] args) {
String st1 = "a" + "b" + "c";
String st2 = "abc";
System.out.println(st1 == st2);
System.out.println(st1.equals(st2));
}
}
答案是:true 和 true
分析:
“a”,”b”,”c”三个本来就是字符串常量,进行+符号拼接之后变成了“abc”,“abc”本身就是字符串常量(Java中有常量优化机制),所以常量池立马会创建一个“abc”的字符串常量对象,在进行st2=”abc”,这个时候,常量池存在“abc”,所以不再创建。所以,不管比较内存地址还是比较字符串序列,都相等。
5.判断以下st2和st3是否相等
package string;
public class Demo2_String {
public static void main(String[] args) {
String st1 = "ab";
String st2 = "abc";
String st3 = st1 + "c";
System.out.println(st2 == st3);
System.out.println(st2.equals(st3));
}
}
答案:false 和 true
分析:
上面的答案第一个是false,第二个是true,第二个是true我们很好理解,因为比较一个是“abc”,另外一个是拼接得到的“abc”,所以equals比较,这个是输出true,我们很好理解。那么第一个判断为什么是false,我们很疑惑。同样,下面我们用API的注释说明和内存图来解释这个为什么不相等。
首先,打开JDK API 1.6中String的介绍,找到下面图片这句话。
关键点就在红圈这句话,我们知道任何数据和字符串进行加号(+)运算,最终得到是一个拼接的新的字符串。上面注释说明了这个拼接的原理是由StringBuilder或者StringBuffer类和里面的append方法实现拼接,然后调用toString()把拼接的对象转换成字符串对象,最后把得到字符串对象的地址赋值给变量。结合这个理解,我们下面画一个内存图来分析。
大致内存过程
1)常量池创建“ab”对象,并赋值给st1,所以st1指向了“ab”
2)常量池创建“abc”对象,并赋值给st2,所以st2指向了“abc”
3)由于这里走的+的拼接方法,所以第三步是使用StringBuffer类的append方法,得到了“abc”,这个时候内存0x0011表示的是一个StringBuffer对象,注意不是String对象。
4)调用了Object的toString方法把StringBuffer对象装换成了String对象。
5)把String对象(0x0022)赋值给st3
所以,st3和st2进行==判断结果是不相等,因为两个对象内存地址不同。
6.判断下面a == c和a == e
String a = "hello2";
final String b = "hello";
String d = "hello";
String c = b + 2;
String e = d + 2;
String f = c.intern();
System.out.println((a == c));
//true 由于变量b被final修饰,因此会被当做编译器常量,在使用到b的地方会直接将变量b替换为它的值“hello”
System.out.println((a == e));
//false d+2相当于是由StringBuilder.append后toString生成了一个新的堆对象,所以为false
System.out.println((a == e));
//intern():判断字符串常量池中是否存在 hello2 这个值,如果存在,则返回常量池当中这个常量的地址
//如果字符串常量池中不存在 hello2 ,则在常量池中加载一份 hello2 ,并返回这个值的地址
答案:true和false
总结:
- 常量与常量的拼接结果在常量池中,原理是编译期优化
- 常量池中不会存在相同内容的常量
- 只要其中有一个是变量,结果就在堆中.变量拼接的原理是StringBuilder
- 如果拼接的结果调用intern()方法,则主动将常量池中还没有的字符串对象放入池中,并返回此对象地址
String相关介绍的更多相关文章
- JAVA基础5——与String相关的系列(1)
与String相关的系列 String, 是JAVA中常见的一个引用类型,且其具有一定的特殊性. String类型被设置为final型,即不可继承,也就不可修改其中的实现. String可以改变吗 S ...
- JAVA基础部分复习(一、8中基础类型,以及String相关内容)
以下是关于java中8种基本类型的介绍说明: package cn.review.day01; /** * java基础复习,8种数据类型 * (byte,short,long,int,double, ...
- Vue 封装axios(四种请求)及相关介绍(十三)
Vue 封装axios(四种请求)及相关介绍 首先axios是基于promise的http库 promise是什么? 1.主要用于异步计算 2.可以将异步操作队列化,按照期望的顺序执行,返回符合预期的 ...
- ppDelegate的相关介绍
// AppDelegate的相关介绍// IOS笔记 //@interface AppDelegate : UIResponder <UIApplicationDelegate>// ...
- 【个人笔记】002-PHP基础-01-PHP快速入门-02-PHP语言相关介绍输
002-PHP基础-01-PHP快速入门 02-PHP语言相关介绍 1.PHP是什么 Hypertext Preprocessor超文本预处理器 是一种通用开源脚本语言 Personal Home P ...
- Android HttpClient HttpURLConnection相关介绍
Android HttpClient HttpURLConnection相关介绍 遇到一个问题 在android studio上用HttpClient编写网络访问代码的时候,发现该类无法导入并使用.. ...
- Android开发工程师文集-Activity生命周期,启动方式,Intent相关介绍,Activity详细讲解
前言 大家好,给大家带来Android开发工程师文集-Activity生命周期,启动方式,Intent相关介绍,Activity详细讲解的概述,希望你们喜欢 Activity是什么 作为一个Activ ...
- CSS3 Backgrounds相关介绍
CSS3 Backgrounds相关介绍 1.背景图片(background images)是在padding-box的左上角落脚安家的,我们可以使用background-position属性改变默认 ...
- 一 hadoop 相关介绍
hadoop 相关介绍 hadoop的首页有下面这样一段介绍.对hadoop是什么这个问题,做了简要的回答. The Apache™ Hadoop® project develops open-sou ...
随机推荐
- C语言结构体及其内存布局
code[class*="language-"], pre[class*="language-"] { color: rgba(51, 51, 51, 1); ...
- springMVC:校验框架:多规则校验,嵌套校验,分组校验;ssm整合技术
知识点梳理 课堂讲义 学习目标 能够阐述表单验证的分类和区别 能够运用表单验证的常用注解 能够编写表单验证的示例 能够编写SSM整合的应用案例 能够总结SSM整合的步骤 1 校验框架 1.1 入门-视 ...
- day03---Vue(04)
一.组件 组件(Component)是 Vue.js 最强大的功能之一. 组件可以扩展 HTML 元素,封装可重用的代码. 组件系统让我们可以用独立可复用的小组件来构建大型应用,几乎任意类型的应用的界 ...
- InlineHook
前言 IATHOOK局限性较大,当我们想HOOK一个普通函数,并不是API,或者IAT表里并没有这个API函数(有可能他自己LoadLibrary,自己加载的),那我们根本就从导入表中找不到这个函数, ...
- Elasticsearch 集群优化-尽可能全面详细
Elasticsearch 集群优化-转载参考1 基本配置 基本配置,5台配置为 24C 125G 17T 的主机,每台主机上搭建了一个elasticsearch节点. 采用的elasticsearc ...
- WPF -- 使用当前进程打开自定义文件的一种方式
问题描述 当双击打开自定义格式的文件时,希望使用当前正在运行的进程,而不是另起一个进程. 本文介绍一种方式解决如上问题,方案参考user3582780的解答 设置自定义文件格式的默认打开方式 参考链接 ...
- Java基础 - List的两个子类的特点
List两个子类的特点 List的两个子类的特点 因为两个类都实现了List接口,所以里面的方法都差不多,那这两个类都有什么特点呢? ArrayList: 底层数据结构是数组,查询快,增删慢. Lin ...
- Lua OpenResty容器化(考古历程)
原文地址:Lua OpenResty容器化(考古历程) 背景 公司有几个"远古时期"的项目,一直都相对较为稳定,但是项目每天总会在一些时段,请求每分钟QPS到达峰值800K左右,导 ...
- java面试系列<4>——IO
面试系列--javaIO 一.概述 java的IO主要分为以下几类: 磁盘操作:File 字节操作:InputStream 和 OutputStream 字符操作:Reader 和 Writer 对象 ...
- Arch! 从安装开始
Arch! 从安装开始 事实上Arch的安装Arch Wiki Installation Guide,已经非常详细了 但是初次面对这些东西时肯定非常迷茫,根本不知道这些东西是在干嘛?为什么要这么干? ...