传一个实体一个string_没想到,一个小小的String还有这么多窍门

e9307adf0c4f4b54248238a5e7789f02.png

1. 看看源码

大家都知道, String 被声明为 final,因此它不可被继承。(Integer 等包装类也不能被继承)。我们先来看看 String 的源码。

在 Java 8 中,String 内部使用 char 数组存储数据。

c6d3ab11f5b9e3ecaa7ca09dfcda9214.png

在 Java 9 之后,String 类的实现改用 byte 数组存储字符串,同时使用 coder 来标识使用了哪种编码。

358a520799c39869c6cd31ad52c32429.png

value 数组被声明为 final,这意味着 value 数组初始化之后就不能再引用其它数组。并且 String 内部没有改变 value 数组的方法,因此可以保证 String 不可变。

2. 不可变有什么好处呢

2.1 可以缓存 hash 值

因为 String 的 hash 值经常被使用,例如 String 用做 HashMap 的 key。不可变的特性可以使得 hash 值也不可变,因此只需要进行一次计算。

2.2 String Pool 的使用

如果一个 String 对象已经被创建过了,那么就会从 String Pool 中取得引用。只有 String 是不可变的,才可能使用 String Pool。

2.3 安全性

String 经常作为参数,String 不可变性可以保证参数不可变。例如在作为网络连接参数的情况下如果 String 是可变的,那么在网络连接过程中,String 被改变,改变 String 的那一方以为现在连接的是其它主机,而实际情况却不一定是。

2.4 线程安全

String 不可变性天生具备线程安全,可以在多个线程中安全地使用。

3. 再来深入了解一下 String

3.1 “+” 连接符

字符串对象可以使用“+”连接其他对象,其中字符串连接是通过 StringBuilder(或 StringBuffer)类及其 append 方法实现的,对象转换为字符串是通过 toString 方法实现的。可以通过反编译验证一下:

8beeb53f853452cc6d786da0f3c44782.png

由上可以看出,Java中使用"+"连接字符串对象时,会创建一个StringBuilder()对象,并调用append()方法将数据拼接,最后调用toString()方法返回拼接好的字符串。那这个 “+” 的效率怎么样呢?

3.2 “+”连接符的效率

使用“+”连接符时,JVM会隐式创建StringBuilder对象,这种方式在大部分情况下并不会造成效率的损失,不过在进行大量循环拼接字符串时则需要注意。比如:

f2a0966d8288dfa3a8e0dee47fba66c2.png

这样由于大量StringBuilder创建在堆内存中,肯定会造成效率的损失,所以在这种情况下建议在循环体外创建一个StringBuilder对象调用append()方法手动拼接(如上面例子如果使用手动拼接运行时间将缩小到1/200左右)。

与此之外还有一种特殊情况,也就是当"+"两端均为编译期确定的字符串常量时,编译器会进行相应的优化,直接将两个字符串常量拼接好,例如:

9cf897ef48433efe900bcab8ba215c54.png

4. 字符串常量

4.1 为什么使用字符串常量?

JVM为了提高性能和减少内存的开销,在实例化字符串的时候进行了一些优化:使用字符串常量池。每当创建字符串常量时,JVM会首先检查字符串常量池,如果该字符串已经存在常量池中,那么就直接返回常量池中的实例引用。如果字符串不存在常量池中,就会实例化该字符串并且将其放到常量池中。由于String字符串的不可变性,常量池中一定不存在两个相同的字符串。

4.2 实现字符串常量池的基础

实现该优化的基础是因为字符串是不可变的,可以不用担心数据冲突进行共享。

运行时实例创建的全局字符串常量池中有一个表,总是为池中每个唯一的字符串对象维护一个引用,这就意味着它们一直引用着字符串常量池中的对象,所以,在常量池中的这些字符串不会被垃圾收集器回收。

我们来看个小例子,了解下不同的方式创建的字符串在内存中的位置:

bb92f59492e404751dd641e2b69f7291.png

24f8e3dc8ebe5c88e55f79913762e7b5.png

