String StringBuffer StringBuilder

本文章还是得提到String Immutable 特性。这样才有利于String 、StringBuffer 、StringBuilder 特点的比较。

以下基于 java for windows

java version “1.8.0_171”
Java™ SE Runtime Environment (build 1.8.0_171-b11)
Java HotSpot™ 64-Bit Server VM (build 25.171-b11, mixed mode)

图中示例代码注释只是为了展示、对比方便,所以放在代码尾部

String

String Immutable 特性

以下为 java.lang.String 属性字段 

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

    /** Cache the hash code for the string */
    private int hash; // Default to 0

    /** use serialVersionUID from JDK 1.0.2 for interoperability */
    private static final long serialVersionUID = -6849794470754667710L;

String 类 implements java.io.Serializable, Comparable< String >, CharSequence。类是用final修饰的。且其属性字段为value未提供set修改其对象内部值。首先要了解一点

  • 每次新建String类型对象 其值(方法区 String Pool 、堆区) 都是不可变的(immutable)。最直观代码如下

    // 这种行为是在 String pool 创建对象,其引用存在栈中
    String str1 = "hello world";
    // 使用此种方法重新创建String对象时 java会首先在String pool中检查是否存在  hello world 存在即返回其引用 所以 str1、str2 指向同一对象
    String str2 = "hello world";
    
    System.out.println("result:" + (str1 == str2)); // result:true
    
    // 此种方式创建的String对象位于堆内 每次创建都指向一个新的对象 各自独立的堆空间
    String str3 = new String("hello world");
    String str4 = new String("hello world");
    
    
    System.out.println("result2:" + (str3 == str4));// result2:false
    
    // 在堆中的String不同于在String pool 的String
    System.out.println("result3:" + (str2 == str4)); // result3:false
    
    // 此操作在Pool中重新创建String 其引用改变 但原  hello world 未变
    str1 = "hello Man";
    
    System.out.println("change1: " + str1);// change1: hello Man
    System.out.println("change1: " + str2);// change1: hello world
    
  • String immutable特性带来很多便利

    • 不可变对象可以提高String Pool的效率和安全性。如果你知道一个对象是不可变的,那么需要拷贝这个对象的内容时,就不用复制它的本身而只是复制它的地址,复制地址(通常一个指针的大小)需要很小的内存效率也很高。对于同时引用这个“hello world” 的其他变量也不会造成影响。节省空间,加快效率。
    • 不可变对象对于多线程是安全的,因为在多线程同时进行的情况下,一个可变对象的值很可能被其他进程改变,这样会造成不可预期的结果,而使用不可变对象就可以避免这种情况。(最直观的例子就是 String被广泛的作为方法的参数,比如打开网络连接时传递主机名和端口号作为参数,传递数据库URL用于连接数据库,也可以在打开文件时传递文件名给Java的I/O class。如果String不是immutable,会带来严重的安全问题,假设一个人对其授权访问的文件,改变了文件名并重新获了文件访问权限,但是因为String是immutable,你就不需要担心这种情况。这也可以解释为啥String 是final的,final string保证了任何人都不能重写String而改变String的行为。)
    • 不可变性,导致其是线程安全的。
    • 支持hash映射和缓存(此点在之后的文章中会提到)

反射修改了String的值?

JAVA不可变类(immutable)机制与String的不可变性 中 看到博主的一段说明:

发现String的值已经发生了改变。也就是说,通过反射是可以修改所谓的“不可变”对象的

特以下代码验证此点

String str1 = "hello world";
String str2 = "hello world";
System.out.println("result1:" + (str1 == str2)); // result1:true


//获取String类中的value字段
Field valueFieldOfString = String.class.getDeclaredField("value");

//改变value属性的访问权限
valueFieldOfString.setAccessible(true);

//获取s对象上的value属性的值
char[] value = (char[]) valueFieldOfString.get(str1);

//改变value所引用的数组中的第5个字符
value[5] = 'X';
System.out.println("str1 changed = " + str1);  // str1 changed = helloXworld

System.out.println("str2 = " +str2);// str2 = helloXworld

结果显而易见:改了里面的值,并且执行其值的 str2 值也被修改


但是,如果将 str1 使用final修饰,输出的结果将是:

str1 changed = hello world
str2 = helloXworld

也就是说 str1 的值并未改变

为什么会出现这种情况?请见下文。

