JAVA中的String

String的Tricky的地方

简单而言,java中的直接用字符串初始化的Spring是指向常量池的。而常量池中的内容是不可改变的。(这句话千万别理解成String对象都在常量池。正常生成的String对象也存在于堆)。

public class Sandbox4 extends Sandbox2{
	String s1 = "test1";
	public static void main(String[] args) {
		Sandbox4 a= new Sandbox4();
		a.ChangeString(a.s1);
		System.out.print(a.s1);
	}
	void ChangeString(String s){
		s = "test2";
	}
	
}

如果你强行要改变他的值。JVM回在常量池中新生成一个对象。而java中都是穿值的。上面的程序中进入方法的s是一个引用。方法改变了引用的值,而不是引用所指向对象的值。所以程序的释出仍然是test1。即使你采用new String的方法声明s1也结果不会改变的。JAVA API中已经明确说了String中的内容不能改变。只能新建一个对象替换。

另外的一点是,当你使用字符串字面量给String赋值时,有可能新生成一个对象(比如说上面的例子)。也有可能不新生成一个对象,而是指向常量池中已经存在的对象。

public class Sandbox4 extends Sandbox2{
	String s1 = "test1";
	public static void main(String[] args) {
		Sandbox4 a= new Sandbox4();
		String s2 = "test1";
		System.out.print("s1==s2:"+(a.s1==s2));
	}	
}

但是,如果将上述换成new String的方法来定义引用,则是两个不同的对象。
如果你想生成一个非常量的String。请使用StringBuffer类。

这里还要牵扯一个问题。就是final。
当书写final Object obj类似的定义时,obj指向的对象并不是不可以变的!而只是obj指向的对象不能变成别的对象。所以跟上面说的情况不同。不能说String是默认final修饰的。这是两回事。

java中的是比较他们的内存地址。object中的equals就是用实现的。当然可以重写。hashcode是用来均匀映射的。理论上hashcode相等不代表什么。
但是,在hashmap等的实现中这样却不行。事实上,我们在不能允许两个equals相等的对象hashcode不同。所以,如果你重写了equals方法,而且会用到集合框架。请重写hashcode。你重写的equals方法一般是要求两个对象中部分或者全部字段相等。显然,用这些字段计算hashcode就好啦。

在这里插入图片描述
在JDK1.8中,常量池被分到了metaSpace。

String不可变原理

String的底层是字符数组。

private final char[] value;

数组引用被设置成了final。我们知道final只在编译期间有用,而且数组引用的final不代表数组中每个元素也是final。所以,String可以被反射改变。

public static void testReflection() throws Exception {  
      
    //创建字符串"Hello World", 并赋给引用s  
    String s = "Hello World";   
      
    System.out.println("s = " + s); //Hello World  
      
    //获取String类中的value字段  
    Field valueFieldOfString = String.class.getDeclaredField("value");  
      
    //改变value属性的访问权限  
    valueFieldOfString.setAccessible(true);  
      
    //获取s对象上的value属性的值  
    char[] value = (char[]) valueFieldOfString.get(s);  
      
    //改变value所引用的数组中的第5个字符  
    value[5] = '_';  
      
    System.out.println("s = " + s);  //Hello_World
}  

但是,不是说String直接字面常量赋值会存储于常量池吗?万能的反射才不管方法区是啥!

除了把value设置为final,JDK作者还将String的class设置为final以免继承者改变类的逻辑,最终的保障就是小心翼翼写代码,不暴露任何可以修改value的方法。

itern方法

在JDK源代码里,intern就是这么一个简单的系统调用。如果池已经包含一个等于此 String 对象的字符串(该对象由 equals(Object) 方法确定),则返回池中的字符串。否则,将此 String 对象添加到池中,并且返回此 String 对象的引用。

public native String intern();
public class Test {
	public static void main(String argv[])
	{

		String s2 = new String("HelloWorld");
		System.out.println(s2 == s2.intern());//输出false,因为s2在堆上,而s2.intern返回常量池中的对象
	}
}

使用这个方法主要目的是节省内存空间。

intern()方法在JDK1.6中的作用是:比如String s = new String(“SEU_Calvin”),再调用s.intern(),此时返回值还是字符串"SEU_Calvin",表面上看起来好像这个方法没什么用处。但实际上,在JDK1.6中它做了个小动作:检查字符串池里是否存在"SEU_Calvin"这么一个字符串,如果存在,就返回池里的字符串;如果不存在,该方法会把"SEU_Calvin"添加到字符串池中,然后再返回它的引用。

目的

  • 不可变对象可以提高String Pool的效率和安全性。如果你知道一个对象是不可变的,那么需要拷贝这个对象的内容时,就不用复制它的本身而只是复制它的地址,复制地址(通常一个指针的大小)需要很小的内存效率也很高。对于同时引用这个“ABC”的其他变量也不会造成影响。java的classloader机制需要使用String类型,所以,保证String的不能修改极为重要。

  • 不可变对象对于多线程是安全的,因为在多线程同时进行的情况下,一个可变对象的值很可能被其他进程改变,这样会造成不可预期的结果,而使用不可变对象就可以避免这种情况。

String的性能问题

以前,String类一直被诟病存在拼接时巨大的性能问题。实际上,String的+是语法糖,内部是新建StringBuilder对象完成拼接后再转String。会导致新建和销毁大量对象。所有请避免频繁使用String进行字符串拼接。

String、StringBuffer和StringBuilder在本质上都是字符数组,不同的是,在进行连接操作时,String每次返回一个新的String实例,而StringBuffer和StringBuilder的append方法直接返回this,所以这就是为什么在进行大量字符串连接运算时,不推荐使用String,而推荐StringBuffer和StringBuilder。

在这里插入图片描述

StringBuilder的append直接使用了其父类的append的方法。

    public AbstractStringBuilder append(String str) {
        if (str == null)
            return appendNull();
        int len = str.length();
        ensureCapacityInternal(count + len);
        str.getChars(0, len, value, count);
        count += len;
        return this;
    }

而StringBuffer,则是升级直接搞成了synchronized修饰一下就完事了。

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

总说StringBuilder线程不安全。不安全在哪呢?你显然不能多线程的操作StringBuider对象。但是当大多数时候我们拼接一个字符串时,都是单线程堆栈的操作环境。所以没啥问题。

进一步深入

String的机制可以说是JAVA最恶心的地方之一。
JDK6,JDK7,JDK8的String类的行为都不相同。真是令人恐怖。

推荐下一篇
https://blog.csdn.net/define_us/article/details/82223291

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值