面试复习之—Java基础(三):String及其包装类

最近在准备面试,把知识点复习一遍,整理出的笔记记录下,里面会穿插代码和面试例题。

内容不是原创,是总结和收集,并在理解的基础上进行一些完善,如果侵权了请联系作者,若有错误也请各位指正。因为收集的时候忘记把来源记录下来了,所以就不po出处了,请见谅(这是个坏习惯,一定改)。




这是面试复习内容的第三篇——String及其包装类,主要是Java基础的内容,所有内容将分为几篇来写。

本篇应与第二篇一起复习,效果更佳。面试复习之—Java基础(二):基本数据类型与常量池

一、String及其包装类

1、String类

在Java语言中,所有类似“A”、”B”的字面值,都是String类的实例;String类位于java.lang包下,是Java语言的核心类,提供了字符串的比较、查找、截取、大小写转换等操作。
String类源码

2、String不可变

1)String类不可变,是基于代码封装和访问控制实现的

  • 用final关键字修饰,意味着String类不能被继承;
  • 它的成员方法都默认为final方法;内部成员char数组final修饰的,字符串一旦创建就不能再修改;
  • 虽然char数组本身的值可以改变(即修改的是数组上每一个元素本身的值,而并未修改数组,不违反final关键字定义),但是String内部并未提供这一方法。因此String不可变是基于代码封装和访问控制实现的。

2)String类实现了Serializable、CharSequence、 Comparable接口。

3)String实例的值是通过字符数组实现字符串存储的。

3、“+”连接符

Java语言为“+”连接符(字符串连接符)以及对象转换为字符串提供了特殊的支持,字符串对象可以使用“+”连接其他对象。

“+”连接符的本质是通过StringBuilder实现的,以下为反编译的源码。
“+”反编译源码
使用“+”连接符大量循环拼接字符串时,会隐式地创建多次StringBuilder对象,造成浪费及效率的损失,则应直接使用StringBuilder/StringBuffer进行创建。

与此之外还有一种特殊情况,也就是当"+“两端均为编译期确定的字符串常量时,编译器会进行相应的优化,直接将两个字符串常量拼接好,“+”连接符对于直接相加的字符串常量效率很高,因为在编译期间便确定了它的值。也就是说形如"I”+“love”+“java”; 的字符串相加,在编译期间便被优化成了"Ilovejava"。对于间接相加(即包含字符串引用,且编译期无法确定值的),形如s1+s2+s3; 效率要比直接相加低,因为在编译器不会对引用变量进行优化。
String引用优化

4、扩容

在append方法中,当字符串长度不足时,扩容为原字符串长度加新字符串长度,若长度超过jvm支持的最大数组长度MAX_ARRAY_SIZE则抛出异常。(JDK1.6之前扩容为原来的2倍。)
String扩容源码

5、字符串常量池

在Java的内存分配中,总共3种常量池,分别是Class常量池、运行时常量池、全局字符串池(字符串常量池)。

每当创建字符串常量时,JVM会首先检查字符串常量池,如果该字符串已经存在常量池中,那么就直接返回常量池中的实例引用。如果字符串不存在常量池中,就会实例化该字符串并且将其放到常量池中。由于String字符串的不可变性,常量池中一定不存在两个相同的字符串。

在HotSpot VM中字符串常量池是通过一个StringTable类实现的,它是一个Hash表,默认值大小长度是1009;这个StringTable在每个HotSpot VM的实例中只有一份,被所有的类共享;字符串常量由一个一个字符组成,放在了StringTable上。要注意的是,如果放进String Pool的String非常多,就会造成Hash冲突严重,从而导致链表会很长,而链表长了后直接会造成的影响就是当调用String.intern时性能会大幅下降(因为要一个一个找)。

6、intern方法

直接使用双引号声明出来的String对象会直接存储在字符串常量池中,如果不是用双引号声明的String对象,可以使用String提供的intern方法。intern 方法是一个native方法,intern方法会从字符串常量池中查询当前字符串是否存在,如果存在,就直接返回当前字符串;如果不存在就会将当前字符串放入常量池中,之后再返回。

7、删除和拷贝

java.lang.System.arraycopy()方法在Java代码里声明为一个native方法。naïve的实现方式就是通过JNI调用JVM里的native代码来实现。它是浅拷贝,也就是说对于非基本类型而言,它拷贝的是对象的引用,而不是去新建一个新的对象。可以认为System.arraycopy()在拷贝数组时是可靠高效的。

StringBuffer和StringBuilder的删除delete方法和数组拷贝Arrays.copyOf()的底层实际上是使用System.arraycopy()来操作的。

substring在JDK1.6 和 1.7的实现

String是我们最常用的类之一,其中substring的使用频率也不低,且面试也会问到。substring方法,用于截取字符串中指定位置并返回子字符串。用法虽然简单,但在不同JDK版本它的实现却有差别。

  • JDB1.6版本下的