这是我的java类 .java(str1未被final修饰前)

import java.lang.reflect.Field;

/**
 * @Description:
 * @Author: amarone
 */
public class Test {

    public static void main(String[] args) throws Exception{
        String str1 = "hello world";
        String str2 = "hello world";
        System.out.println("result1:" + (str1 == str2));


       
        Field valueFieldOfString = String.class.getDeclaredField("value");

      
        valueFieldOfString.setAccessible(true);

    
        char[] value = (char[]) valueFieldOfString.get(str1);

      
        value[5] = 'X';
		
        System.out.println("str1 changed = " + str1);  //str1 changed = helloXworld

        System.out.println("str2 = " +str2); // str2 = helloXworld
    }

}

这是经过javac 编译.java 后的 .class 。 通过 jd-gui.exe 反编译 (str1未被final修饰前)

import java.io.PrintStream;
import java.lang.reflect.Field;

public class Test
{
  public static void main(String[] paramArrayOfString)
    throws Exception
  {
    String str1 = "hello world";
    String str2 = "hello world";
    System.out.println("result1:" + (str1 == str2));
    


    Field localField = String.class.getDeclaredField("value");
    

    localField.setAccessible(true);
    

    char[] arrayOfChar = (char[])localField.get(str1);
    

    arrayOfChar[5] = 'X';
    
    System.out.println("str1 changed = " + str1);
    
    System.out.println("str2 = " + str2);
  }
}

这是经过javac 编译.java 后的 .class 。 通过 jd-gui.exe 反编译 (str1被final修饰)

import java.io.PrintStream;
import java.lang.reflect.Field;

public class Test
{
  public static void main(String[] paramArrayOfString)
    throws Exception
  {
    String str = "hello world";
    System.out.println("result1:" + ("hello world" == str));
    


    Field localField = String.class.getDeclaredField("value");
    

    localField.setAccessible(true);
    

    char[] arrayOfChar = (char[])localField.get("hello world");
    

    arrayOfChar[5] = 'X';
    
    System.out.println("str1 changed = hello world");
    
    System.out.println("str2 = " + str);
  }
}

可以注意到: 在加上final 修饰之后,java文件被编译为class文件时

(这也是我上篇文章《(四)Final 关键字》 中提到部分有涉及的 编译期优化 )

System.out.println("str1 changed = " + str1); ==> System.out.println("str1 changed = hello world");

输出的不是引用的变量而是一个字符串!也就是说在编译器就已经优化了,变量再怎么改都没用。final修饰Stirng是为了防止继承String暴露Stirng内部属性。

为什么要有StringBuffer或者StringBuilder?

Stirng 字符串拼接时

import java.io.PrintStream;

public class Test
{
  public static void main(String[] paramArrayOfString)
    throws Exception
  {
    String str1 = "aaa";
    
    String str2 = str1 + "b";
    
    System.out.println(str1);
    System.out.println(str2);
    System.out.println(str1 + str2);
  }
}

str1、str2、"b" 都是单独的对象,如果存在大量的字符串拼接,那么就会大量创建对象,然后你的新生代(幸存者区)很快就满了80% 然后就触发GC了。或者就直接挂了。

为了应对大量拼接字符串拼接,java提供了 StringBuffer 类。

 public final class StringBuffer
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence
{
    **
     * A cache of the last value returned by toString. Cleared
     * whenever the StringBuffer is modified.
     */
    private transient char[] toStringCache;

    /** use serialVersionUID from JDK 1.0.2 for interoperability */
    static final long serialVersionUID = 3388685877147921107L;
    
    ········
}

StringBuffer 继承于 AbstractStringBuilder

abstract class AbstractStringBuilder implements Appendable, CharSequence {
    /**
     * The value is used for character storage.
     */
    char[] value;

    /**
     * The count is the number of characters used.
     */
    int count;
}

StringBuffer 提供了append() 方法用以在指定位置添加字符串

   @Override
    public synchronized StringBuffer append(Object obj) {
        toStringCache = null;
        super.append(String.valueOf(obj));
        return this;
    }

    @Override
    public synchronized StringBuffer append(String str) {
        toStringCache = null;
        super.append(str);
        return this;
    }

可以看到几乎所有对字符串操作的方法全部添加了synchronized关键字。保证了线程安全,但是需要付出部分性能代价。

参考链接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值