JAVA基础—字符串

  • StringBuffer 和 StringBuilder 的 3 个区别
    • 都是可变字符串,类结构:
  • JAVA基础--字符串 - 图1
    继承了一个抽象的字符串父类:AbstractStringBuilder
    • 区别1:线程安全

      StringBuffer线程安全的,StringBuilder:线程不安全的,因为StringBuffer的所有公开方法都是synchronized修饰的,而StringBuilder并没有。

  1. //StringBuffer代码片段
  2. @Override
  3. public synchronized StringBuffer append(String str){
  4. toStringCache = null;
  5. super.append(str);
  6. return this;
  7. }
  1. - 区别2:缓冲区
  1. //StringBuffer代码片段
  2. private transient char[] toStringCache;
  3. @Override
  4. public synchronized String toString(){
  5. if(toStringCache == null){
  6. toStringCache = Arrays.copyOfRange(value,0,count);
  7. }
  8. return new String(toStringCache,true);
  9. }
  10. //StringBuilder代码片段
  11. @Override
  12. public String toString(){
  13. //Create a copy , don't share the array
  14. retuen new String(value,0,count);
  15. }

StringBuffer每次取toString都会直接使用缓冲区的toStringCache值构造一个字符串。而StringBuilder则每次直接都需要复制一次字符串数组,再构造一个字符串。

  1. - 区别3:性能

线程安全的StringBuffer性能弱于非现场安全的StringBuilder;StringBuffer所有公开方法都是同步的,StringBuilder是没有对方法加锁同步的。

  1. - 总结:StringBuffer适合多线程操作同一个StringBuffer的场景;StringBuilder适合单线程。
  • String为什么要不可变

    String是不可变类,这个类的所有实例不可更改,创建时被初始化的信息不可变,好处很多。

    • 直接原因:存值的char[ ] 被final修饰
    • 根本原因(综合考虑内存、同步、数据结构及安全等因素)。
      • 字符串池的需要

        字符串池(字符串内部池)是在方法区的特殊区域。当一个String被创建如果这个String已经在内存存在,直接返回该引用,而不是创建新的对象返回引用,节省了内存空间。 String String1 = “abcd”; String String2 = “abcd”;

JAVA基础--字符串 - 图2

>
> 如果String可变,改变一个String将导致其他引用该String的值变化。
- - **缓存哈希值**
> Java中String的哈希值经常被用,如HashMap中。保存不变可保证返回相同的哈希值,保证了哈希的唯一性,所以他可被缓存而不用担心被改变。这一位置不需要在每次使用的时候计算哈希值,更高效。
>
> String类部分代码:
>
> */** Cache the hash code for the string \*/
> ***private int** **hash**; *// Default to 0*
- - **使其他类的使用更加容易**
> 例子(有点牵强)

HashSet set = new HashSet(); set.add(new String(“a”)); set.add(new String(“b”)); set.add(new String(“c”)); foe (String a : set) a.value = “a”; / 若可变,将违反Set设计 /

- - **安全**
> String在很多类中,常作为参数。若可变,连接或文件有可能被篡改,导致验证安全问题。如

boolean connect(String s){ if (!isSecure(s)){ throw new SefcurityException(); }
//here will cause proplem, if s is changed before this by using other references. causeProblem(s); }

- - **不可变对象时自然线程安全的**
> 因不可变对象是不可改变的,它能够被多个线程自由的共享。这消除了同步。
- **总结**:String被设计成不可更改是为了**效率**和**安全**。这也是为什么现在有很多不可改变的类
### 原理
- 什么是不可变对象
> 对象创建后不能改变他的状态,就说对象不可变。不能改状态的意思是,不能改变对象内的成员变量,包括基本数据类型的变量值不能改变对象内的成员变量,包括基本数据类型变量的值不能改变,引用类型的变量不能指向其他的对象,引用类型指向的对象的状态也不能改变。
- fina关键字作用
- - 修饰类:表示不能被继承,所有方法也自动成为final方法,断子绝孙类。
  - 修饰方法:子类不可重写。
  - 修饰基本数据类型变量:表示变量为常量,值不能修改。
  - 修饰引用类型变量:表示引用在构造对象后不能指向其他对象,但该引用指向的对象状态可变。
- 不可变分析

String s = “abc”;//1 System.out.println(“s = “ + s); s = “123”;//2 System.out.println(“s = “ + s);

> 打印结果变了,因s只是一个String对象的引用,并不是String本身。代码(1)先在方法区的**运行时常量池**创建一个String对象"abc",然后在**java栈**中创建一个String对象的引用s,并让s指向"abc"。代码(2会在方法区的运行时常量池创建一个新的String对象"123",然后让引用s重新指向这个新的对象,而原来的对象"abc"还在内存中,没有改变。
- 不可变原理
- - String的类和成员变量都使用了final修饰符,value数据是实际存储值的数组,value私有(private)且没有setter(),所以外部不可修改,final修饰导致内部不可修改,但是final只可保证value不能指向其他的对象,而不能保证value指向的对象的状态不可变。
  - String类源码表面,String不可变,关键是SUN工程师,在所有String的方法里都很小心的没有去动数组里面的元素。所以String类不可变动的关键在底层的实现,而不仅仅是一个final。
- 改变String对象
> fina修饰只保证了value引用不可指向其他对象,但是不能保证数组本身不可变,所以可以通过修改字符数组里面的元素来达到修改String的效果。private类型只能通过反射获取String对象的value属性,再去修改value指向的字符数组里面的元素。

String s = “hello world”; System.out.println(“s=” + s); //获取String类型中的value属性 Field valueField = String.class.getDeclaredField(“value”); //改变value属性的访问权限 valueField.setAccessiable(true); //获取s对象上的value属性的值 char[] value = (char[]) valueField.get(s); //改变value所引用的数组中的第六个字符 value[5] = ‘_’; System.out.println(“s=” + s);