Java 面试知识初步整理

目录

1. Java 基础知识

      1.1 八大基本类型?

       1.2 抽象类和接口的区别?

       1.3 什么是 Java 反射机制?

       1.4 重载和覆盖的区别?

       1.5 什么是类加载以及双亲委派?

       1.6 什么是多态?多态存在的必要条件?什么是向上转型?

       1.7 关键字:static、final、violate;

       1.8 “==” 和 equals 的区别?

       1.9 为什么重写 equals() 一定要重写 hasCode()?

       1.10 package 的作用?

       1.11 面向对象的特征和优点?

       1.12 byte的取值范围为什么是[-128, 127]?

       1.13 String、StringBuilder、StringBuffer的区别?

       1.14 finally 关键字何时执行,是否一定会被执行?

       1.15 Java NIO 的原理是什么?

2.  集合

       2.1 Voctor、ArrayList、LinkedList 之间的区别,容量如何扩充?

       2.2 HashMap 和 HashTable 的区别?

       2.3 为什么 HashMap 的键值可以为 null,HashTable 不能为 null?

       2.4 描述下 HashMap 的 put() 操作?

       2.5 HashMap 怎么实现线程安全?

       2.6 CurrentHashMap底层原理?

       2.7 HashMap遍历的三种方法

3. Java虚拟机(JVM)

       3.1 JVM 加载 class 文件的原理机制?

       3.2 对象的内存布局?Java 虚拟机组成部分,哪些是线程私有?

       3.3 new, 对象的创建过程?

       3.4 如何判断对象是否存活?

       3.5 垃圾收集算法有哪几个?

       3.6 内存分配与回收策略?

       3.7 什么是 Java 内存模型,以及Java内存模型的作用?

4. 线程和并发

       4.1 什么是线程安全,线程安全的实现方法?

       4.2 线程的优点和缺点?

       4.3 synchronized 和 Lock 锁的区别?

       4.4 sleep() 和 wait() 方法的区别?

       4.5 死锁和死锁的四个必要条件,死锁避免?

       4.6 阻塞队列和生产者和消费者模型?

       4.7 CopyOnWriteArrayList?

       4.8 什么是读写锁(ReentrantReadWriteLock)?


