温馨提示:距离2024年结束还剩18天,剩余约为4.92%...

转载

Java基础面试题

1、ArrayList和LinkedList区别?

  1. 都是List接口的实现类
  2. ArrayList基于数组,LinkedList基于链表
  3. ArrayList:
    • 查询快,增删慢
    • 往数组尾部添加元素的效率高,也就是调用add(obj),但是还是比LinkedList慢。
  4. LinkedList:
    • 数据添加删除效率高,只需要改变指针指向即可
    • 查询数据的平均效率低,需要对链表进行遍历

2、ArrayList扩容机制怎样?

  1. ArrayList每次扩容是原来的1.5倍。
  2. 数组进行扩容时,会将老数组中的元素重新拷贝一份到新的数组中,每次数组容量的增长大约是其原容量的1.5倍。
  3. 代价是很高的,因此在实际使用时,我们应该尽量避免数组容量的扩张。尽可能,就指定其容量,以避免数组扩容的发生。
  4. 创建方式方式不同,容量不同。
public class TestArrayList {
    
    public static void main(String[] args) {
        
        List<Integer> list = new ArrayList<>();
        
        ​
            
            for (int i = 1; i <= 20; i++) {
                
                if (i == 1) {
                    
                    System.out.println("断点位置 没添加元素时候容量 0");
                    
                }
                
                list.add(i);
                
                if (i == 1) {
                    
                    System.out.println("断点位置 第一次添加元素 容量 10");
                    
                }
                
                if (i == 11) {
                    
                    System.out.println("断点位置 第1次扩容 容量 15");
                    
                }
                
                if (i == 16) {
                    
                    System.out.println("断点位置 第2次扩容 容量15*1.5= 22.5,22 or 23 ");
                    
                }
                
            }
        
    }
    
}
public class TestArrayList1 {
    
    public static void main(String[] args) {
        
        List<Integer> list = new ArrayList<>(4);
        
        ​
            
            for (int i = 1; i <= 20; i++) {
                
                if (i == 1) {
                    
                    System.out.println("断点位置 没添加元素时候容量 4");
                    
                }
                
                list.add(i);
                
                if (i == 5) {
                    
                    System.out.println("断点位置 第一次扩容 容量 6 ");
                    
                }
                
                if (i == 7) {
                    
                    System.out.println("断点位置 第二次扩容 容量9");
                    
                }
                
            }
        
    }
    
}

3.HashMap和HashTable的区别?

  1. HashMap(1.8)采用了数组+链表+红黑树的数据结构,能在查询和修改方便继承了数组的线性查找和链表的寻址修改。
  2. HashMap是非synchronized,所以HashMap比HashTable更快。
  3. HashMap可以接受null键和值,而Hashtable则不能(原因就是equlas()方法需要对象,因为HashMap是后出的API经过处理才可以)

4、HashMap的工作原理?

  • HashMap 的存储机制
    • 在 Java 1.8 中,如果链表的长度超过了 8且数组长度最小要达到64 ,那么链表将转化为红黑树;链表长度低于6,就把红黑树转回链表;
  • Java 1.8 中 HashMap 的不同
    • 在 Java 1.8 中,如果链表的长度超过了 8 ,那么链表将转化为红黑树;链表长度低于6,就把红黑树转回链表;
    • 发生 hash 碰撞时,Java 1.7 会在链表头部插入,而 Java 1.8 会在链表尾部插入;
    • 在 Java 1.8 中,Entry 被 Node 代替(换了一个马甲)。
  • put过程
    • 对Key求Hash值,然后再计算下标
    • 如果没有碰撞,直接放入桶中(碰撞的意思是计算得到的Hash值相同,需要放到同一个bucket中)
    • 如果碰撞了,以链表的方式链接到后面
    • 如果链表长度超过阀值( TREEIFY THRESHOLD==8),就把链表转成红黑树,链表长度低于6,就把红黑树转回链表
    • 如果节点已经存在就替换旧值
    • 如果桶满了(容量16*加载因子0.75),就需要 resize(扩容2倍后重排)
  • get过程
    • 当我们调用get()方法,HashMap会使用键对象的hashcode找到bucket位置
    • 找到bucket位置之后,会调用keys.equals()方法去找到链表中正确的节点
    • 最终找到要找的值对象

5、List、Set、Map三个接口的区别以及常见子类?

  1. List、Set单列,Map是双列的键值对
  2. List可重复,set不可重复
  3. List有序的,set是无序
  4. List中最常用的两个子类:ArrayList(基于数组,查询快)和LinkedList(基于链表,增删快)
  5. Set中最常用的两个子类:HashSet和TreeSet
  6. Map中最常用的两个子类:HashMap和TreeMap

