面试 Java 基础八股文十问十答第四期

面试 Java 基础八股文十问十答第四期

作者:程序员小白条,个人博客

相信看了本文后,对你的面试是有一定帮助的!

⭐点赞⭐收藏⭐不迷路!⭐

31.HashMap的put 方法执行过程

HashMap 只提供了 put 用于添加元素,putVal 方法只是给 put 方法调用的一个方法,并没有提供给用户使用。

对 putVal 方法添加元素的分析如下:

  1. 如果定位到的数组位置没有元素 就直接插入。
  2. 如果定位到的数组位置有元素就和要插入的 key 比较,如果 key 相同就直接覆盖,如果 key 不相同,就判断 p 是否是一个树节点,如果是就调用e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value)将元素添加进入。如果不是就遍历链表插入(插入的是链表尾部)。

面试 Java 基础八股文十问十答第四期_第1张图片


32.ConcurrentHashMap的put方法

  1. 根据 key 计算出 hashcode 。
  2. 判断是否需要进行初始化。
  3. 即为当前 key 定位出的 Node,如果为空表示当前位置可以写入数据,利用 CAS 尝试写入,失败则自旋保证成功。
  4. 如果当前位置的 hashcode == MOVED == -1,则需要进行扩容。
  5. 如果都不满足,则利用 synchronized 锁写入数据。
  6. 如果数量大于 TREEIFY_THRESHOLD 则要执行树化方法,在 treeifyBin 中会首先判断当前数组长度 ≥64 时才会将链表转换为红黑树。
public V put(K key, V value) {
    return putVal(key, value, false);
}

/** Implementation for put and putIfAbsent */
final V putVal(K key, V value, boolean onlyIfAbsent) {
    // key 和 value 不能为空
    if (key == null || value == null) throw new NullPointerException();
    int hash = spread(key.hashCode());
    int binCount = 0;
    for (Node<K,V>[] tab = table;;) {
        // f = 目标位置元素
        Node<K,V> f; int n, i, fh;// fh 后面存放目标位置的元素 hash 值
        if (tab == null || (n = tab.length) == 0)
            // 数组桶为空,初始化数组桶(自旋+CAS)
            tab = initTable();
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
            // 桶内为空,CAS 放入,不加锁,成功了就直接 break 跳出
            if (casTabAt(tab, i, null,new Node<K,V>(hash, key, value, null)))
                break;  // no lock when adding to empty bin
        }
        else if ((fh = f.hash) == MOVED)
            tab = helpTransfer(tab, f);
        else {
            V oldVal = null;
            // 使用 synchronized 加锁加入节点
            synchronized (f) {
                if (tabAt(tab, i) == f) {
                    // 说明是链表
                    if (fh >= 0) {
                        binCount = 1;
                        // 循环加入新的或者覆盖节点
                        for (Node<K,V> e = f;; ++binCount) {
                            K ek;
                            if (e.hash == hash &&
                                ((ek = e.key) == key ||
                                 (ek != null && key.equals(ek)))) {
                                oldVal = e.val;
                                if (!onlyIfAbsent)
                                    e.val = value;
                                break;
                            }
                            Node<K,V> pred = e;
                            if ((e = e.next) == null) {
                                pred.next = new Node<K,V>(hash, key,
                                                          value, null);
                                break;
                            }
                        }
                    }
                    else if (f instanceof TreeBin) {
                        // 红黑树
                        Node<K,V> p;
                        binCount = 2;
                        if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                       value)) != null) {
                            oldVal = p.val;
                            if (!onlyIfAbsent)
                                p.val = value;
                        }
                    }
                }
            }
            if (binCount != 0) {
                if (binCount >= TREEIFY_THRESHOLD)
                    treeifyBin(tab, i);
                if (oldVal != null)
                    return oldVal;
                break;
            }
        }
    }
    addCount(1L, binCount);
    return null;
}

33.hashmap子类知道多少?

面试 Java 基础八股文十问十答第四期_第2张图片

34.对于多态的理解是什么?

1.父类的引用指向子类的对象

子类重写父类的方法:子类可以继承父类的方法,并对其进行重写。当通过父类的引用调用这个方法时,实际执行的是子类重写后的方法。

比如Person person = new Student Person是父类 Student ,都有一个工作的方法,student重写工作方法,比如上学。

2.接口的引用指向实现类的对象

  1. List list = new ArrayList(); 2) ArrayList list= new ArrayList()