public String substring(int beginIndex, int endIndex) {
	if (beginIndex < 0) {
	    throw new StringIndexOutOfBoundsException(beginIndex);
	}
	if (endIndex > count) {
	    throw new StringIndexOutOfBoundsException(endIndex);
	}
	if (beginIndex > endIndex) {
	    throw new StringIndexOutOfBoundsException(endIndex - beginIndex);
	}
	return ((beginIndex == 0) && (endIndex == count)) ? this : new String(offset + beginIndex, endIndex - beginIndex, value);   //如果开始位置等于且结束为止等于count字符个数。那么就返回本身,否则创建一个新的字符串对象
}

-------------------------------------------------

 String(int offset, int count, char value[]) {
	this.value = value;
	this.offset = offset;
	this.count = count;
}

在JDK1.6中,为了提升效率,使用String带偏移量的构造函数直接创建新的子串,但是this.value = value存在问题,因为这样并未真正构建新的String对象,而是继续引用原串,但是由于存在偏移量,仅使用了字符串中指定长度的字符。
在这里插入图片描述

JDK1.6这个方法的问题就在这,可以想想,当它改变偏移量跟count之后,引用的字符数组还是原来那个(value),用到的字符数组是"str“这三个,那其它是不是就没用了,我们知道java中GC会不定时回收没用的对象,但是前提是没被引用的。这个”substring“字符数组在被截取后还是被引用着,但是仅仅只有”str"这三个位置需要使用,其它就浪费了,回收不了。假设,我们需要截取一个很长很长的字符串中很短的字符串,截取后真正用到的就只有那很短的字符串,那很长的字符就会一直占据着那些没用到的位置,那就可能会造成 内存泄露

  • JDB1.7版本下的
public String substring(int beginIndex, int endIndex) {
        if (beginIndex < 0) {
            throw new StringIndexOutOfBoundsException(beginIndex);
        }
        if (endIndex > value.length) {
            throw new StringIndexOutOfBoundsException(endIndex);
        }
        int subLen = endIndex - beginIndex;
        if (subLen < 0) {
            throw new StringIndexOutOfBoundsException(subLen);
        }
        return ((beginIndex == 0) && (endIndex == value.length)) ? this : new String(value, beginIndex, subLen);
}

---------------------------------------------
public String(char value[], int offset, int count) {
        if (offset < 0) {
            throw new StringIndexOutOfBoundsException(offset);
        }
        if (count < 0) {
            throw new StringIndexOutOfBoundsException(count);
        }
        // Note: offset or count might be near -1>>>1.
        if (offset > value.length - count) {
            throw new StringIndexOutOfBoundsException(offset + count);
        }
        this.value = Arrays.copyOfRange(value, offset, offset+count);
}

--------------------------------------------
public static char[] copyOfRange(char[] original, int from, int to) {
        int newLength = to - from;
        if (newLength < 0)
            throw new IllegalArgumentException(from + " > " + to);
        char[] copy = new char[newLength];
        System.arraycopy(original, from, copy, 0,
                         Math.min(original.length - from, newLength));
        return copy;
    }

由源码可见,在JDK1.7中,最后使用的是System.arraycopy方法,在之前我们提到过这个方法。

System.arraycopy方法是浅拷贝,对于基本类型而言它是穿件了新对象;对于非基本类型而言,它拷贝的是对象的引用,而不是去新建一个新的对象。

因此1.7对char字符创建了一个新的子串对象,解决了1.6中的内存泄漏问题。
在这里插入图片描述

8、String、StringBuffer与StringBuilder

类型说明使用建议
String不可变字符串对象,指向字符的引用,每次创建需要new出字符对象。初始化时可以赋空值。操作少量的数据时使用String,否则浪费资源。
StringBuffer字符串变量,可被多次修改,效率低、线程安全,可同步访问。初始化时不可赋空值,需创建对象。多线程操作字符串缓冲区下操作大量数据 StringBuffer。
StringBuilder字符串变量,可被多次修改,效率高、线程不安全,不应同步访问。初始化时不可赋空值,需创建对象。单线程操作字符串缓冲区下操作大量数据 StringBuilder(推荐使用)

String类

主要区别

  • 1)String是不可变字符序列,StringBuilder和StringBuffer是可变字符序列。
  • 2)执行速度StringBuilder > StringBuffer > String。
  • 3)StringBuilder是非线程安全的,StringBuffer是线程安全的。

总结

1)直接使用双引号声明出来的String对象会直接存储在常量池中;

2)String对象的intern方法会得到字符串对象在常量池中对应的引用,如果常量池中没有对应的字符串,则该字符串将被添加到常量池中,然后返回常量池中字符串的引用;

3) 字符串的+操作其本质是创建了StringBuilder对象进行append操作,然后将拼接后的StringBuilder对象用toString方法处理成String对象,这一点可以用javap -c命令获得class文件对应的JVM字节码指令就可以看出来。
在这里插入图片描述

例题

1、比较以下字符串是否相等?
在这里插入图片描述
字符串进行加号(+)运算,最终得到是一个拼接的新的字符串。然后调用toString()把拼接的对象转换成字符串对象,最后把得到字符串对象的地址赋值给变量。
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值