6、抽象类接口的区别?

  • 抽象类:
  1. 抽象类使用abstract修饰;
  2. 抽象类不能实例化,即不能使用new关键字来实例化对象;
  3. 含有抽象方法(使用abstract关键字修饰的方法)的类是抽象类,必须使用abstract关键字修饰;
  4. 抽象类可以含有抽象方法,也可以不包含抽象方法,抽象类中可以有具体的方法;
  5. 如果一个子类实现了父类(抽象类)的所有抽象方法,那么该子类可以不必是抽象类,否则就是抽象类;
  6. 抽象类中的抽象方法只有方法体,没有具体实现;
  • 接口:
  1. 接口使用interface修饰;
  2. 接口不能被实例化;
  3. 一个类只能继承一个类,但是可以实现多个接口;
  4. 接口中方法均为抽象方法;
  5. 接口中不能包含实例域或静态方法(静态方法必须实现,接口中方法是抽象方法,不能实现)
  • 区别
  1. 抽象类可以提供成员方法的实现细节,而接口中没有;但是JDK1.8之后,在接口里面可以定义default方法,default方法里面是可以具备方法体的。
  2. 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型;
  3. 接口中每一个方法也是隐式指定为 public abstract,不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态方法;
  4. 一个类只能继承一个抽象类,而一个类却可以实现多个接口。
  • 使用场景
  1. 抽象类用于表示一类事物,抽象概念产品描述。
  2. 接口表示一种能力、规范、约束,实现接口表示具有某种能力。

7、说下ConcurrentHashMap ?

  1. 在ConcurrentHashMap中,无论是读操作还是写操作都能保证很高的性能。
  2. 在进行读操作时(几乎)不需要加锁,而在写操作时通过锁分段技术只对所操作的段加锁而不影响客户端对其它段的访问。
  3. 在理想状态下,ConcurrentHashMap 可支持16个线程执行并发写操作,及任意数量线程的读操作。
  4. 关于它的存储结构
    • JDK 1.7 中使用分段锁(ReentrantLock + Segment + HashEntry),相当于把一个 HashMap 分成多个段,每段分配一把锁,这样支持多线程访问。锁粒度:基于 Segment,包含多个 HashEntry。
    • JDK 1.8 中使用 CAS + synchronized + Node + 红黑树。锁粒度:Node(首结点)(实现 Map.Entry K,V)。锁粒度降低了。

8、final, finally, finalize的区别?

  • final
    • 用于声明属性,方法和类,分别表示属性不可变,方法不可覆盖,类不可继承。内部类要访问局部变量,局部变量必须定义成final类型。java中的final类有java.lang.String、java.lang.Math、java.util.Scanner、java.net.URL、java.lang.reflect.Parameter、java.time.Year等。
  • finally
    • finally是异常处理语句结构的一部分,表示总是执行。
    • finally中的return会覆盖try和catch中的return值。
    • finally语句在return语句执行之后return返回之前执行的。
    • finally语句中没有return语句覆盖返回值,那么原来的返回值可能因为finally里的修改而改变,也可能不变。
    • try或catch代码块中(return/throw)的返回值保留,再来执行finally代码块中的语句,等到finally代码块执行完毕之后,在把之前保留的返回值给返回出去。
    • finally代码块执行不到的情况:
      • 在执行到try catch finally前就抛出了异常
      • try中有 System.exit(0) 代码,会退出JVM
      • 线程在执行 try 语句块或者 catch 语句块时被打断(interrupted)或者被终止(killed),或宕机。
  • finalize
    • finalize是Object类的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法,可以覆盖此方法提供垃圾收集时的其他资源回收,例如关闭文件等。该方法最多只被调用一次,但JVM不保证此方法总被调用。

9.为什么重写了equals要重写hashcode

hashCode 通常在可以起到快速初次判断对象是否相等的作用,可以提高性能。

  1. Hashcode方法是用来计算一个元素在哈希桶中的存储位置
  2. equals方法用来判断当前哈希桶中是否存在相同的元素

hashcode不同, 对象一定不同, 不用再使用equals()比较了;hashcode相同, 对象可能不同, 这个时候再使用equals()进一步比较。

package com.lcuyp.redis.bean;

import java.io.Serializable;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Objects;

/**
* @Description:
* @author: yp
*/
public class Student implements Serializable {

    private String name;
    private String grade;
    private int age;

    public Student() {
    }

    public Student(String name, String grade, int age) {
        this.name = name;
        this.grade = grade;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getGrade() {
        return grade;
    }

    public void setGrade(String grade) {
        this.grade = grade;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }


    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return age == student.age &&
            Objects.equals(name, student.name) &&
            Objects.equals(grade, student.grade);
    }



    @Override
    public String toString() {
        return "Student{" +
            "name='" + name + '\'' +
            ", grade='" + grade + '\'' +
            ", age=" + age +
            '}';
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, grade, age);
    }

    public static void main(String[] args) {

        Student s1 = new Student("zs", "三年级", 18);
        Student s2 = new Student("zs", "三年级", 18);

        System.out.println(s1 == s2); //false
        System.out.println(s1.equals(s2));//true
        System.out.println(s1.hashCode());
        System.out.println(s2.hashCode());

        //如果只重写了equals(),set集合里面会有2个学生, 认为s1和s2不是同一个学生,因为s1和s2的hashcode不一样
        //如果重写了equals()和hashcode(), set集合里面只有1个学生,因为s1和s2的hashcode是一样的, 认为是同一个学生
        HashSet<Student> set = new HashSet<>();
        set.add(s1);
        set.add(s2);
        System.out.println(set.size());

    }
}

  • 作者:CZC(关于作者)
  • 发表时间:2024-08-15 18:30
  • 版权声明
  • 评论区:

    留言