String常见问题

问题1:String 和 StringBuilder、StringBuffer 的区别

参考

创建成功的字符的串对象,它的的长度是固定的,内容也是不能被改变和编译的。虽然使用“+”运算符可以达到附加新字符或字符串的目的,但是“+”会产生一个新的String实例,会在内存中创建新的字符串对象。如果重复地对字符串进行修改,将会极大的增加系统的开销。
这时候可以使用StringBuffer或者StringBuilder

package com.dong.test;

public class StringTest {
	public static void main(String[] args) {
		String str= "";
		long startTime = System.currentTimeMillis();
		for(int i=0;i<100000;i++){
			str = str+i;
		}
		long endTime = System.currentTimeMillis();
		long time = endTime - startTime;
		System.out.println("String 消耗的时间是"+time);
		
		StringBuffer s = new StringBuffer();
		startTime = System.currentTimeMillis();
		for(int i=0;i<100000;i++){
			s.append(i);
		}
		endTime = System.currentTimeMillis();
		time = endTime - startTime;
		System.out.println("StringBuffer 消耗的时间是"+time);
		}
}

运行结果

String 消耗的时间是17044
StringBuffer 消耗的时间是3

通过这个例子可以看出,两个操作的执行时间差距很大,如果程序中需要频繁地附加字符串,建议使用StirngBuffer。

StringBuffer

构造一个其中不带字符的字符缓冲区,初始容量为16个字符。

特点:

  1. 可以对字符串内容进行修改
  2. 是一个容器
  3. 长度是可变的
  4. 缓冲区中可以存储任意类型的
  5. 最终需要变成字符串

StringBuilder

JDK1.5出现StringBuiler构造一个其中不带字符的字符串生产器,初始容量为16个字符。该类被设计用作StirngBuffer的一个简单替换,用在字符串缓冲区被单个线程使用时。

StringBuffer和StringBuilder的区别

  • StringBuffer 是线程安全,通过synchronized同步锁实现对方法的加锁,
  • StringBuilder 线程不安全;在考虑线程线程安全问题时单个线程操作使用StringBuilder效率高;多线程操作的时候使用StringBuffer安全。但是在开发过程中,如果在方法中定义StringBuilder去拼接字符串,此时是不会有线程安全性问题的,因为方法中的变量存在栈中,不是共享变量。

问题2:生成几个对象

我们知道几乎所有的对象否在堆中创建,但是String比较特殊,有一个字符串常量池的也是可以存放String对象。所以String对象可以在堆中也可以常量池中。下面具体看看两个实例

创建String对象方式1:

 String a = "mark"; //a指向的是字符串常量池的地址

逻辑:先去字符串常量池中查找是否已经有此值,如果有则把返回引用,否则会先在常量池中创建,然后返回引用。这样方式只会在常量池创建一个对象

创建String对象方式2:

 String str2 = new String("mark"); //而 String str 执行的是堆中的地址

在堆上创建一个字符串对象,然后再去常量池中查询此字符串的值是否已经存在,如果不存在会先在常量池中创建此字符串,然后把引用的值指向此字符串。所以可能会创建两个对象(堆一个,字符串常量一个(不存在则创建))

拓展:在日常中使用方式1定义String变量,以为可以使用字符串常量池的缓存功能,提高效率。

问题3:== 和 equals()的区别

参考

== 对于基本数据类型来说,是用于比较 “值”是否相等的;而对于引用类型来说,是用于比较引用地址是否相同的, equals() 方法属于Object类的方法,默认实现是比较引用地址,Object类中equals()方法如下:

public boolean equals(Object obj) {
    return (this == obj);
}