1. Java 基础知识

      1.1 八大基本类型?

       byte, short, int, long, float, double, char, boolean。

 

       1.2 抽象类和接口的区别?

              1、抽象类:abstract class 修饰;接口:interface 修饰。

              2、抽象类:关键字 extends 继承抽象类;接口:implements 实现接口 。

              3、抽象类:单继承;接口:多实现。

              4、抽象类的成员变量:public、protected、private、默认default,成员方法:public、protected、默认default;接口的成员变量:public static final,成员方法:public abstract。

 

       1.3 什么是 Java 反射机制?

               动态获取类的信息和调用类的属性和方法。其本质还是类加载机制,动态编译。

 

       1.4 重载和覆盖的区别?

               1、覆盖是垂直关系,存在于父类和子类之间;重载是水平关系,是同一个类里面的。

               2、覆盖的参数和返回值必须相同;重载的参数类型不同,返回值可以相同;

               3、覆盖是由调用对象的类型决定的;重载是由调用方法的参数决定的。

 

       1.5 什么是类加载以及双亲委派?

               1、类加载机制:将后缀为.class文件加载到内存,经过加载、链接、初始化生成二进制可执行文件。

               加载:将字节码读取到JVM中,并映射为JVM认可的数据结构。

               链接:1)验证:检验是否符合JVM虚拟机规范;

                          2)准备:初始化静态变量为零值;

                          3)解析:将间接引用替换为直接引用。

               初始化:执行类中定义的Java程序代码, 静态变量初始化。

               2、类加载器:通过类的全限定名获取描述此类的二进制文件。

               3、双亲委派:一个类收到了类加载的请求,将请求委派给父类处理,父类无法完成加载请求,子类加载器才会尝试加载。

 

       1.6 什么是多态?多态存在的必要条件?什么是向上转型?

               1、多态:引用变量所指向的具体类型,以及引用变量调用的具体方法在运行期间决定。

               2、多态存在的必要条件:继承、覆盖、重载、父类引用指向子类对象。

               3、向上转型:不可以调用子类有,而父类没有的方法。

 

       1.7 关键字:static、final、violate;

               1、static关键字:为某一数据类型或者对象分配单一的存储空间,实现方法和属性和类关联而不是和对象关联。

                      1)修饰变量:内存中仅有一个全局变量;

                      2)修饰方法:不能访问非静态的成员变量和方法:

                      3)修饰代码块:在类加载阶段初始化;

                      4)修饰内部类:只能访问外部类的静态变量和方法。

                2、final关键字:

                       1)修饰属性:基本类型值不可变,对象引用不可变;

                       2)修饰方法:不可被覆盖;

                       3)修饰类:不可被继承。

                3、violate关键字:每次访问violate修饰的变量都会从相应的内存中仅从存取,用于修饰多线程访问的变量。

 

       1.8 “==” 和 equals 的区别?

              “==”用于比较地址,equals用于比较对象的值

 

       1.9 为什么重写 equals() 一定要重写 hasCode()?

              1、hashCode和equals的约定关系:1)重写 equals() 一定要重写 hasCode();2)对象不同,hashCode可能相同。

              2、重写 equals() 而不重写 hasCode()会造成,对象值相同,但两对象的hashCode不相同,在HashMap对象中会出现相同的对象被存储在不同的key中。同时在,查找的时候,也会出现,hashMap中有该对象,但是却查找不到的情况。

    // hash 在 String 类中有定义,value 是 char 类型数组

    public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;

            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }

       1.10 package 的作用?

              1)多层命名空间,解决命名冲突;

              2)按功能对代码文件进行分门别类,有利于管理,一目了然清晰。

 

       1.11 面向对象的特征和优点?

                1、优点:开发效率高,鲁棒性好,可维护性高。

                2、特点:抽象、继承、封装、多态。

 

       1.12 byte的取值范围为什么是[-128, 127]?

                [-127, 127] 的二进制补码表示为[11111111, 01111111],-128的补码表示为10000000。

 

       1.13 String、StringBuilder、StringBuffer的区别?

               1、String是不可变类,线程安全,内部由final修饰的char数组组成,频繁拼接字符串会在内存存储大量不再使用的字符串,造成内存泄漏。

               2、StringBuilder,线程不安全,可变类。

               3、StringBuffer,线程安全,可变类。

 

       1.14 finally 关键字何时执行,是否一定会被执行?

               在 “try{}”中,return 执行之前执行,不一定,发生错误或者强行退出时不执行。

 

       1.15 Java NIO 的原理是什么?

               NIO 由Buffer、selector、channel 组成,用于解决多线程实现IO时存在的线程开销问题。NIO 首先在服务端创建Selector对象,客户端访问服务端的时候,会创建一个channel,服务端轮询处理所有的channel。

 

 

