Java面试题冲刺第一天--基础篇1

目录

  • 面试题1:Java 中操作字符串都有哪些类?它们之间有什么区别? 正经回答:
    • 深入追问:
      • 追问1:这三者在效率上怎么说?
      • 追问2:那StringBuffer和StringBuffer线程安全主要差在哪里呢?
  • 面试题2:请你说一下Error 和 Exception 区别是什么?
    • 正经回答:
    • 面试题3:== 和 equals 的区别是什么
      • 正经回答:
        • 深入追问:
          • 追问1:如果我们不重写equals() 方法,会怎么样?
          • 追问2:重写equals的同时,我们需要重写hashCode()方法么?为什么?
      • 总结

        面试题1:Java 中操作字符串都有哪些类?它们之间有什么区别? 正经回答: 操作字符串的类有:StringStringBufferStringBuilder
        String 和 StringBuffer、StringBuilder 的区别在于 String 声明的是不可变的对象,每次操作都会生成新的 String 对象,然后将指针指向新的 String 对象,而 StringBuffer、StringBuilder 可以在原有对象的基础上进行操作,所以在经常改变字符串内容的情况下最好不要使用 String。
        而StringBuffer 和 StringBuilder 最大的区别在于,StringBuffer 是线程安全的,而 StringBuilder 是非线程安全的,但 StringBuilder 的性能却高于 StringBuffer,所以在单线程环境下推荐使用 StringBuilder,多线程环境下推荐使用 StringBuffer。
        String StringBuffer StringBuilder
        类是否可变 不可变(Final) 可变 可变
        功能介绍 每次对String的操作都会在“常量池”中生成新的String对象 任何对它指向的字符串的操作都不会产生新的对象。每个StringBuffer对象都有一定的缓冲区容量,字符串大小没有超过容量时,不会分配新的容量,当字符串大小超过容量时,自动扩容 功能与StringBuffer相同,相比少了同步锁,执行速度更快
        线程安全性 线程安全 线程安全 线程不安全
        使用场景推荐 单次操作或循环外操作字符串 多线程操作字符串 单线程操作字符串


        深入追问:

        追问1:这三者在效率上怎么说?
        StringBulider > StringBuffer > String
        String <(StringBuffer,StringBuilder)的原因?
        • String:字符串常量
        • StringBuffer:字符串变量(有同步锁)
        • StringBuilder:字符串变量(无同步锁)
        从上面的名字可以看到,String是"字符串常量",也就是不可改变的对象。源码如下:
        public final class String{}

        对于上面这句话的理解你可能会产生这样一个疑问 ,比如这段代码:
        String str = "唐伯虎"; str = str + "点香烟"; System.out.print(str); // result : "唐伯虎点香烟"

        我们明明改变了String型的变量str啊,为什么说是没有改变呢?我们来看一下这张对String操作时内存变化的图:
        Java面试题冲刺第一天--基础篇1
        文章图片

        我们可以看到,初始String值为"唐伯虎",然后在这个字符串后面加上新的字符串"点香烟",这个过程是需要重新在栈堆内存中开辟内存空间的,最终得到了"唐伯虎点香烟"字符串也相应的需要开辟内存空间,这样短短的两个字符串,却需要开辟三次内存空间,不得不说这是对内存空间的极大浪费,执行效率同理。
        为了应对经常性操作字符串的场景,Java才提供了其他两个操作字符串的类 —— StringBuffer、StringBuilder。
        他们俩均属于字符串变量,是可改变的对象,每当我们用它们对字符串做操作时,实际上是在一个对象上操作的,这样就不会像String一样创建一些而外的对象进行操作了,速度自然就相对快了。
        我们一般在StringBuffer、StringBuild类上的主要操作是 append 和 insert 方法,这些方法允许被重载,以接受任意类型的数据。每个方法都能有效地将给定的数据转换成字符串,然后将该字符串的字符追加或插入到字符串缓冲区中。append 方法始终将这些字符添加到缓冲区的末端;而 insert 方法则在指定的点(index)添加字符。
        1. StringBuilder一个可变的字符序列是JDK1.5新增的。此类提供一个与 StringBuffer 兼容的 API,但不保证同步。该类被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。如果可能,建议优先采用S
        2. tringBuilder类,因为在大多数实现中,它比 StringBuffer 要快。且两者的方法基本相同。然而在应用程序要求线程安全的情况下,则必须使用 StringBuffer 类。
        【Java面试题冲刺第一天--基础篇1】String 类型和 StringBuffer、 StringBuild类型的主要性能区别其实在于 String 是不可变的对象(final), 因此在每次对 String 类型进行改变的时候其实都等同于在堆中生成了一个新的 String 对象,然后将指针指向新的 String 对象,这样不仅效率低下,而且大量浪费有限的内存空间,所以经常改变内容的字符串最好不要用 String 。因为每次生成对象都会对系统性能产生影响,特别是当内存中的无引用对象过多了以后, JVM 的 GC 开始工作,那速度是一定会相当慢的。另外当GC清理速度跟不上new String的速度时,还会导致内存溢出Error,会直接kill掉主程序!报错如下:
        Caused by: java.lang.OutOfMemoryError: GC overhead limit exceeded
        Exception in thread "I/O dispatcher 3797236" java.lang.OutOfMemoryError: GC overhead limit exceeded

        追问2:那StringBuffer和StringBuffer线程安全主要差在哪里呢? StringBuffer和StringBuilder可以算是双胞胎了,这两者的方法没有很大区别。但在线程安全性方面,StringBuffer允许多线程进行字符操作。这是因为在源代码中StringBuffer的很多方法都被关键字synchronized 修饰了,而StringBuilder没有。
        synchronized的含义: ??
        每一个类对象都对应一把锁,当某个线程A调用类对象O中的synchronized方法M时,必须获得对象O的锁才能够执行M方法,否则线程A阻塞。一旦线程A开始执行M方法,将独占对象O的锁。使得其它需要调用O对象的M方法的线程阻塞。只有线程A执行完毕,释放锁后。那些阻塞线程才有机会重新调用M方法。这就是解决线程同步问题的锁机制。 >??了解了synchronized的含义以后,大家可能都会有这个感觉。多线程编程中StringBuffer比StringBuilder要安全多了 ,事实确实如此。如果有多个线程需要对同一个字符串缓冲区进行操作的时候,StringBuffer应该是不二选择。
        注意:是不是String也不安全呢?事实上不存在这个问题,String是不可变的。线程对于堆中指定的一个String对象只能读取,无法修改。试问:还有什么不安全的呢?
        实际应用场景中:
        • 如果不是在循环体中进行字符串拼接的话,直接使用 String 的 “+” 就好了;
        • 单线程循环中操作大量字符串数据 → StringBuilder.append();
        • 多线程循环中操作大量字符串数据 → StringBuffer.append();

        面试题2:请你说一下Error 和 Exception 区别是什么?

        正经回答:
        Error 和 Exception 都是 Throwable 的子类,在Java中只有Throwable类型的实例才可以被抛出或者捕获,它是异常处理机制的基本类型。
        Java面试题冲刺第一天--基础篇1
        文章图片

        • Exception和Error体现了java平台设计者对不同异常情况的分类,Exception是程序正常运行中,可以预料的意外情况,可能并且应该被捕获,进行相应的处理。
        • Error是指正常情况下,不大可能出现的情况,绝大部分的Error都会导致程序处于非正常的、不可恢复的状态。既然是非正常情况,不便于也不需要捕获。常见的比如OutOfMemoryError之类都是Error的子类。
        • Exception又分为可检查(checked)异常和不可检查(unchecked)异常。可检查异常在源代码里必须显式的进行捕获处理,这是编译期检查的一部分。不可检查时异常是指运行时异常,像NullPointerException、ArrayIndexOutOfBoundsException之类,通常是可以编码避免的逻辑错误,具体根据需要来判断是否需要捕获,并不会在编译期强制要求。

        面试题3:== 和 equals 的区别是什么 正经回答:
        • == : 它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象。(基本数据类型 == 比较的是值,引用数据类型 == 比较的是内存地址)
        • equals(): 它的作用也是判断两个对象是否相等。但它一般有两种使用情况:
        情况1:类没有覆盖 equals() 方法。则通过 equals() 比较该类的两个对象时,等价于调用了Object类的equals() 方法,也就是通过“==”比较这两个对象。
        // Object类中的equals() 方法public boolean equals(Object obj) {return (this == obj); }

        情况2:类覆盖了 equals() 方法。一般,我们都会覆盖 equals() 方法来两个对象的内容相等;若它们的内容相等,则返回 true (即,认为这两个对象相等)。
        // String类中的equals() 方法,已覆盖,用于比较内容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; }

        深入追问:

        追问1:如果我们不重写equals() 方法,会怎么样? 举例说明:
        重点说明:是否重写Object类中的equals方法,会对结果造成的影响
        public static void main(String[] args) {// 字符串比较String a = "陈哈哈"; String b = "陈哈哈"; if (a == b) {// truea==bSystem.out.println("a==b"); }if (a.equals(b)) {// truea.equals(b)System.out.println("a.equals(b)"); }// StringBuffer 对象比较,由于StringBuffer没有重写Object的equal方法,因此结果出现错误StringBuffer c = new StringBuffer("陈哈哈"); StringBuffer d = new StringBuffer("陈哈哈"); if (c == d) {// falsec != dSystem.out.println("c == d"); } else {System.out.println("c != d"); }if (c.equals(d)) { // false 调用了Object类的equal方法System.out.println("StringBuffer equal true"); }else {System.out.println("StringBuffer equal false"); }}

        • object的equals方法是比较的对象的内存地址,而String的equals方法比较的是对象的值。
        • 因为String中的equals方法是被重写过的,而StringBuilder没有重写equals方法,从而调用的是Object类的equals方法,也就相当于用了 ==;

        追问2:重写equals的同时,我们需要重写hashCode()方法么?为什么? 在重写equals()方法时,也有必要对hashCode()方法进行重写,尤其是当我们自定义一个类,想把该类的实例存储在集合中时。??
        hashCode方法的常规约定为:值相同的对象必须有相同的hashCode,也就是equals()结果为相同,那么hashcode也要相同,equals()结果为不相同,那么hashcode也不相同;
        当我们使用equals方法比较说明对象相同,但hashCode不同时,就会出现两个hashcode值,比如在HashMap中,就会认为这是两个对象,因此会出现矛盾,说明equals方法和hashCode方法应该成对出现,当我们对equals方法进行重写时,也要对hashCode方法进行重写。
        可以通过ide快捷键快速生成两个方法,假设现在有一个学生Student类,其中有 age 和 name 两个特征。生成代码如下:
        @Overridepublic boolean equals(Object o){//首先比较两个的地址值是否相同,如果相同,那内容也一定相同 if(this == o) return true; //如果o为空值或者两个对象的类型是否相同,如果类型不同或者o为空值则内容一定不同 if(o == null || getClass() != o.getClass()) return false; //将object类型的实例强转为Student类型Student student = (Student)o; //比较两个实例的age是否相同 if(age != student.age) return false; //在比较name是否相同 return name != null ? name.equals(student.name ) : student.name == null; }@Overridepublic int hashCode() { int result = age; result = 31 * result + (name != null ? name.hashCode() : 0); return result; }


        总结 本篇文章就到这里了,希望能给你带来帮助,也希望您能够多多关注脚本之家的更多内容!

          推荐阅读