字符串在Java生产开发中的使用频率是非常高的,可见,字符串对于我们而言非常关键。那么从C语言过来的同学会发现,在C中是没有String类型的,那么C语言要想实现字符串就必须使用char数组,通过一个个的字符来组拼成字符串。

Java中是如何实现字符串的

那其实在Java中,关于字符串的实现,其实用的也是char数组,这可以从源码中得到体现。

/** * Initializes a newly created {@code String} object so that it represents * the same sequence of characters as the argument; in other words, the * newly created string is a copy of the argument string. Unless an * explicit copy of {@code original} is needed, use of this constructor is * unnecessary since Strings are immutable. * * @param original * A {@code String} */ public String(String original) { this.value = original.value; this.hash = original.hash; }复制代码

这是String类的构造方法,而这个value实际上就是char数组。

/** The value is used for character storage. */ private final char value[];复制代码

字符串在内存中的保存方式

我们都知道如何去创建一个字符串,那么, 字符串在内存中的保存方式是怎样的呢?在内存中有一个区域叫做常量池,而当我们以这样的方式去创建字符串:

String s1 = "abc";String s2 = "abc";复制代码

这个字符串就一定会被保存到常量池中。而Java虚拟机如果发现常量池中已经存在需要创建的字符串中,它就不会重复创建,而是指向那个字符串即可。

String s1 = "abc";String s2 = "abc";System.out.println(s1 == s2);复制代码

所以上述代码段的执行结果一定是true。但是如果使用new关键字区创建字符串,过程就不太一样了。比如下面的声明:

String s3 = new String("abc");String s4 = new String("abc");复制代码

过程是这样的:首先将abc保存在常量池中,此时并没有引用,然后new关键字会去创建一个字符串对象,就会在堆内存中创建abc,然后s3变量指向abc。当执行第二句声明时,因为常量池中已经存在abc,所以不会重复创建,而new关键字又会去堆内存开辟空间存放abc,然后s4变量指向abc。

String s3 = new String("abc");String s4 = new String("abc");System.out.println(s3 == s4);复制代码

所以上述代码段的执行结果一定是false。

字符串驻留

当相同的字符串常量被多次创建时,注意是使用双引号(" ")显式声明时,字符串常量对象会被保存在常量池中,且只会创建一个对象,这就是字符串驻留,这个名词的产生就是为了提升性能。简单提一下,字符串中有一个方法叫做intern();那么这个方法有什么作用呢? 该方法会去常量池中寻找当前调用该方法的字符串常量,若找到,则直接返回该字符串对象,若没有,则将当前字符串放入常量池并返回,总之该方法一定会返回字符串。

String s3 = new String("abc");String s4 = new String("abc");System.out.println(s3.intern() == s4.intern());复制代码

所以上述代码段的执行结果一定是true,因为字符串驻留只允许常量池中一个相同字符串的存在。

JVM内存结构

刚才一直在说常量池,那么常量池具体在哪呢?这就要来研究一下JVM的内存结构。JVM分为堆、栈、方法区,栈又分为本地方法栈和Java栈。

在Java7之前常量池就放在方法区里,而从Java7开始,常量池被移到了堆。这样说过于抽象,我们可以通过代码来感受这一过程。

String s1 = new String("hello") + new String("world");String s2 = "helloworld";System.out.println(s1 == s2);复制代码

上述程序段的执行结果一定是false。因为s1变量在堆中,而s2变量在常量池中,两者肯定不相同。那么看下面这段代码,猜猜看结果是什么?

String s1 = new String("hello") + new String("world");System.out.println(s1.intern() == s1);复制代码

按照刚才的分析,intern()返回的一定是常量池里的字符串,而s1变量在堆中,它们肯定是不一样的,但运行结果竟然是true。那是不是就能解释常量池在堆中,所以它们指向的是同一个对象呢?其实还不完全是,我们可以继续看一段代码。

String s1 = new String("hello") + new String("world");System.out.println(s1.intern() == s1);String s2 = new String("hello") + new String("world"); System.out.println(s2.intern() == s2);复制代码

这段代码的运行结果:

truefalse复制代码

感觉很神奇,让人猜不透,摸不着。别急,下面我们来一起分析一下。