2.  集合

       2.1 Voctor、ArrayList、LinkedList 之间的区别,容量如何扩充?

             1、Voctor底层由数组组成,线程安全,扩容是2倍增长,查询快,增删慢;

             2、ArrayList底层由数组组成,非线程安全,扩容为1.5倍增长,查询快,增删慢;

            3、LinkedList底层由链表组成,增删快,查询慢。

 

       2.2 HashMap 和 HashTable 的区别?

               1、HashMap 的数据结构为数组+链表组成,非线程安全,关键字和值可以为空,初始size为16,增长因子为0.75,二倍增长;

               2、HashTable 和 hashMap相似,线程安全,关键字和值不可为空,初始size为11,扩容:newsize = oldsize*2+1。

 

       2.3 为什么 HashMap 的键值可以为 null,HashTable 不能为 null?

               HashTable 通过 get() 获得 value 为 null 时,因为多线程的并发访问和修改值,导致无法判断 null 是被设置为空还是从该关键字不存在。 而 Hash Map 是非线程安全的,通过 get() 获得的 value 为 null 或者 0 来判断。

 

       2.4 描述下 HashMap 的 put() 操作?

               1、根据关键字 key 获得 Entry 对象的 hash 码,判断该位置的是否为 null;为 null 的话,直接插入该值;

               2、不为 null,遍历 Entry 对象所在的链表,如果不为空,将该位置的值更新,如果为空,插入链表的尾部;

               3、如果不为空,且链表长度大于8,改为红黑树插入。

    public V put(K key, V value) {
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key);
        int i = indexFor(hash, table.length);
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }

 

       2.5 HashMap 怎么实现线程安全?

               1、synchronizedMap()函数实现;

               2、使用 HashTable;

               3、分段锁:CurrentHashMap。

 

       2.6 ConcurrentHashMap 底层原理?

               Java 1.7版本是通过分段锁机制来实现的,ConcurrentHashMap 初始有16个分段锁Segment,后期不能再进行扩容的。每个分段锁Segment都类似于一个HashMap,继承了重入锁ReentrantLock,有了锁的功能,由数组和链表组成,当链表的长度大于8时,转化为红黑树。

              Java 1.8版本时通过数组 + 红黑树 + CAS (compare and swap) + sychronized 机制 组成,数组可以扩容。

    /** Implementation for put and putIfAbsent */
    final V putVal(K key, V value, boolean onlyIfAbsent) {
        if (key == null || value == null) throw new NullPointerException();
        int hash = spread(key.hashCode());
        int binCount = 0;
        for (Node<K,V>[] tab = table;;) {
            Node<K,V> f; int n, i, fh;
            if (tab == null || (n = tab.length) == 0)
                tab = initTable();
            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
                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 (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;
    }

 

       2.7 HashMap遍历的三种方法

               方法1:使用 For-Each 迭代 entries;

Map<Integer, Integer> map = new HashMap<Integer, Integer>();
for(Map.Entry<Integer, Integer> entry : map.entrySet()){
	System.out.println("key = " + entry.getKey() + ", value = " + entry.getValue())
}

               方法2 使用For-Each迭代keys和values:

Map<Integer, Integer> map = new HashMap<Integer, Integer>();
 
//iterating over keys only
for (Integer key : map.keySet()) {
	System.out.println("Key = " + key);
}
 
//iterating over values only
for (Integer value : map.values()) {
	System.out.println("Value = " + value);
}

               方法3 使用Iterator迭代:

Map<Integer, Integer> map = new HashMap<Integer, Integer>();
Iterator<Map.Entry<Integer, Integer>> entries = map.entrySet().iterator();
while (entries.hasNext()) {
	Map.Entry<Integer, Integer> entry = entries.next();
	System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
}

       2.8 HashMap 是怎么扩容的?为什么要再做一次 Hash 计算?多线程情况下会存在什么问题?

          1、HashMap 的初始大小默认为16,加载因子为0.75,当HashMap 的元素个数/HashMap 大小等于加载因子,则容量扩展为原来的2倍;HashMap 的容量必须是2的幂,因为性能,通过限制 HashMap 的容量是一个2的幂数,定位 Entry 在新 Hash Map 的位置时,h & (length-1) 和 h % length 结果是一致的,Java的%、/操作比&慢10倍左右。

          扩容的时候,当复制旧的 HashMap 到新的 HashMap 时,遇到哈希冲突时,采用头插法将冲突的 Entry 插入到链表中。

          2、不同的键的的hashcode仅仅只能通过低位来区分,高位的信息没有被充分利用。极端情况就是:所有的hashCode低位全相等,而高位不相等,通过一个再 Hash 来减少哈希冲突。

          3、 采用队头插入的方式,导致了HashMap在“多线程环境下”的死循环问题。

    void resize(int newCapacity) {
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
        if (oldCapacity == MAXIMUM_CAPACITY) {//最大容量为 1 << 30
            threshold = Integer.MAX_VALUE;
            return;
        }

        Entry[] newTable = new Entry[newCapacity];//新建一个新表
        boolean oldAltHashing = useAltHashing;
        useAltHashing |= sun.misc.VM.isBooted() &&
                (newCapacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
        boolean rehash = oldAltHashing ^ useAltHashing;//是否再hash
        transfer(newTable, rehash);//完成旧表到新表的转移
        table = newTable;
        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
    }

    void transfer(Entry[] newTable, boolean rehash) {
        int newCapacity = newTable.length;
        for (Entry<K,V> e : table) {//遍历同桶数组中的每一个桶
            while(null != e) {//顺序遍历某个桶的外挂链表
                Entry<K,V> next = e.next;//引用next
                if (rehash) {
                    e.hash = null == e.key ? 0 : hash(e.key);
                }
                int i = indexFor(e.hash, newCapacity);//找到新表的桶位置;原桶数组中的某个桶上的同一链表中的Entry此刻可能被分散到不同的桶中去了,有效的缓解了哈希冲突。
                e.next = newTable[i];//头插法插入新表中
                newTable[i] = e;
                e = next;
            }
        }
    }

3. Java虚拟机(JVM)

       3.1 JVM 加载 class 文件的原理机制?

               类加载机制:将后缀为.class文件加载到内存,经过加载、链接、初始化生成二进制可执行文件。

               加载:将字节码读取到JVM中,并映射为JVM认可的数据结构。

               链接:1)验证:检验是否符合JVM虚拟机规范;

                         2)准备:初始化静态变量为零值;

                         3)解析:将间接引用替换为直接引用。

               初始化:执行类中定义的Java程序代码, 静态变量初始化。

       

       3.2 对象的内存布局?Java 虚拟机组成部分,哪些是线程私有?

             线程私有的程序计数器、虚拟机栈、本地方法栈,线程共享的堆和方法区。

             1、程序计数器:指向当前运行的程序;

             2、虚拟机栈:存放局部变量表、方法栈帧等;

             3、本地方法栈:存放本地方法;

             4、堆:用于存放对象的实例;

             5、方法区:虚拟机加载的类信息、常量、静态变量等;(运行时常量池:存放编译器生成的字面量和符号引用)。

 

       3.3 new, 对象的创建过程?

             1、常量池中查找是否存在;

             2、类加载;

             3、内存分配;

             4、初始化非静态变量为零值;

             5、设置对象头;

             6、执行非静态变量的Java程序代码。

 

       3.4 如何判断对象是否存活?

              1、引用计数法:每个对象拥有一个引用计数器,当对象被引用的时候,计数器+1,引用释放,计数器-1,存在循环引用的风险;

              2、可达性分析算法:以 GC Roots 为起始点,向下进行搜索,搜索走过的路径称为引用链,当该对象不存在到达 GC Roots 时,表示该对象可以被回收;

 

       3.5 垃圾收集算法有哪几个?

              1、标记清除算法;

              2、复制算法;

              3、标记整理算法;

              4、分代回收算法(详细介绍见3.6)。

 

       3.6 内存分配与回收策略?

              新生代内存分为:Eden、Survivor_from、Survivor_to,老年代为:Old;

              新对象在 Eden 分配内存,对象有年龄,每次Eden区内存不够,执行一次新生代GC,对象的年龄增加一岁;新生代GC发生时,JVM会将 Eden 和 Survivor_from 中存活的对象,复制到 Survivor_to 内存中,然后将 Eden 和 Survivor_from 内存清空。

             年龄达到16的送入老年代和大对象会直接进入老年代。

 

       3.7 什么是 Java 内存模型,以及Java内存模型的作用?

               Java 内存模型定义各个变量的访问规则,由 violate、final、sychronized 关键字实现原子性、可见性、有序性,以及 happen before 原则组成,happen before 原则:程序顺序规则、管程规则、violate规则、线程start()规则、线程join()规则、传递性规则。

 

