深入理解String类

前言

String是Java中一个不可变的类,所以他一旦被实例化就无法被修改。不可变类有很多优势。本文总结了为什么字符串被设计成不可变的。将涉及到内存、同步和数据结构相关的知识


1、字符串常量池

在Java的内存分配中,总共3种常量池,分别是Class常量池、运行时常量池、字符串常量池。
创建字符串常量时,JVM会首先检查字符串常量池,如果该字符串存在常,就直接返回常量池中的实例引用。否则就会实例化该字符串并且将其放到常量池中。
由于String字符串的不可变性,常量池中一定不存在两个相同的字符串。

1.1 内存区域

在HotSpot VM中字符串常量池是通过一个StringTable类实现的,它是一个Hash表,默认值大小长度是1009;字符串常量由一个一个字符组成,放在了StringTable上。如果放进String Pool的String非常多,就会造成Hash冲突严重,从而导致链表会很长,而链表长了后直接会造成的影响就是当调用String.intern时性能会大幅下降(因为要一个一个找)。

在JDK6及之前版本,字符串常量池是放在方法区中的,StringTable的长度是固定的1009;在JDK7版本中,字符串常量池被移到了堆中,StringTable的长度可以通过**-XX:StringTableSize=66666**参数指定。至于JDK7为什么把常量池移动到堆上实现,原因可能是由于方法区的内存空间太小且不方便扩展,而堆的内存空间比较大且扩展方便。

1.2 存放内容

在JDK6及之前版本中,String Pool里放的都是字符串常量;在JDK7.0中,由于String.intern()发生了改变,因此String Pool中也可以存放于堆内的字符串对象的引用。

/**
 * 运行结果为true false
 */
String s1 = "AB";
String s2 = "AB";
String s3 = new String("AB");
System.out.println(s1 == s2);
System.out.println(s1 == s3);

在这里插入图片描述
###1.3 手动入池intern方法
可以使用String提供的intern方法,针对new的字符串,手动入池。intern方法会从字符串常量池中查询当前字符串是否存在,如果存在,就直接返回当前字符串;如果不存在就会将当前字符串放入常量池中,之后再返回。

2、为什么字符串设计成不可变的

  1. JVM为了提高性能和减少内存的开销,在实例化字符串的时候进行了一些优化:使用字符串常量池。如果字符串可变的话,当两个引用指向指向同一个字符串时,对其中一个做修改就会影响另外一个。
  2. 易于在其他对象中使用。Java中经常会用到字符串的哈希码,字符串的不可变能保证其hashcode保持一致,这样就可以避免一些不必要的麻烦。
  3. 安全性。String被广泛的使用在其他Java类中充当参数。比如网络连接、打开文件等操作。如果字符串可变,那么类似操作可能导致安全问题。因为某个方法在调用连接操作的时候,他认为会连接到某台机器,但是实际上并没有(其他引用同一String对象的值修改会导致该连接中的字符串内容被修改)。可变的字符串也可能导致反射的安全问题,因为他的参数也是字符串。
  4. 线程安全的。由于无法更改不可变对象,因此可以在多个线程之间自由共享它们,不存在同步的问题。

3、String 字符串编码如何转换?

String s1 = "Hello World";
String s2 = new String(s1.getBytes("GB2312"), StandardCharsets.ISO_8859_1);

4、String、StringBuffer、StringBuilder

在这里插入图片描述

点击详见链接
执行速度StringBuilder > StringBuffer > String。

String 是字符串常量,StringBuffer 和 StringBuilder 都是字符串变量,后两者的字符内容可变,而前者创建后内容不可变;

StringBuffer 是线程安全的,而 StringBuilder 是非线程安全的,线程安全会带来额外的系统开销,所以 StringBuilder 的效率比 StringBuffer 高;

String 的每次修改操作都是在内存中重新 new 一个对象出来,而 StringBuffer、StringBuilder 则不用,且提供了一定的缓存功能,默认 16 个字节数组的大小,超过默认的数组长度时扩容为原来字节数组的长度 * 2 + 2,所以使用 StringBuffer 和 StringBuilder 时可以适当考虑下初始化大小,以便通过减少扩容次数来提高代码的高效性。

5、如何判断两个String是否相等?

有两种方式判断字符串是否相等,使用"“或者使用equals方法。当使用”"操作符时,不仅比较字符串的值,还会比较引用的内存地址。大多数情况下,我们只需要判断值是否相等,此时用equals方法比较即可。

还有一个equalsIgnoreCase可以用来忽略大小写进行比较。

String s1 = "abc";
String s2 = "abc";
String s3= new String("abc");
System.out.println("s1 == s2 ? "+(s1==s2)); //true
System.out.println("s1 == s3 ? "+(s1==s3)); //false
System.out.println("s1 equals s3 ? "+(s1.equals(s3))); //true

6、Java 中如何判断一个字符串只包含大写字母

此方法循环遍历字符串中的每个字符,并使用 java.lang.Character#isUpperCase() 判断每个字符是否为大写。

public static boolean testAllUpperCase(String str) {
        for (int i = 0; i < str.length(); i++) {
            char c = str.charAt(i);
            if (!Character.isUpperCase(c)) {
                return false;
            }
        }
        return true;
    }

7、为什么针对安全保密高的信息,char[] 比 String 更好?

String 是不可变的,一旦创建就不能更改,直到垃圾收集器将它回收才能消失,即使我们修改了原先的变量,实际上也是在内存中新建一个对象,原数据还是保留在内存中等待回收。

字符数组 char[] 中的元素是可以更改的,也就是说像密码等保密信息用完之后我们可以马上修改它的值而不留痕迹,从而相对于 String 有更好的安全性。

范式涉及到安全性的问题就是一个敏感的话题,这里我们看一下大牛们的讨论:
https://www.zhihu.com/question/36734157/answer/68767786

  1. 如果没有及时清空而由GC来清除的话,暴露窗口大约是秒这个数量级,如果能够在计算HASH后立即清除,暴露窗口大约是微秒数量级。如此简单的设计就可以降低如此多的被攻击概率,性价比是非常高的。
  2. 如何使用反射来修改String? 和修改char[] 相比,有何区别和风险?
    通过reflection机制可以查看String的内部的内存成员,从而可以直接修改其中的数据区。但是这样的做法会有问题,内部化的String为了提高HASH速度,节省空间,值相同的字符串通常只有一个实例。你自己的char[],修改它是没有副作用的。但是String里的char[],很可能是多个String所共享的,你改掉它就会殃及别的String。举个例子,有一个密码是"Password",而你密码框提示密码输入的文字也是"Password",改掉第一个"Password"会把后面那个也改掉。
  3. 如果一点明文也不想出现,应该怎么做?
    为了保证“全部处理流程均无明文密码”,需要底层API在给你密码之前就做了HASH,并且这个HASH算法就是你想要的那种。最好还加盐。不过这只是在用户程序方面无明文,底层获取中会不会有明文就保证不了了。
  4. 有没有绝对安全策略?
    安全往往是相对于攻击成本而言的,攻击收益越高,黑客就越能接受攻击成本高的方案。因此,你采取的安全策略应该与这个攻击收益相匹配。对于极其敏感和宝贵的数据来源,就需要在安全方面上下很大功夫。目前来看,没有绝对的安全,只有相对的安全。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值