5. String类常见的面试题

5.1 判断字符串s1和s2是否相等

4706ac3839765871e47b1a505650d075.png

解析:

s1和s2:

String s1 = "123";先是在字符串常量池创建了一个字符串常量“123”,“123”常量是有地址值,地址值赋值给s1。接着声明 String s2=“123”,由于s1已经在方法区的常量池创建字符串常量"123",进入常量池规则:如果常量池中没有这个常量,就创建一个,如果有就不再创建了,故直接把常量"123"的地址值赋值给s2,所以s1==s2为true。

由于String类重写了equals方法,s1.equals(s2)比较的是字符串的内容,s1和s2的内容都是"123",故s1.equals(s2)为true。

s3和s4:

s3创建了一个新的字符串"1234",s4是两个新的字符串"12"和"34"通过"+“符号连接所得,根据Java中常量优化机制, “12” 和"34"两个字符串常量在编译期就连接创建了字符串"1234”,由于字符串"1234"在常量池中存在,故直接把"1234"在常量池的地址赋值给s4,所以s3==s4为true。

s3和s5:

s5是由一个变量s1连接一个新的字符串"4",首先会在常量池创建字符串"4",然后进行"+“操作,根据字符串的串联规则,s5会在堆内存中创建StringBuilder(或StringBuffer)对象,通过append方法拼接s1和字符串常量"4”,此时拼接成的字符串"1234"是StringBuilder(或StringBuffer)类型的对象,通过调用toString方法转成String对象"1234",所以s5此时实际指向的是堆内存中的"1234"对象,堆内存中对象的地址和常量池中对象的地址不一致,故s3==s5为false。

看下JDK8的API文档里的解释:

Java语言为字符串连接运算符(+)提供特殊支持,并为其他对象转换为字符串。字符串连接是通过StringBuilder (或StringBuffer )类及其append方法实现的。字符串转换是通过方法来实现toString,由下式定义0bject和继承由在Java中的所有类。有关字符串连接和转换的其他信息,请参阅Gosling,Joy 和Steele,Java 语言规范。

不管是常量池还是堆,只要是使用equals比较字符串,都是比较字符串的内容,所以s3.equals(s5)为true。

Java常量优化机制:给一个变量赋值,如果等于号的右边是常量,并且没有一个变量,那么就会在编译阶段计算该表达式的结果,然后判断该表达式的结果是否在左边类型所表示范围内,如果在,那么就赋值成功,如果不在,那么就赋值失败。但是注意如果一旦有变量参与表达式,那么就不会有编译期间的常量优化机制。

s3和s6:

String s6 = new String("1234");在堆内存创建一个字符串对象,s6指向这个堆内存的对象地址,而s3指向的是字符串常量池的"1234"对象的地址,故s3==s6为false。

e7333a3c0951334098ad56b50e7889c7.png

5.2 创建多少个字符串对象?

780fcea00c68ecdda98cf7d44eaebcce.png

解析:

String s0 = “123”;

字符串常量池对象:“123”,1个;

共1个。

String s1 = new String(“123”);

字符串常量池对象:“123”,1个;

堆对象:new String(“123”),1个;

共2个。

String s2 = new String(“1” + “2”);

字符串常量池对象:“12”,1个(Jvm在编译期做了优化,“1” + "2"合并成了 “12”);

堆对象:new String(“12”),1个

共2个。

由于s2涉及字符串合并,我们通过命令看下字节码信息:

ef9917a685e0fe33d6f361e7e5939e3a.png

得到字节码信息如下:

fff68834c0406fd3afccda75b3e56f2f.png

我们可以很清晰看到,创建了一个新的String对象和一个字符串常量"12",new String("1" + "2") 相当于 new String("12"),共创建了2个字符串对象。

String s3 = new String(“12”) + “3”;

字符串常量池对象:“12”、“3”,2个,

堆对象: new Stringbuilder().append(“12”).append(“3”).toString();转成String对象,1个;

共3个。

我们同样看下编译后的结果:

e2bcf58e9090843fd65199c42e6cc58b.png

