[Java大厂必备面试题] 点滴促就辉煌, 每日三题 [Day1]: 基础篇1

这里是征得哈哈哥本人同意搬运! 我看完一篇就搬运一篇  其中可能会插曲自己的理解  

=============================================================================================

  本栏目Java开发岗高频面试题主要出自以下各技术栈:Java基础知识集合容器并发编程JVMSpring全家桶MyBatis等ORMapping框架MySQL数据库Redis缓存RabbitMQ消息队列Linux操作技巧等。  ____感谢哈哈赞助https://blog.csdn.net/qq_39390545/article/details/117423180?spm=1001.2014.3001.5501

=============================================================================================

面试题1:Java 中操作字符串都有哪些类?它们之间有什么区别?

正经回答:

操作字符串的类有:StringStringBufferStringBuilder

String 和 StringBuffer、StringBuilder 的区别在于 String 声明的是不可变的对象,每次操作都会生成新的 String 对象,然后将指针指向新的 String 对象,而 StringBuffer、StringBuilder 可以在原有对象的基础上进行操作,所以在经常改变字符串内容的情况下最好不要使用 String。

  而StringBuffer 和 StringBuilder 最大的区别在于,StringBuffer 是线程安全的,而 StringBuilder 是非线程安全的,但 StringBuilder 的性能却高于 StringBuffer,所以在单线程环境下推荐使用 StringBuilder,多线程环境下推荐使用 StringBuffer。

StringStringBufferStringBuilder
类是否可变不可变(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操作时内存变化的图:

我们可以看到,初始String值为"唐伯虎",然后在这个字符串后面加上新的字符串"点香烟",这个过程是需要重新在栈堆内存中开辟内存空间的,最终得到了"唐伯虎点香烟"字符串也相应的需要开辟内存空间,这样短短的两个字符串,却需要开辟三次内存空间,不得不说这是对内存空间的极大浪费,执行效率同理。
 

他们俩均属于字符串变量,是可改变的对象,每当我们用它们对字符串做操作时,实际上是在一个对象上操作的,这样就不会像String一样创建一些而外的对象进行操作了,速度自然就相对快了。

  我们一般在StringBuffer、StringBuild类上的主要操作是 append 和 insert 方法,这些方法允许被重载,以接受任意类型的数据。每个方法都能有效地将给定的数据转换成字符串,然后将该字符串的字符追加或插入到字符串缓冲区中。append 方法始终将这些字符添加到缓冲区的末端;而 insert 方法则在指定的点(index)添加字符。
 

StringBuilder一个可变的字符序列是JDK1.5新增的。此类提供一个与 StringBuffer 兼容的 API,但不保证同步。该类被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。


如果可能,建议优先采用StringBuilder类,因为在大多数实现中,它比 StringBuffer 要快。且两者的方法基本相同。然而在应用程序要求线程安全的情况下,则必须使用 StringBuffer 类。
 

   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和StringBuilder线程安全主要差在哪里呢?
  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对象只能读取,无法修改。试问:还有什么不安全的呢?

实际应用场景中:

  1. 如果不是在循环体中进行字符串拼接的话,直接使用 String 的 “+” 就好了;
  2. 单线程循环中操作大量字符串数据 → StringBuilder.append();  这里使用比较多的就是mybatis 的  的原生sql   的字符串 追加!!!
  3. 多线程循环中操作大量字符串数据 → StringBuffer.append();

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

正经回答:

Error 和 Exception 都是 Throwable 的子类,在Java中只有Throwable类型的实例才可以被抛出或者捕获,它是异常处理机制的基本类型。

Exception和Error体现了java平台设计者对不同异常情况的分类,Exception是程序正常运行中,可以预料的意外情况,可能并且应该被捕获,进行相应的处理。

Error是指正常情况下,不大可能出现的情况,绝大部分的Error都会导致程序处于非正常的、不可恢复的状态。既然是非正常情况,不便于也不需要捕获。常见的比如OutOfMemoryError之类都是Error的子类。

Exception又分为可检查(checked)异常和不可检查(unchecked)异常。可检查异常在源代码里必须显式的进行捕获处理,这是编译期检查的一部分。不可检查时异常是指运行时异常,像NullPointerException、ArrayIndexOutOfBoundsException之类,通常是可以编码避免的逻辑错误,具体根据需要来判断是否需要捕获,并不会在编译期强制要求。
 

面试题3:== 和 equals 的区别是什么

正经回答:

== : 它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象。(基本数据类型 == 比较的是值,引用数据类型 == 比较的是内存地址)

基本数据类型:

byte(位)、short(短整数)、int(整数)、long(长整数)、float(单精度)、double(双精度)、char(字符)和boolean(布尔值)。”

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) {// true  a==b
        System.out.println("a==b");
    }
    if (a.equals(b)) {// true  a.equals(b)
        System.out.println("a.equals(b)");
    }

    // StringBuffer 对象比较,由于StringBuffer没有重写Object的equal方法,因此结果出现错误
    StringBuffer c = new StringBuffer("陈哈哈");
    StringBuffer d = new StringBuffer("陈哈哈");

    if (c == d) {// false  c != d
        System.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 两个特征。生成代码如下:

 

@Override
public 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;
}

@Override
public int hashCode() {
    int result = age;
    result = 31 * result + (name != null ? name.hashCode() : 0);
    return result;
}

 

小结:

梅花香自苦寒来,

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

是汤圆丫

怎么 给1分?

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值