String重写了该方法,比较的是两个字符串的每一个字符是否都相等,String类中equals()方法如下:

    public boolean equals(Object anObject) {
        if (this == anObject) {   // 判断内存地址是否相同
            return true;
        }
        if (anObject instanceof String) { // 待比较的对象是否是 String,如果不是 String,直接返回不相等
            String anotherString = (String)anObject;
            int n = value.length;
            if (n == anotherString.value.length) { // 两个字符串的长度是否相等,不等则直接返回不相等
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {// 依次比较每个字符是否相等,若有一个不等,直接返回不相等
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }

分析1.

        String str = "i"; //java 虚拟机会将其分配到常量池中
        String str2 = new String("i"); //而 String str=new String(“i”) 则会被分到堆内存中。

        System.out.println(str == str2); //false,堆栈中的地址和字符串常量池的地址相比肯定是不一样的
        System.out.println(str.equals(str2)); //true

分析2

        String str = "i"; //java 虚拟机会将其分配到常量池中
        String str3 = "i"; //此时都是执行是常量池中同一个地址
        System.out.println(str == str3); //true

        String str2 = new String("i"); //而 String str=new String(“i”) 则会被分到堆内存中。
        String str4 = new String("i"); //创建了另一个对象
        System.out.println(str2 == str4); //false 不同的对象

分析3

        String s = "hello"; 
        char c[] = {'h', 'e', 'l', 'l', 'o'};
        System.out.println("equal:" + c.equals(s)); //false 调用Object的equals()方法,比较的引用地址
        System.out.println("equal:" + s.equals(c)); //false 调用的是String的equals()方法,c不是String类型,不会一次比较每个字符

分析4

        String s1 = "hello"; 
        String s2 = "he" + "llo";//JVM优化 被直接编译成了 "hello" 
        System.out.println(s1 == s2); //true

        String s3 = "he";
        String s4 = s3 + "llo"; //s4的值需要在运行时才能确定,e的值会存放在堆中
        System.out.println(s1 == s4); //false

        final String s5 = "he"; //final 修饰的变量,会被看作常量
        String s6 = s5 + "llo";
        System.out.println(s1 == s6); //true

        final String s7 = getHe(); //final修饰方法时,编译期无法确定s7的值
        String s8 = s7 + "llo";
        System.out.println(s1 == s8); //false
    private static String getHe() {
        return "he";
    }

问题4:String用final 修饰的好处

参考:

  • 安全性:final类不可继承,这么就会有子类去修改原有的语义;
  • 高效:更好地支持字符串常量池,字符串常量池的作用是缓存常量池。假设现在s1和s2都执行常量池的引用,如果String是可变的,s1的修改好,s2的值也会跟着改变,这就可能造成系统极大的不稳定,也不符合字符串常量池的设计思想了。

问题5: intern()方法

参考

在调用”str”.intern()方法的时候会返回”str”,但是这个方法会首先检查字符串池中是否有”str”这个字符串,如果存在则返回这个字符串的引用;否则就将这个字符串添加到字符串池中,然会返回这个字符串的引用。

注:JDK1.6版本和JDK1.6+后该方法有不同的语义

JDK 1.6:当调用intern方法时,如果字符串常量池先前已经创建好了该字符串的对象,则返回字符串常量池中的该对象的引用。否则,将此·字符串对象副本添加到字符串常量池中,并且返回该字符串对象的引用。

JDK 1.6+:如果字符串常量池先前已经创建出该字符串的对象,则返回字符串常量池中对象的该字符串的引用。否则,如果该对象已经存在堆中,则将堆中的此对象的引用添加到字符串常量池中,并且返回该引用,如果堆中不存在,则在池中创建该字符串并返回其引用

        //先中创建对象,然后在符串常量池中创建“a”
        String s = new String("a");
        //JDK1.6或JDK1.6+:尝试把s中的“a”的副本放入常量池,此时常量池中已经存在“a”
        s.intern();
        String s2 = "a";
        System.out.println(s == s2); //一个是堆的地址,一个是常量池的地址肯定是false

        /*
         1.此时常量池已经有"a"了
         2.在堆中创建对象'aa'
        * */
        String s3 = new String("a") + new String("a");;

        /*
        * 此时常量池中没有‘aa’
        * 1.JDK1.6:把堆引用 副本 放入常量池(副本和原来的地址对比还是不一样的)
        * 2.JDK1.6+:把堆的引用直接放入常量池(此时地址一样的)
        * */
        s3.intern();
        String s4 = "aa";
        System.out.println(s3 == s4); //JDK1.6:false;JDK1.6+:true

常见面试题

1.牛客网-字符串的比较
2.牛客网-生成几个对象

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值