可以看到,包括StringBuilder在内,共创建了4个对象,字符串"12"和字符串"3"是分开创建的,所以共创建了3个字符串对象。

总结:

new String()是在堆内存创建新的字符串对象,其构造参数中可传入字符串,此字符串一般会在常量池中先创建出来,new String()创建的字符串是参数字符串的副本,看下API中关于String构造器的解释:

String(String original)

初始化新创建的String对象,使其表示与参数相同的字符序列;换句话说,新创建的字符串是参数字符串的副本。

所以new String()的方式创建字符串百分百会产生一个新的字符串对象,而类似于"123"这样的字符串对象则需要在创建之前看常量池中有没有,有的话就不创建,没有则创建新的对象。 "+"操作符连接字符串常量的时候会在编译期直接生成连接后的字符串,若该字符串在常量池已经存在,则不会创建新的字符串;连接变量的话则涉及StringBuilder等字符串构建器的创建,会在堆内存生成新的字符串对象。

以上就是我们给您带来的关于Java字符串的一些知识总结和面试技巧,你学废了吗?

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/332837.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

envoy api 网关_在边缘,作为网关或在网格中构建控制平面以管理Envoy代理的指南...

envoy api 网关最近, Envoy已成为流行的网络组件。 马特克莱因( Matt Klein )在几年前写了一个博客,内容涉及Envoy的动态配置API,以及它如何成为Envoy的采用曲线向右移的部分原因。 他称该博客为“通用数据平面API”。…

d3.js和mysql_如何从mysql数据库中提取数据并使用D3.JS进行可视化?

以下是一个php脚本,您应该可以将其保存在某个文件中(假设您称之为“getdata.php”),可以从您的HTML文件中访问,其中包含D3代码.调用时,它将以json格式从MySQL数据库返回数据(只要数据库服务器不在您的域之外);$username "******";$password "******";$ho…

apigee 安装_APIGEE – API网关简介

apigee 安装在本文中,我想简要介绍一下APIGEE。 APIGEE主要提供现成的以下功能作为api网关。 协议转换 与任何协议(包括SOAP,REST,XML二进制或自定义)进行转换 交通管理 开箱即用的灵活,分布式配额管理…

linux mysql 忘记root密码_Linux MySQL忘记root密码解决方案

在使用MySQL数据库时,由于某些原因长时间没有登陆MySQL,或者由于工作交接完成度不高,导致数据库root登陆密码忘记,如何解决?一、更改my.cnf配置文件1、用命令编辑/etc/my.cnf配置文件,即:vim /e…

Java编程中如何获取项目文件的路径/文件路径

// 获取类加载器 ClassLoader cl JDBCUtils.class.getClassLoader(); // 获取文件的统一资源定位器对象 URL url cl.getResource("db_config.properties"); // 获取文件的路径 String path url.getPath();

inmemory_通过Spring Boot了解H2 InMemory数据库

inmemory介绍 基本上,数据库设置涉及几个步骤,然后才能在应用程序中通过已配置的数据源使用它。 在实际项目实施中,这实际上是必需的。 但是,在某些情况下,我们只需要为某件事完成POC,并且整个数据库设置工…

mysql 查询分组平均数_9、mysql分组查询-----group by 和 having

举例说明:有 Store_Information表之前我们根据这个表,用函数可以算出sales的总和,平均数等如果现在我们需要算出每一间店(store_name)的销售总额(sales)呢?比如:1、los angeles 销售额加起来是 18000san diego…

macOS彻底卸载/删除Microsoft相关的程序

cd /Library/Application\ Support/ # 可以将目录Microsoft整个删除 sudo rm -rf Microsoft # 如果要单独删除微软的自动更新程序,可以进入 cd Microsoft/MAU2.0 # 删除程序文件Microsoft AutoUpdate.app sudo rm -rf Microsoft\ AutoUpdate.app # 进入~/Library/Pr…

java 字符串文字筛选_重新开始Java的原始字符串文字讨论

