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放到了堆空间中

  1. 因为永久代的回收效率很低,在full gc的时候才会触发.而full gc是老年代的空间不足,永久代不足时才会触发.

这就导致了StringTable回收效率不高.

  1. 而我们开发中会有大量的字符串被创建,回收效率低,导致永久代内存不足,放到堆里,能及时回收内存.

常用方法

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的字符串拼接方式!

    原因:

    1. StringBuilder的append()自始至终只创建一个StringBuilder的对象

      使用String的字符串拼接方式创建过多个StringBuilder对象和String对象

    2. 使用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个

    1. new StringBuilder()
    2. new String("abc")
    3. 常量池中的"abc"
    4. new String("def")
    5. 常量池中的"def"

    深入剖析:StringBuilder的toString():

    1. new String("abcdef")


intern()的使用

如何保证变量s指向的是字符串常量池中的数据?

有两种方式:

  1. String s = "lld";//字面量定义
  2. //调用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

总结:

  1. 常量与常量的拼接结果在常量池中,原理是编译期优化
  2. 常量池中不会存在相同内容的常量
  3. 只要其中有一个是变量,结果就在堆中.变量拼接的原理是StringBuilder
  4. 如果拼接的结果调用intern()方法,则主动将常量池中还没有的字符串对象放入池中,并返回此对象地址

String相关介绍的更多相关文章

  1. JAVA基础5——与String相关的系列(1)

    与String相关的系列 String, 是JAVA中常见的一个引用类型,且其具有一定的特殊性. String类型被设置为final型,即不可继承,也就不可修改其中的实现. String可以改变吗 S ...

  2. JAVA基础部分复习(一、8中基础类型,以及String相关内容)

    以下是关于java中8种基本类型的介绍说明: package cn.review.day01; /** * java基础复习,8种数据类型 * (byte,short,long,int,double, ...

  3. Vue 封装axios(四种请求)及相关介绍(十三)

    Vue 封装axios(四种请求)及相关介绍 首先axios是基于promise的http库 promise是什么? 1.主要用于异步计算 2.可以将异步操作队列化,按照期望的顺序执行,返回符合预期的 ...

  4. ppDelegate的相关介绍

    //  AppDelegate的相关介绍//  IOS笔记 //@interface AppDelegate : UIResponder <UIApplicationDelegate>// ...

  5. 【个人笔记】002-PHP基础-01-PHP快速入门-02-PHP语言相关介绍输

    002-PHP基础-01-PHP快速入门 02-PHP语言相关介绍 1.PHP是什么 Hypertext Preprocessor超文本预处理器 是一种通用开源脚本语言 Personal Home P ...

  6. Android HttpClient HttpURLConnection相关介绍

    Android HttpClient HttpURLConnection相关介绍 遇到一个问题 在android studio上用HttpClient编写网络访问代码的时候,发现该类无法导入并使用.. ...

  7. Android开发工程师文集-Activity生命周期,启动方式,Intent相关介绍,Activity详细讲解

    前言 大家好,给大家带来Android开发工程师文集-Activity生命周期,启动方式,Intent相关介绍,Activity详细讲解的概述,希望你们喜欢 Activity是什么 作为一个Activ ...

  8. CSS3 Backgrounds相关介绍

    CSS3 Backgrounds相关介绍 1.背景图片(background images)是在padding-box的左上角落脚安家的,我们可以使用background-position属性改变默认 ...

  9. 一 hadoop 相关介绍

    hadoop 相关介绍 hadoop的首页有下面这样一段介绍.对hadoop是什么这个问题,做了简要的回答. The Apache™ Hadoop® project develops open-sou ...

随机推荐

  1. C语言结构体及其内存布局

    code[class*="language-"], pre[class*="language-"] { color: rgba(51, 51, 51, 1); ...

  2. springMVC:校验框架:多规则校验,嵌套校验,分组校验;ssm整合技术

    知识点梳理 课堂讲义 学习目标 能够阐述表单验证的分类和区别 能够运用表单验证的常用注解 能够编写表单验证的示例 能够编写SSM整合的应用案例 能够总结SSM整合的步骤 1 校验框架 1.1 入门-视 ...

  3. day03---Vue(04)

    一.组件 组件(Component)是 Vue.js 最强大的功能之一. 组件可以扩展 HTML 元素,封装可重用的代码. 组件系统让我们可以用独立可复用的小组件来构建大型应用,几乎任意类型的应用的界 ...

  4. InlineHook

    前言 IATHOOK局限性较大,当我们想HOOK一个普通函数,并不是API,或者IAT表里并没有这个API函数(有可能他自己LoadLibrary,自己加载的),那我们根本就从导入表中找不到这个函数, ...

  5. Elasticsearch 集群优化-尽可能全面详细

    Elasticsearch 集群优化-转载参考1 基本配置 基本配置,5台配置为 24C 125G 17T 的主机,每台主机上搭建了一个elasticsearch节点. 采用的elasticsearc ...

  6. WPF -- 使用当前进程打开自定义文件的一种方式

    问题描述 当双击打开自定义格式的文件时,希望使用当前正在运行的进程,而不是另起一个进程. 本文介绍一种方式解决如上问题,方案参考user3582780的解答 设置自定义文件格式的默认打开方式 参考链接 ...

  7. Java基础 - List的两个子类的特点

    List两个子类的特点 List的两个子类的特点 因为两个类都实现了List接口,所以里面的方法都差不多,那这两个类都有什么特点呢? ArrayList: 底层数据结构是数组,查询快,增删慢. Lin ...

  8. Lua OpenResty容器化(考古历程)

    原文地址:Lua OpenResty容器化(考古历程) 背景 公司有几个"远古时期"的项目,一直都相对较为稳定,但是项目每天总会在一些时段,请求每分钟QPS到达峰值800K左右,导 ...

  9. java面试系列<4>——IO

    面试系列--javaIO 一.概述 java的IO主要分为以下几类: 磁盘操作:File 字节操作:InputStream 和 OutputStream 字符操作:Reader 和 Writer 对象 ...

  10. Arch! 从安装开始

    Arch! 从安装开始 事实上Arch的安装Arch Wiki Installation Guide,已经非常详细了 但是初次面对这些东西时肯定非常迷茫,根本不知道这些东西是在干嘛?为什么要这么干? ...