4. 线程和并发

       4.1 什么是线程安全,线程安全的实现方法?

              多个线程访问某个类,始终保持正确的行为。

              1、互斥同步(悲观锁);

              2、非互斥同步(乐观锁)。

 

       4.2 线程的优点和缺点?

       优点:1)充分利用多核 CPU 的资源,提升系统效率;

                 2)建模简单;

                 3)异步事件的简单处理。

       缺点:1)安全性问题(程序执行顺序);

                 2)死锁;

                 3)资源消耗过高、吞吐率过低、响应不灵敏等。

 

       4.3 synchronized 和 Lock 锁的区别?

              1、synchronized 适合线程竞争不激烈的时候;Lock 适合线程竞争激烈的时候。

              2、synchronized 是对象的内置锁,是不可重入锁;Lock 是 ReetrantLock 可重入锁;

              3、synchronized 可用来修饰方法和代码块,托管给JVM执行;Lock 需要通过代码实现,一般在 try/catch 代码块中手动编写和释放;

 

       4.4 sleep() 和 wait() 方法的区别?

              1、sleep()是Thread类的方法,用于控制自身的流程;wait() 是 Object() 类的方法用于线程同步;

              2、sleep()不释放锁;wait()释放锁;

              3、sleep()可以在任何地方;wait()只能在 sychronized 修饰的方法和代码块中。

 

       4.5 死锁和死锁的四个必要条件,死锁避免?

              因为申请资源而互相等待其他线程释放资源,

              死锁的四个必要条件:1)互斥;2)占有并等待;3)不剥夺;4)循环等待。

              破坏死锁的四个必要条件之一,以及银行家算法。

 

       4.6 阻塞队列和生产者和消费者模型?

               有界缓冲区(bounded-buffer)问题,阻塞队列 ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue。

               PriorityBlockingQueue是一个无界队列,它没有限制,在内存允许的情况下可以无限添加元素;它又是具有优先级的队列,是通过构造函数传入的对象来判断,传入的对象必须实现comparable接口。

               

       4.7 CopyOnWriteArrayList?

              写时复制, 在往集合中添加数据的时候,先拷贝存储的数组,然后添加元素到拷贝好的数组中,然后用现在的数组去替换成员变量的数组(就是get等读取操作读取的数组)。多读取,少添加。

 

       4.8 什么是读写锁(ReentrantReadWriteLock)?

              每次只能有一个写线程,但是可以有多个线程并发地读数据。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值