通过这个图来理解一下,首先第一行代码会在常量池中创建hello和world两个字符串,接着在堆中开辟了一个空间存放组合后的字符串helloworld,然后变量s1指向它。我们说intern()会返回常量池中的字符串,那么在常量池中没有helloworld的情况下intern()方法会怎样处理呢?其实它会将对堆中helloworld的引用放入常量池中,此时s1.intern()和s1都指向的是同一个对象,它们是相等的。但是s2在创建的过程中也会在堆中开辟一个空间存放helloworld,使变量s2指向它,而s2.intern()方法在执行的时候发现,helloworld的引用已经存在,所以直接返回,但此时返回的其实是s1变量的引用,那么s2.intern()与s2不相等相信大家能够理解了。

String s1 = new String("hello") + new String("world");System.out.println(s1.intern() == s1);String s2 = new String("hello") + new String("world");System.out.println(s2.intern() == s1);复制代码

那么这段程序的输出结果你若是能立马知晓,那么恭喜你,前面的知识点你已基本掌握。执行结果就是:

truetrue复制代码

我们还可以通过一个极端的方法来判断常量池的位置。

List list = new ArrayList();String str = "boom";for(int i = 0;i < Integer.MAX_VALUE;i++) { String temp = str + i; str = temp; list.add(temp.intern()); }复制代码

通过编写这一段程序能够让JVM去不停地将字符串变量存入常量池从而使其内存溢出,内存溢出后控制台信息如下:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at java.util.Arrays.copyOfRange(Arrays.java:2694) at java.lang.String.(String.java:203) at java.lang.StringBuilder.toString(StringBuilder.java:405) at com.itcast.test2.StringTest.main(StringTest.java:25)复制代码

可以看到,控制台信息提示堆内存溢出,这也可以得出常量池的位置是在堆内。这是Java7及其以后版本的输出信息,当我们将版本切换为Java7之前的版本,同样的代码,输出信息如下:

Exception in thread "main" java.lang.OutOfMemoryError: PermGen space at java.util.Arrays.copyOfRange(Arrays.java:2694) at java.lang.String.(String.java:203) at java.lang.StringBuilder.toString(StringBuilder.java:405) at com.itcast.test2.StringTest.main(StringTest.java:25)复制代码

PermGen space其实就是方法区, 那么其实在JVM中的堆,一般分为三大部分:新生代、老年代、永久代:这个PermGen space就是永久代,也就是方法区,叫法不同而已。

其它问题

继续来探讨一下关于字符串常量的一些其它问题。

String s1 = "hello" + "world";String s2 = "helloworld";System.out.println(s1 == s2); String temp = "hello";String s3 = temp + "world";String s4 = "helloworld";System.out.println(s3 == s4);复制代码

那么,这两个输出的结果是什么呢?结果是:

truefalse复制代码

第一个输出为true不难理解,因为s1和s2指向的都是常量池中的helloworld字符串,那么s3和s4难道就不是吗?它还真就不是这样了。s3在创建过程中会将temp保存在堆内存中,所以s3和s4指向的对象不是同一个。我们可以通过反编译来证实,将这段代码的.class文件进行反编译,结果如下:

String s1 = "helloworld";String s2 = "helloworld"; System.out.println(s1 == s2); String temp = "hello";String s3 = String.valueOf(temp) + "world";String s4 = "helloworld";System.out.println(s3 == s4);复制代码

我们可以看到,s1和s2的创建过程其实是一模一样的,其实,JVM为了优化速度,当它确定是两个字符串常量进行拼接时,它会在编译器就完成拼接,而并不会去创建对象处理,但是s3的创建要经过temp变量,因为JVM无法在编译期就推测出temp,所以它要通过String对象来进行处理,将temp放入堆内存。所以,并不是说只有出现new关键字变量才会放入堆内存中。

希望这篇文章能够使你更加深入地理解字符串常量。