java 字符串文字筛选在2018年12月宣布 将从JDK 12中删除原始字符串文字 。 现在,在新的一年中,与Java中原始字符串文字的设计有关的讨论又开始了。 在琥珀色专家OpenJDK邮件列表上的“ 原始字符串文字-重新开始讨论 ”一文中 ,Brian Goetz参…

mysql event 日志_MySQL Event计划任务刷慢日志

前言最近在尝试一个日志系统graylog来收集mysql的慢查询日志提,供后续的分析、监控和报警等。测试步骤已经到日志已成功收集到graylog,测试时需要刷一些慢查询日志出来。为了刷比较多的日志和不对测试环境造成较大的影响,想到了使用mysql的sl…

aws lambda_API网关和AWS Lambda进行身份验证

aws lambda当Foreach最初踏上微服务之路时,我们并没有真正构建微服务。 我们以为我们做到了,但是我们所有的服务中总存在一些逻辑。 当然,每个服务实际上应该只专注于自己的任务,而不应该专注于属于另一个微服务的事物。 我们这方…

refreshtoken用mysql_微信access_token和refresh_token保存于redis

此处以保存用户授权access_token为例,接口调用access_token可在项目启动时进行缓存。部分代码如下:Autowiredprivate RedisTemplate redisTemplate;Value("${myapp.redisWxUserAccessToken}")private String redisWxUserAccessToken;// 获取pu…

JDBC的基本使用

文章目录概念基本使用步骤详解各个对象DriverManager注册驱动获取数据库连接Connection获取执行 SQL 语句的对象事务管理Statement执行 SQL 语句ResultSetPreparedstatement概念 Java DataBase Connectivity Java 数据库连接,即使用 Java 语言操作数据库 JDBC本质…

sqlrelay mysql_php+sqlrelay+mysql实现连接池及读写负载均衡

phpsqlrelaymysql实现连接池及读写负载均衡上一篇 /下一篇 2008-04-02 18:25:19/ 个人分类:MySQL作者:ziqiusqlrelay.jpg(20.33 KB)2007-8-31 14:26在大型的web应用中数据库经常成为并发访问的一个瓶颈,为了有效的解决并发访问的瓶颈&#x…

maven 文件上传下载_使用Maven将文件上传和下载到S3

maven 文件上传下载多年来,我已经看到许多团队以许多不同的方式使用Maven。 Maven可用于许多ci / cd任务,而无需使用额外的管道代码,或者可用于在运行某些测试之前准备开发环境。 通常,它是一种方便的工具,在Java团队…

JDBC的事务管理

文章目录事务定义操作步骤示例代码事务定义 一个包含多个步骤的业务操作。如果这个业务操作被事务管理,则这多个步骤要么同时成功,要么同时失败。 操作步骤 使用 Connection 对象来管理事务。 1.开启事务 2.提交事务 3.回滚事务 开启事务: setAutoC…

s71200模拟量输入输出_模拟用户输入并检查输出的简单方法

s71200模拟量输入输出最近,我的一些学生向我询问了赫尔辛基大学MOOC提供的单元测试的机制,我检查了它们的实现,并认为这对于初学者了解实际发生的情况是有帮助的,因此在此发表了这篇小文章。 我们将以“机场”项目为例&#xff0…

mysql设计一个简单的系统_一个简单数据库设计例子

一个曾经做过的简单的管理系统中数据库设计的例子,包括设计表、ER图、建模、脚本. 项目信息 Project Name: Book Manager System DB: MySQL5.5 DB Name: db_library Tables: 1). tb_book_info 2). tb_user 3). tb_admin_info 4). tb_borrow_return 5). tb_boo一个曾经做过的简单…

数据库连接池_DataSource_数据源(简单介绍C3P0和Druid)

文章目录概念好处实现C3P0基本使用配置文件druid下载 jar 包配置文件演示代码定义工具类示例代码概念 数据库连接池其实就是一个容器,而这个容器其实就是一个集合,这个集合存放着多个数据库连接对象。 系统在初始化的时候,将创建一定数量的数…