Java深度学习系列——深入学习String

前言:

我是张哲,一位在互联网上不愿透露姓名的小学员,接下来大家看到的所有内容都是我背写的知识点,这里的知识点和你所学习到的不同,我中和了我的一些书籍和网上刷的面试笔记,相信这里能让你接触到更深入的知识点,我会慢慢的把我对某个知识点的理解写进去。

String字符串本质上就是个Java类,所属于java.lang包下。

注:Java中有两个包不用导入,一个是本包,一个是java.lang包,前者是package关键字后的包,无需导入,后者是JVM在类加载时给我们导入的包,也无需导入,当然也可以选择去手动导入java.lang包。

String的特性
  • 不可变特性
  • 不可继承
  • 常量池优化
不可变特性

原因是我们在创建String类空间的时候它的构造方法会将String字符串写到自己
本类的char类型的数组中,由于这个数组是 private final char[] value类型的数组
数组的长度不可变,final修饰的内存地址不可变,private修饰的外类不可访问
所以这就有了String的不可变特性,但其实可以通过反射的方式改变。

不可继承

由于String类是final修饰的所以不允许继承String类

常量池优化

引用类型中String创建的最频繁,所以String类可以通过字面量赋值的方式创建空间
比如:String str=“张哲”; 会在字符串常量池中开辟空间,如果字符串常量池中有了
“张哲” 这个字符串则不需要开辟空间。
这里重点讨论下字符串常量池存放在哪里?

  1. Java6中存放在方法区的常量缓冲区中
  2. Java7中将常量缓冲区放入堆内存中,因此Java7中字符串常量池在堆内存
  3. Java8中堆内存取消了永久代的说明,更名为元空间,但字符串常量池此时还是在堆内存中

小知识拓展
  • 堆内存Java8之前分为:永久代、新生代、老生代,Java8后取消了永久代,所以Java8的堆内存分为:新生代、老生代
  • 元空间是物理内存,理论上可以把内存条上的所有剩余空间归属于元空间,所以元空间不容易发生内存溢出
  • Java8中就没有了方法区这么一说,合并永久代,更名元空间

字符串的不可变特性有哪些好处:
  • 节省内存空间,只有字符串是不可变的字符串常量池才能实现,节省了很多堆内存的空间
  • 多线程安全,多个线程之间可以说只能去访问这个资源,无法修改它
  • 避免了漏洞,IP地址和端口号是字符串形式,不可变的,否则黑客可以改变字符串对象的值,造成安全漏洞
  • 用字符串做key键,这样hashCode值就被缓存了,不需要重新计算,所以字符串很适合做缓存中的key

下面让我们看一段代码
String str1="abc,def";
String str2="abc"+",def";
String str3="abc";
str3+=",def";
System.out.println(str1==str2);
System.out.println(str1==str3);
最终的结果返回:true   false

原因:判断str1和str2的结果答案在上面,而str3在执行+=的时候地址会改变

+和concat()方法的区别:

这个问题很细节很细节,首先要看 + 有哪些作用

  1. 正号(正数的默认,可不写)
  2. 计算结果
  3. 连接符
    而concat()方法只能做字符串连接,但是Java有了+为什么还要有concat方法呢?下面看这个例子:
String str1="";
String str2="";
long data1=System.currentTimeMillis();
for(Integer i=1;i<=50000;i++){
	str1=str1+i.toString();
}
data1=System.currentTimeMillis()-data1;
System.out.println(data1);

long data2=System.currentTimeMillis();
for(Integer i=1;i<=50000;i++){
	str2=str2.concat(i.toString());
}
data2=System.currentTimeMillis()-data2;
System.out.println(data2);
最终返回的结果为  5532   2474

发现:concat方法在拼接字符串时效率明显优于 + ,原因是字符串有着它的不可变特性,我们每次拼接字符串都会新建一个字符串对象,而字符串对象空间产生会起码在里面产生char类型的数组,这里会很消耗性能,而concat方法拼接的时候会频繁的操作小数组,在去拼接,这样的效率要比 + 好很多。因此我们在拼接字符串的时候建议使用concat方法,如果拼接很频繁的话建议使用可变性字符串(StringBuffer和StringBuilder)。
不是说 + 在拼接的时候没有concat方法好,只不过 + 在拼接字符串的时候确实没有concat方法效率高,但是 + 还可以拼接数值、null、boolean等等。

String str1=null和String str2=“”的区别:

前者在字符串常量池中并没有去创建空间,只不过在栈内存开辟了存放str1的空间,后者是空串,在字符串常量池中创建了个空间,只不过空间的内容为空,所以这里要特别注意的是前者不允许访问任何的属性和方法,否则发生空指针异常。


##### StringBuffer和StringBuilder的区别: StringBuffer线程安全,但是执行效率不高 StringBuilder执行效率高,但非线程安全 知其然更要知其所以然,这样才能在平时吊打所有对手,因此我告诉你原因(下面这条建议有了线程基础在看)
StringBuffer的方法加了synchronized关键字,可以做到线程同步,但是这样会影响性能
StringBuilder的方法没有synchronized关键字,这样性能会更高,但是非线程安全。
接下来吧这句话说出来。。。我估计就能让面试官刮目相看了:
其实不光StringBuffer和StringBuilder我发现Java早起版本和后来版本基本都是这个区别
StringBuffer是早起版本,在JDK1.0的时候就有了,而StringBuilder是JDK1.5的新特性,早期版本基本都是线程安全,但效率不高,后来版本都是效率高,但非线程安全,集合中的Vector和ArrayList也是这个道理。

关于StringBuilder的非线程安全的解决方案:

首先我们要先闹明白为什么有了StringBuffer后还要有StringBuilder,因为很多时候我是单线程,或者我即便是多线程也不一定要去抢共享的资源,所以这些情况下我没有必要担心线程安全,反而还影响我的运行效率,于是StringBuilder就诞生了。

可是我又想使用StringBuilder,有在调用它某个方法时必须保证线程安全,我该怎么办呢???

别急,阿哲同学给你提供两种结局方案:

  • 给他来个中间过渡方法,调用这个方法之前,把它单独放到过渡的方法里作为执行体,然后我们将这个过渡方法加上synchronized关键字——完美
  • 给它来个同步代码块(synchronized的第二种控制线程同步的方式)

可变字符串(StringBuilder、StringBuffer)和不可变字符串(String)的区别:

首先看到这个问题,你可能感到我在开玩笑,有病啊写这么个题目,可变和不可变呗。。。之前我的想法也是这样,但是我们不妨深入想想:面试官肯定知道一个是可变的一个是不可变的,当然几乎是个技术人就知道,但是这个题中隐藏了一个细节——Comparable接口,没错就是这里,String实现了Comparable接口,而可变字符串没有实现Comparable接口,从而可变字符串没有 compareTo方法。而可变字符串有自己的append方法来拼接字符串,这是不可变字符串里没有的。

字符串的几种创建方式:

看到这里我想说:你真能坚持啊!!!这个问题吧建议学完JavaSE内容在回来看,首先这个问题大家一想,应该也就两种吧,嘿!!!

  • 字面量赋值
  • 通过new关键字
  • 克隆 clone()方法
  • 通过反射
  • 反序列化
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值