java基础面试 50道

作者:gaoqiang 时间:23-03-12 阅读数:130人阅读

==和equals的区别

==: 对比的是栈中的值,基本数据类型是变量值,引用类型是堆中内存对象地址

equals:object中默认采用==比较,通常会重写equals方法

Object中的equals方法

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

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;
    }

以下例子说明二者的区别

public static void main(String[] args) {
        String s1 = "hello";
        String s2 = new String("hello");
        String s3 = s2; //引用传递
        // s1 是在堆中的常量池中分配内存,s2是在堆中分配内存,s3赋的值是s2的引用地址
        // == 对比的是栈中的内存地址,s1和s2的内存地址不是同一个地址,所以为false
        System.out.println(s1 == s2);   // false
        System.out.println(s1 == s3);   // false
        System.out.println(s2 == s3);   // true
        // equals对比的是两个值的内容。所以结果都为true
        System.out.println(s1.equals(s2));  // true
        System.out.println(s1 .equals(s3)); // true
        System.out.println(s2 .equals(s3)); // true
    }

final的作用及用法

修饰类:表示类不可以被继承

修饰方法:表示方法不可以被重写,但可以重载

修饰变量:表示变量一旦赋值,不可以被修改

  1. 修饰成员变量

    修饰类变量:必须在声明该变量值时指定初始值或者在静态代码块中指定初始值

    修饰局部变量:可以在非静态初始化块声明该变量或在构造器中执行初始值

  2. 修饰局部变量:系统不会为局部变量初始化,需要由程序员显式初始化。所以局部变量既可以在声明时赋值,也可以不赋值,如果声明时不赋值,则在使用时必须先初始化该变量值

  3. 修饰基本类型和引用类型的区别:

    如果修饰基本类型,则变量赋值后不允许修改。

    如果修饰引用类型。则变量初始化后,不能再指向另一个对象的引用,但引用的值是可以改变的

String ,StringBuffer, StringBuilder三者之间的区别

String是final修饰的,不可变,每次操作都会产生新的String对象,将占用更多内存

StringBufferStringBuilder都是操作原对象

StringBuffer内部实现方法均有synchronized 修饰,所以是线程安全的

StringBuilder是非线程安全的

在性能上来说 StringBuilder >StringBuffer >String

使用场景:当字符串需要频繁变更时优先使用StringBuilder ,在多线程使用共享变量时使用StringBuffer

List和Set的区别

List: 按对象进入的顺序保存对象,可重复,允许多个null元素,可以使用iterator取出所有元素,逐一遍历,也可以使用get(i) 取出指定下标的元素

Set: 无序,不可重复,最多允许有一个null元素,取元素时只能使用iterator取出所有元素,逐一遍历

ArrayList和LinkedList的区别

ArrayList 基于动态数组,连续内存存储,适合下标访问(随机访问),尾部插入、删除性能较好,其他部分插入、删除都会移动数据,因此性能会低

 

扩容:ArrayList在使用new创建之后,并不会立即开辟数组空间,而是使用空数组代替。只有当第一个元素被加入的时候,才会真正的开辟数组空间,空间默认大小是10.也就是说,ArrayList是一个懒开辟。在加入元素时,如果size+1大于数组长度,就需要进行扩容了

扩容的机制

ArrayList() 无参构造默认使用长度为0的数组

ArrayList(int initialCapacity) 使用指定容量的数组

ArrayList(Collection<? extends E> c) 使用 c的大小为数组容量

add(E e); 首次扩容为10 ,再次扩容大约为上次容量的1.5倍,以下为计算公式:

newCapacity=oldCapacity + oldCapacity>>1 新数组容量= 旧数组容量+旧数组容量右移一位,大约为50%

addAll(Collection<? extends E> c); 当集合没有元素时,扩容为Math.max(10,实际元素个数),当集合有元素时,扩容为 Math.max(原容量的1.5倍,实际元素个数)

list.addAll() 遵循一个规律:

list下次扩容的大小与当前元素个数两者之间找一个较大值

List<Integer> list = new ArrayList<>();   //当调用add方法时,List默认开辟一个容量为10的数组
list.addAll(Arrays.asList(1,2,3,4,5,6,7,8,9,10,11));  //list下次扩容的大小与当前元素个数两者之间找一个较大值
扩容结果为 11 

List<Integer> list = new ArrayList<>();   //当调用add方法时,List默认开辟一个容量为10的数组
for (int i = 0; i<10; i++) {
    list.add(i);
}
list.addAll(Arrays.asList(1,2,3));   // 先给list增加10个元素,再次触发扩容
//扩容结果 15
list.addAll(Arrays.asList(1,2,3,4,5,6));
//扩容结果 16

LinkedList 基于链表,可以存储在分散的内存中,适合做插入及删除操作,缺点:随机访问慢,占用内存多

HashMap面试

底层数据结构:1.7版本为 数组+链表 ,1.8版本为数组+链表 || 红黑树

为何要使用红黑树,为何一开始不树化,树化阈值为何是8,何时退化为链表?

红黑树本身是为了避免DOS攻击的,防止链表超长,造成性能下降,树化应当是偶然情况。

正常情况下,链表长度不会超过8,除非遇到系统被恶意攻击,构造大量相同hash值

红黑树占用的空间比链表要大很多,如非必要,尽量使用链表

