Java 基础篇--字符串

在之前的文章中,已经发布了常见的面试题,这里我了点时间整理了一下对应的解答,由于个人能力有限,不一定完全到位,如果有解答的不合理的地方还请指点,在此谢过。

 

本文主要描述字符串的知识点,下面先看下面一段代码:

public static void main(String[] args)  {  
        String s1 = new String("abc");  
        String s2 = "abc";  
        String s3 = new String("abc");  
        String s4 = "abc";  
        String s5 = "a"+"bc";  
        String s6 = new String("a")+"bc";  
        System.out.println(s1 == s2);//false  
        System.out.println(s1.equals(s2));//true  
        System.out.println(s1 == s3);//false  
        System.out.println(s2 == s3);//false  
        System.out.println(s2 == s4);//true  
        System.out.println(s2 == s5);//true  
        System.out.println(s2 == s6);//false  
        System.out.println(s1 == s1.intern());//false  
        System.out.println(s2 == s1.intern());//true  
        System.out.println(s2 == s2.intern());//true  
        System.out.println(s1.intern() == s3.intern());//true  
    }  

 

上面的题目涉及到的知识点有equals和 == 的区别,jvm的内存模型,intern方法的使用,字符串String常量类。那我们就先把以上涉及的知识点描述一遍之后再来分析答案。

 

Java中== 和equals的区别是什么?

在实际项目中,我们常常会碰到判断两个相等的情况。在java语言中,== 和equals 都是有判断两者相等的意思,不过,使用场景上有些不同。其主要区别如下:

== 是运算符,equals是Object的基本方法,也就是说每个java类都有这个方法。在java语言中,运算符是不允许重载的,而方法是可以重写的,所以我们可以定义自己的equals的方法,这就意味着equals方法在每个类中有可能判断相等的方式不一样。

 

== 运算符判断相等的方法是:如果是基本类型的话,则判断的是基本类型的值是否相等;如果是引用类型的话,则判断引用的地址是否相等。而equals方法,我们知道如果不重写的话,那么其底层的实现其实就是 == (参考object的)。

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

但是如果重写之后的话,我们就要根据类的重写去看下具体的实现了,在这里,我们看下Integer和String的判断思路。

//string

public boolean equals(Object anObject) {

        if (this == anObject) {//先判断是否相等

            return true;

        }

        if (anObject instanceof 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;

    }

//Integer

public boolean equals(Object obj) {

        //判断是否是整型

        if (obj instanceof Integer) {

            return value == ((Integer)obj).intValue();//使用==判断

        }

        return false;

    }

从上面的源码中可以看到,实际上java需要在object中添加equals方法,主要是因为==这个无法重写,那如果我们想要判断是否相等的话,可能就么办法了,所以java干脆写个方法,方便大家自己实现。

 

jvm的内存模型

Jvm的内存模型是后续jvm中的重点内容,由于刚刚的题目涉及到这块的知识,在这里,先简单介绍下jvm (1.7版本)分为:方法区、堆、栈、本地方法栈、程序计数器。而到了1.8版本,稍微做了些调整,使用了元数据区替换了方法区,并且元数据区并不在jvm的控制范围内,其使用的是本地内存,那么jvm中就只有堆、栈、本地方法栈、程序计数器。在java中,为了避免字符串常量分配的性能消耗,java在初始化字符串的时候,会为字符串放置到字符串常量池中。而字符串常量池在不同的版本实际上存放的位置也是不同的,在1.6版本时放在方法区,在1.7版本又移到了堆中,而到了1.8版本被移动到元空间。Jvm的知识后续还会重点介绍,在这里,主要是说明字符串在jvm中存储位置,方便理解上面的题目。

 

字符串的intern方法

该方法的作用是:如果某个字符串没有存入常量池,那现在常量池中创建,然后将常量池中的字符串返回。如果已经存在,那就直接将该字符串返回

该方法的作用主要在于:1)节省内存,如果常量中存在的话,那就只有一个对象了。 2)如果大家都用intern的话,可以直接使用==来进行判断是否相同,而不需要使用equals,因为equals的效率是低下的。

 

