一文弄懂Java中String的所有小秘密

简介

String是java中非常常用的一个对象类型。可以说java中使用最多的就是String了。那么String到底有哪些秘密呢?接下来本文将会一一讲解。

String是不可变的

String是不可变的,官方的说法叫做immutable或者constant。

String的底层其实是一个Char的数组。

private final char value[]; 

所有的String字面量比如"abc"都是String的实现。

考虑下面的赋值操作:

1String a="abc"; 2String b="abc";

对于java虚拟机来说,"abc"是字符串字面量,在JDK 7之后,这个字符串字面量是存储在java heap中的。而在JDK 7之前是有个专门的方法区来存储的。

有了“abc”,然后我们将“abc” 赋值给a和b。

image

可以看到这里a和b只是java heap中字符串的引用。

再看看下面的代码发生了什么:

String c= new String("abc"); 

首先在java heap中创建了“abc”,然后调用String的构造函数:

1 public String(String original) { 2 this.value = original.value; 3 this.hash = original.hash; 4 }

在构造函数中,String将底层的字符串数组赋值给value。

因为Array的赋值只是引用的赋值,所以上述new操作并不会产生新的字符串字面值。

但是new操作新创建了一个String对象,并将其赋值给了c。

String的不可变性还在于,String的所有操作都会产生新的字符串字面量。原来的字符串是永远不会变化的。

字符串不变的好处就在于,它是线程安全的。任何线程都可以很安全的读取字符串。

传值还是传引用

一直以来,java开发者都有这样的问题,java到底是传值还是传引用呢?

我想,这个问题可以从两方面来考虑。

首先对于基础类型int,long,double来说,对他们的赋值是值的拷贝。而对于对象来说,赋值操作是引用。

另一方面,在方法调用的参数中,全部都是传值操作。

1public static void main(String[] args) { 2 String x = new String("ab"); 3 change(x); 4 System.out.println(x); 5} 6 7public static void change(String x) { 8 x = "cd"; 9}

我们看上面的例子,上面的例子输出ab。因为x是对“ab”的引用,但是在change方法中,因为是传值调用,所以会创建一个新的x,其值是“ab”的引用地址。当x被重新赋值之后,改变的只是拷贝之后的x值。而本身的x值是不变的。

substring() 导致的内存泄露

第一次看到这个话题,大家可能会很惊讶,substring方法居然会导致内存泄露?这个话题要从JDK 6开始讲起。

我们先看下JDK 6的实现:

1String(int offset, int count, char value[]) { 2 this.value = value; 3 this.offset = offset; 4 this.count = count; 5} 6 7public String substring(int beginIndex, int endIndex) { 8 //check boundary 9 return new String(offset + beginIndex, endIndex - beginIndex, value); 10}

可以看到,JDK 6的substring方法底层还是引用的原始的字符串数组。唯一的区别就是offset和count不同。

我们考虑一下下面的应用:

1String string = "abcdef"; 2String subString = string.substring(1, 3); 3string = null;

虽然最后我们将String赋值为null,但是subString仍然引用了最初的string。将不会被垃圾回收。

在JDK 7之后,String的实现发送了变化:

1public String(char value[], int offset, int count) { 2 //check boundary 3 this.value = Arrays.copyOfRange(value, offset, offset + count); 4} 5 6public String substring(int beginIndex, int endIndex) { 7 //check boundary 8 int subLen = endIndex - beginIndex; 9 return new String(value, beginIndex, subLen); 10}

Arrays.copyOfRange将会拷贝一份新的数组,而不是使用之前的数组。从而不会发生上面的内存泄露的问题。

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

Hive 删除行, 表 ,清空表

删除行A表数据如下id(String)       name(String)\1                       aaa2                      bbb3                      ccc\

Java中有趣的String、StringBuffer与StringBuilder

String介绍  String类属于java.lang包中,String类是不可变类,任何对String的改变都会引发新的String对象的生成。  创建String的两种方式:  1.通过构造器创建:StringstrnewString("Iamastring.");2.通过变量赋值:String

HIVE 时间操作函数

日期函数UNIX时间戳转日期函数: from\_unixtime语法:   from\_unixtime(bigint unixtime\, string format\)返回值: string说明: 转化UNIX时间戳(从19700101 00:00:00 UTC到指定时间的秒数)到当前时区的时间格式举例:hive   selec