提起字符串,我们应该都非常熟悉了,作为JAVA中提供的一个标准预定义类。在日常使用过程中,字符串String灵活的可变性和丰富的API接口给我们带来了极大的便利,但值得注意的是,其实本身String类是没有提供修改方法的,String类也被称之为【不可变字符串】。
是不是感觉有点和印象中的不太一样?的确,如果我们想修改一个字符串,可以有很多种方式,比如:
String target = 'oldString'
target = target.replace("old", "new"); // "newString" target = "new"+target.substring(3); // "newString"
一 不可变字符串
这些不都是对target进行了修改吗?没错,这样得到返回的结果,的确是我们需要的字符串。可此target所指向的字符串 已非彼target的指向字符串了。
在我们生成获取oldString的时候,可以看作是在内存区域里分配一块连续地址,用以储存oldString的值,并用一个target变量指向newString的值,当我们对其进行修改的时候,相当于重新开辟一块新的内存地址储存newString,然后将target变量重新指向newString,所以从效果上来看,可能确实看起来像是替换了oldString中的字符,但实际上是给target配对了一个新的字符串对象。为什么要这么做呢?一般来看,修改一个对象的值,应该比新建一个对象更简单,更高效。
在《JAVA核心技术卷I》中是这样解释的:“JAVA设计者认为共享带来的高效率远远胜过提取,拼接字符串所带来的低效率。我个人是这么理解的,在JVM模型的堆部分,会分配内存存储String对象实例,而堆内存存储的对象是线程共享的,在比较、拷贝等更高频次的字符串操作中,共享这些字符串可能比拼接修改字符串带来的高效更有价值。
那问题又来了,这样无休止的创建新的String对象,岂不是会给程序的运行带来很大的压力?别忘了,这可是JAVA。在JVM的垃圾回收机制中,会将一段时间内没有被引用的对象进行回收处理。所以可以不用担心对象创建过多带来的麻烦。
二 构建字符串
虽然说有自动垃圾回收机制帮我们处理字符串对象的清理,但在面对频繁拼接字符串场景时,我们还是应该科学创建字符串,而非疯狂的新建字符串对象。那有什么好的方式呢?
可变字符串StringBuilder,它和char[]有点类似于数组和ArrayList的关系,本身最小单元都是不可变长度,但通过包装处理,将扩容操作隐藏起来,从而使其看起来成为了可变长的灵活对象。值得注意的是,StringBuilder不是一个线程安全的操作类,所以在对线程安全有要求的地方,需要使用StringBuffer。
从实现来看,StringBuilder内部维护了一个char[]数组,并通过封装创建新数组,替换旧数组等方式,实现可变char[]数组效果。这个方式比String类更加灵活,所以在面对不同情况的时候,需要灵活应对,使用不同的方式处理字符串。