在第一种情况下,无法使用ArrayList特有的方法,因为声明的是一个List类型的变量,只能使用List接口中定义的方法。而在第二种情况下,声明了一个ArrayList类型的变量,可以使用ArrayList特有的方法。

3.方法的重载

方法的重载:方法重载指的是在同一个类中定义多个同名但参数列表不同的方法。在调用这个方法时,编译器会根据参数的类型和数量来确定具体调用哪个方法。

4.方法重写

4.1子类中的方法必须与父类中的方法具有相同的名称。

4.2子类中的方法必须具有相同的参数列表(参数的类型、顺序和数量)。

4.3子类中的方法的返回类型可以是父类方法返回类型的子类型(也称为协变返回类型)。

4.4子类中的方法不能缩小父类方法的访问权限(即不能将父类方法的访问权限由public改为private),不能更加严格,但是可以扩大访问权限。

5.向上转型和向下转型

5.1向上转型(Upcasting):将一个子类对象转换为父类类型。这是一个隐式的转型过程,不需要显式地进行类型转换。

5.2向下转型(Downcasting):将一个父类对象转换为子类类型。这是一个显式的转型过程,需要使用强制类型转换符进行类型转换。需要注意进行类型检查,避免类型转换异常。

35.对于static变量的理解?static变量分配内存的时候发生在哪个环节?

static变量是一种静态变量,它在程序执行期间保持不变。它被所有类的对象所共享,这意味着无论创建多少个类的对象,静态变量只有一个副本

静态变量在类定义时被声明,而不是在对象创建时分配内存。内存分配发生在程序加载时,在程序的生命周期内只会分配一次

静态变量是在类加载阶段的第二阶段连接(第二阶段准备阶段)从方法区取出内存,进行静态变量的默认初始化,真正被赋值的时候是第三阶段初始化。

36.JDK1.8对于方法区的实现是?(元空间)元空间还会存放什么东西?

  • 为什么永久代(HotSpot虚拟机)要改成方法区(元数据区)?

因为永久代的垃圾回收条件苛刻,所以容易导致内存不足,而转为元空间,使用本地内存,极大减少了 OOM 异常,毕竟,现在电脑的内存足以支持 Java 的允许。

  • 只有 Hotspot 才有永久代。BEA JRockit、IBMJ9 等来说,是不存在永久代的概念的。
  • 必要性:1.为永久代设置空间大小是很难确定的 2.对永久代进行调优是很困难的。

元数据区(MetaSpace)存放:1.运行时常量池和静态常量池(字符串常量池放在堆中) 2.类元信息(类的二进制字节码文件,类的名称,父类,接口,成员变量,方法,静态变量,常量,常量池的符号引用和指向其他类的引用) 3.方法元信息(方法访问修饰符,返回类型,参数类型,异常类型)

37.为什么String要设计成Final类?

只有当字符串是不可变的,字符串池才有可能实现

字符串池的实现可以在运行时节约很多heap空间,因为不同的字符串变量都指向池中的同一个字符串。但如果字符串是可变的,那么String interning将不能实现(注:String interning是指对不同的字符串仅仅只保存一个,即不会保存多个相同的字符串。),因为这样的话,如果变量改变了它的值,那么其它指向这个值的变量的值也会一起改变。

如果字符串是可变的,那么会引起很严重的安全问题

譬如,数据库的用户名、密码都是以字符串的形式传入来获得数据库的连接,或者在socket编程中,主机名和端口都是以字符串的形式传入。因为字符串是不可变的,所以它的值是不可改变的,否则黑客们可以钻到空子,改变字符串指向的对象的值,造成安全漏洞。

因为字符串是不可变的,所以是多线程安全的

同一个字符串实例可以被多个线程共享。这样便不用因为线程安全问题而使用同步。字符串自己便是线程安全的。

类加载器要用到字符串,不可变性提供了安全性,以便正确的类被加载

譬如你想加载java.sql.Connection类,而这个值被改成了myhacked.Connection,那么会对你的数据库造成不可知的破坏。

作为Map的key,提高了访问效率

因为字符串是不可变的,所以在它创建的时候hashcode就被缓存了,不需要重新计算。这就使得字符串很适合作为Map中的键,字符串的处理速度要快过其它的键对象。这就是HashMap中的键往往都使用字符串。因为Map使用得也是非常之多,所以一举两得

38.什么情况下会导致元数据区溢出?

1.加载的类的数量过多(比如CGLIB不断生成代理类)