java 字符串第一个字符_深入Java源码剖析之字符串常量相关推荐

  1. java实现gdal栅格矢量化,《GDAL源码剖析与开发指南》一一1.5 GDAL源码目录

    本节书摘来自异步社区出版社<GDAL源码剖析与开发指南>一书中的第1章,第1.5节,作者:李民录 更多章节内容可以访问云栖社区"异步社区"公众号查看. 1.5 GDAL ...

  2. java 克隆对象工具类_关于dorado-core源码包中CloneUtils克隆工具类对对象进行克隆复制操作...

    一.前言 基于dorado.core.jar开源包中的com.bstek.dorado.util.CloneUtils克隆工具类,对对象进行通过java.lang.reflect.Method映射克隆 ...

  3. Java Review - 并发编程_并发List_CopyOnWriteArrayList源码剖析

    文章目录 概述 源码解析 初始化 添加元素 获取指定位置元素 修改指定元素 删除元素 弱一致性的迭代器 CopyOnWriteArrayList 是如何实现弱一致性的 小结 概述 并发包中的并发Lis ...

  4. java显示长度和第一个字符_从Java字符串中以长度1的字符串返回的第一个字母的最佳方法是什么?...

    假设以下内容: String example = "something"; String firstLetter = ""; 以下分配方式firstLetter ...

  5. java扑克牌类游戏下载_【参考源码】Java入门第三季 7-1 简易扑克牌游戏

    先创建 PokerCard 扑克牌类,存放牌的花色.大小,还有一个关键值weight,用于定义52张牌的排位大小,这样就避免了后面比较双方牌大小的繁琐步骤 public class PokerCard ...

  6. stl源码剖析_《STL源码剖析》学习笔记——空间配置器

    目录 1. 空间配置器概述 2. 构造和析构基本工具 3. 空间的配置与释放,std::alloc 4. 内存基本处理工具 1. 空间配置器概述 从STL的实现角度来看,空间配置器的位置尤为重要,整个 ...

  7. stl源码剖析_《STL源码剖析》学习笔记

    第二章 空间配置器 简述空间配置器: 关于一级空间配置器: 直接使用malloc.free.realloc进行内存管理操作.且在内存不足时,会陷入oom_malloc,即模拟C++的set_new_h ...

  8. python源码剖析读书笔记总结_《Python源码剖析》读书笔记:内存垃圾回收

    Python内存回收的基石是引用计数,"当一个对象的引用被创建或复制时,对象的引用技术加1:当一个对象的引用被销毁时,对象的引用技术减1",如果对象的引用计数减少为0,将对象的所占 ...

  9. 【java】java 实现 将 字符串 第一个字符 大写 或者 小写

    1.概述 java 实现 将 字符串 第一个字符 大写 或者 小写 /*** 第一个字符串的字符大写** @param string* @return*/static String capitaliz ...

最新文章

  1. 《Cacti实战》——第1章 认识Cacti
  2. 【learning】矩阵树定理
  3. Excel随机选取指定数据
  4. Nginx的安装与部署
  5. 想拥有最新的微软嵌入式技术 就赶快加入微软嵌入式专家社区吧!
  6. 用 Apache 发布 ASP.NET 网站
  7. hdu 1116 欧拉路
  8. QCon速递:Xen漏洞热补丁修复、异地双活、ODPS新功能与金融互联网
  9. oracle开机启动脚本
  10. php.exe系统错误,PhpStorm中报 “Cannot run program git.exe, 系统找不到指定的文件” 错误的解决方法...
  11. getifaddrs
  12. web全栈工程师必备技能
  13. 斗鱼直播与熊猫直播竞品分析
  14. Tableau性能提升
  15. PL/SQL编程:过程函数触发器题目分析
  16. Mac电脑键盘(ujmiko)不能使用问题记录
  17. 小米回应年底裁员:事情确实有,规模没那么大;华为关闭其在俄罗斯的企业事业部|极客头条...
  18. Git同步和撤销命令
  19. MacBook无线鼠标
  20. RED5流媒体服务器作为客户端转发流至另一个RED5服务器

热门文章

  1. html里span和div,HTML div和span
  2. 刷新页面,无论点击多少次让Element UI的Message消息提示弹出一个
  3. XPath和lxml类库
  4. 非常好的C语言章节习题集带答案,非常好的C语言章节习题集带答案选编.doc
  5. 李宏毅机器学习(七)GPT的野望
  6. 史上最清楚的BP算法详解
  7. Spring Boot中使用MongoDB的连接池配置
  8. 【深度揭秘】百度、阿里、腾讯内部岗位级别和薪资结构,附带求职建议!
  9. 论文浅尝 | 重新审视语言模型与知识库的关系
  10. 论文浅尝 | 利用 KG Embedding 进行问题回答