如果hash值足够随机,在负载因子0.75的情况下,链表长度超过8的几率仅为 亿分之6 ,选择8就是为了树化几率足够小

树化为红黑树需满足以下条件:

数组容量大于64并且链表长度大于8

退化情况1:在扩容的时,如果拆分树时,树元素<=6 则退化为链表

退化情况2:remove树节点时root,root.left,root.right,root.left.left有一个为null,也会退化为链表

加载因子为何是0.75f?

  1. 在空间占用和查询时间之间取得较好的权衡

  2. 大于0.75,空间节省了,但会造成链表过长,影响性能

  3. 小于1.75,冲突减少了,但扩容会更频繁,占用更多空间

hashMap的key能否为null ,作为key的对象有什么要求?

  1. hashMap的key可以为null,Map的其他实现则不行,例如:treeMap,hashTable, CurrentHashMap

  2. 作为key的对象,必须重写hashcode和equals方法,并且key的内容不能修改。

重写hashcode是为了让map的key具有更好的分布性。重写equals是为了计算,当两个对象的索引相同的情况下,比较两个值的内容是否相同。如果hashcode相同,不一定equals,但是两个对象如果equals,则hashcode肯定相同

创建单例模式的几种方式!

/**
 * 饿汉式单例
 */
public class Singleton1 implements Serializable {

    //1. 构造私有
    private Singleton1() {
        // 预防反射破坏单例
        if (INSTANCE != null){
            throw new RuntimeException("不能重复创建单例");
        }
        System.out.println("私有构造");
    }
    //2. 静态成员变量类型为当前类本身,new 的值为当前私有构造创建的唯一实例
    private static final Singleton1 INSTANCE = new Singleton1();

    //3. 静态方法返回当前实例
    public static Singleton1 getInstance() {
        return INSTANCE;
    }
    // 预防实现Serializable 后反序列化破坏单例
    public Object readResolve(){
        return INSTANCE;
    }

}

/**
 * 枚举饿汉式单例
 */
enum Singleton2{
    INSTANCE;

    //以下代码为测试使用
    Singleton2(){
        System.out.println("私有构造");
    }

    public static Singleton2 getInstance() {
        return INSTANCE;
    }

    @Override
    public String toString() {
        return getClass().getName()+"@" +Integer.toHexString(hashCode());
    }
}

/**
 * 懒汉式单例
 */
class Singleton3{
    private Singleton3(){
        System.out.println("私有构造");
    }
    private static Singleton3 INSTANCE = null;

    /**
     * synchronized 解决多线程调用时,创建的对象不唯一问题
     */
    public static synchronized Singleton3 getInstance() {
        if (INSTANCE == null){
            INSTANCE = new Singleton3();
        }
        return INSTANCE;
    }

}

/**
 * 懒汉式单例 -DCL
 */
class Singleton4{
    private Singleton4(){
        System.out.println("私有构造");
    }

    /**
     * 使用双检索创建静态变量时必须加 volatile 修饰
     * 解决共享变量的可见性,有序性,不能保证原子性
     */
    private static volatile Singleton4 INSTANCE = null;

    /**
     * DCL 双检锁懒汉式单例
     * 解决多线程调用时,创建的对象不唯一问题
     */
    public static Singleton4 getInstance() {
        if (INSTANCE == null){
            synchronized(Singleton4.class){
                if (INSTANCE == null){
                    INSTANCE = new Singleton4();
                }
            }
        }
        return INSTANCE;
    }
}

/**
 * 内部类懒汉式单例 --推荐此方式创建
 * 优点:既满足了懒汉式特性,又解决了线程安全的问题
 */
class Singleton5{
    private Singleton5(){
        System.out.println("私有构造");
    }
    private static class Holder{
        static Singleton5 INSTANCE = new Singleton5();
    }

    public static Singleton5 getInstance(){
        return Holder.INSTANCE;
    }
}

JDK中单例模式的应用:

  1. Runtime 饿汉式单例实现

  2. System中Console 使用双检锁懒汉式单例

  3. Collections 中使用大量的单例模式

对象引用类型分为哪几类?

  1. 强引用 : 普通变量赋值即为强引用 例如:User u = new User();

    通过 GC Root 的引用链如果强引用不到该对象,该对象才能被回收

  2. 软引用(SoftReference)

    ① 例如:SoftReference a = new SoftReference(new A());

    ② 如果仅有软引用该对象时,首次垃圾回收不会回收该对象,如果内存 GC仍不足,再次回收时才会释放对象

    ③ 软引用自身需要配合引用队列来释放

    ④ 典型例子是反射数据

  3. 弱引用(WeakReference)

① 例如:WeakReference a = new WeakReference(new A());

②如果仅有弱引用引用该对象时,只要发生垃圾回收,就会释放该对象

③ 弱引用自身需要配合引用队列来释放

④ 典型例子是 ThreadLocalMap 中的 Entry 对象

  1. 虚引用(PhantomReference)

① 例如:PhantomReference a = new PhantomReference(new A());

②必须配合引用队列一起使用,当虚引用引用的对象被回收时会将虚引用对象入队,由 Reference Handler 线程释放其关联的外部资源

③ 典型例子是 Cleaner 释放 DirectByteBuffer 占用的直接内存

本文链接:https://www.518wz.top/post/20.html 转载需授权!

分享到:

发表评论