2.类的大小过大,包含大量静态属性或常量

3.元数据区的参数设置不当,内存给的太小

堆在什么时候会内存溢出?

堆内存存在大量对象,且对象都有被引用,创建对象不合理。

栈在什么时候会溢出?

递归调用没有写好结束条件,导致栈溢出。

频繁FullGC的几种原因?

拓展:新生代占比堆的三分之一,而老年代占比堆的三分之二。1:2

新生代占比:Eden8,S0,S1 各占1份,8:1:1

① 系统承载高并发请求,或者处理数据量过大,导致YoungGC很频繁,而且每次YoungGC过后存活对象太多,内存分配不合理,Survivor区域过小,导致对象频繁进入老年代,频繁触发FullGC

② 系统一次性加载过多数据进内存,搞出来很多大对象,导致频繁有大对象进入老年代,然后频繁触发FullGC

③ 系统发生了内存泄漏,创建大量的对象,始终无法回收,一直占用在老年代里,必然频繁触发FullGC

④ Metaspace 因为加载类过多触发FullGC

⑤ 误调用 System.gc() 触发 FullGC

39.Java的常量优化机制

数值常量优化机制

1、给一个变量赋值,如果“=”号右边是常量的表达式没有一个变量,那么就会在编译阶段计算该表达式的结果。

2、然后判断该表达式的结果是否在左边类型所表示范围内。

3、如果在,那么就赋值成功,如果不在,那么就赋值失败。

byte b1  = 1 + 2;
System.out.println(b1);
// 输出结果 3

这个就是典型的常量优化机制,1 和 2 都是常量,编译时可以明显确定常量结果,所以直接把 1 和 2 的结果赋值给 b1 了。(和直接赋值 3 是一个意思)

编译器对 String 的常量也有优化机制
String s1 = "abc";
String s2 = "a"+"b"+"c";
System.out.println(s1 == s2); // true
String a = "a1";
String b = "a" + 1; // 常量+基础数据类型
System.out.println((a == b));  //result = true

String a = "atrue";
String b = "a" + true;
System.out.println((a == b));  //result = true

String a = "a3.4";
String b = "a" + 3.4;
System.out.println((a == b));  //result = true
String s1 = "ab";
String s2 = "abc";
String s3 = s1 + "c"; // 变量+常量
System.out.println(s3 == s2); // false
final String s1 = "ab";
String s2 = "abc";
String s3 = s1 + "c"; //变量相加
System.out.println(s2 == s3); // true

40.Comparable和Comparator接口区别和对比使用场景

1、Comparable 接口简介

Comparable接口:位于java.lang包下,需要重写public int compareTo(T o);

Comparable 是排序接口。若一个类实现了Comparable接口,就意味着“该类支持排序”。

“实现Comparable接口的类的对象”可以用作“有序映射(如TreeMap)”中的键或“有序集合(TreeSet)”中的元素,而不需要指定比较器。

它强行将实现它的每一个类的对象进行整体排序-----称为该类的自然排序,实现此接口的对象列表和数组可以用Collections.sort(),和Arrays.sort()进行自动排序;

接口中通过compareTo(T o)来比较x和y的大小。若返回负数,意味着x比y小;返回零,意味着x等于y;返回正数,意味着x大于y。

2、Comparator 接口简介

Comparator接口:位于java.util包下,需要重写int compare(T o1, T o2);

Comparator 是比较器接口。我们若需要控制某个类的次序,而该类本身不支持排序(即没有实现Comparable接口);

它是针对一些本身没有比较能力的对象(数组)为它们实现比较的功能,所以它叫做比较器,是一个外部的东西,通过它定义比较的方式,再传到Collection.sort()和Arrays.sort()中对目标排序,而且通过自身的方法compare()定义比较的内容和结果的升降序;

int compare(T o1, T o2) 和上面的x.compareTo(y)类似,定义排序规则后返回正数,零和负数分别代表大于,等于和小于。

总结:

1.只要涉及对象大小的比较,就可以实现这两个接口的任意一个。

Comparable接口:通过对象调用重写后的compareTo()

Comparator接口:通过对象调用重写后的compare()

2.如果要调用sort进行排序

Comparable接口:自然排序

Comparator接口:定制排序–>属于临时性的排序规则

3.compare和compareTo方法的对比

compareTo:是拿调这个方法的对象和形参比大小

compare:直接让两个形参比大小

你可能感兴趣的:(面试八股文系列,面试,java,职场和发展)