字符串是一定不能修改的么?

对于这个问题,我们知道String这个类使用的是final char数组保存数据,如下:

public final class String implements java.io.Serializable, Comparable<String>, CharSequence {

    /** The value is used for character storage. */

private final char value[];//指向的地址不能改变

}

该数组的地址使用final修饰,那么意味着地址实际上是不能修改的,但是并不意味着里面的值是不能修改的,所以,如果我们能够获取到value的值,是能够将其修改的。但是现在value是private修饰的,并且没有提供获取value的方法,再者String是一个final类,也无法继承。那是不是就无法获取到真正的值了。显然不是的,如果我们使用反射的话,是可以无视private属性的,并且获取到value的值,如果我们将获取到的value的值进行修改,那也就意味着我们能够修改相应的值。

 

Ok,有了这些基本的知识之后,我们可以对上面的代码进行解答了。回到代码本身,我们依次画出每个存入的位置,知道位置之后,就知道==是否相等了。

上面是画出的每个对象指向的地址,这里常量池不指定是在哪,因为上面介绍了不同版本存入的位置也不相同。在这里有几个需要说明的是:

  1. 使用intern返回一定是字符串常量池的位置
  2. 使用new出来的字符串,一定会在堆中存在字符串,并且指向的是堆中的位置
  3. +运算符对于字符串来说,需要注意的是,在初始化赋值的时候,如果常量字符串+常量字符串(s5 = a+bc),那么返回的是存入常量池中计算结果(abc)。如果是存在其他的需要找地址计算值的话(s6 = new String(a) +bc),那么只会返回存在堆的计算结果,并不会将结果存入到常量池中

知道上面的知识点之后,对解决上面的题目应该是ok的。

 

字符串除了上面的知识点之外,我们还有几个相关的知识点也在本文中说明。

 

说明一下StringBuilder、StringBuffer和String的区别?

这个是面试可能会出现的问题,虽然现在已经比较少使用StringBuilder或者StringBuffer了,但是我们还是应该要知道一下这两个类出现的作用是什么?

其实核心的问题在于String是不可变,那字符串的拼接就变得不是那么友好,String在做字符串拼接的时候,需要重新申请内存存入新的字符串。基于这样的一个因素,StringBuilder和StringBuffer出现了,其主要的作用是在字符串拼接的效率上。一般来说字符串的拼接速度是StringBuilder>StringBuffer>String。而StringBuilder和StingBuffer的区别在于StringBuilder是线程不安全的,StringBuffer是线程安全的。下面我们看下其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;

    }

而StringBuilder和StringBuffer的区别在于:方法上是否加锁

 

//StringBuilder

 public StringBuilder append(String str) {

        super.append(str);

        return this;

    }

//StringBuffer

public synchronized StringBuffer append(String str) {

        toStringCache = null;

        super.append(str);

        return this;

    }

 

Java中hashCode 和equals方法的区别?

我们知道hashCode 和equals方法都是父类Object中的基本方法。HashCode 方法定义的初衷是为了求解一个类的hashCode值,而equals方法的定义初衷是为了判断两个对象是否内容一致。一般情况下,我们会认为equals相等的两个对象,其hashcode值也相等。而hashcode相等的两个对象,equals不一定相等。在《effect java》中有一条重要的规则是:如果重写了equals方法,就一定要重写hashcode方法。规则很简单,原因在于如果我们只重写了其中的一个,可能会导致在某些java的基本类中判断两个对象是否相等的时候出错,尤其是在hashMap中存入的对象作为key的时候,该对象一定要重写两个方法。因为hashMap在判断key是否相同的时候,是先判断hashcode后再判断equals的。

本文的内容就这么多,如果你觉得对你的学习和面试有些帮助,帮忙点个赞。谢谢。

 

想要了解更多java内容(包含大厂面试题和题解)可以关注公众号,也可以在公众号留言,帮忙内推阿里、腾讯等互联网大厂哈

        

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值