请你谈谈Java中的浅拷贝与深拷贝
浅拷贝,拷贝对象中所有成员变量,但引用类型的成员变量还是指向原来的对象。可以使用Object中clone()方法实现浅拷贝。
深拷贝,拷贝对象的所有成员变量,引用类型的成员变量都指向新的对象。深拷贝可以通过重写clone()方法实现,然后递归克隆引用类型。还有一种方式就是将被拷贝对象序列化,再反序列化完成深拷贝。
请你说说Java中Exception和Error
Exception,应该分为两种:运行时异常(RuntimeException),编译时异常(Exception)。
运行时异常,RuntimeException类及其子类。运行时异常,java编译器不会实现检查它,我们可以不通过try-catch捕获,也不必在方法上声明抛出该异常,编译可以通过。常见的运行时异常有:类转换异常(ClassCastException)、数组越界异常(IndexOutOfBoundsException)、空指针异常(NullPointerException)、算术异常等。
编译时异常,Exception类及其子类,但不是RuntimeException的子类。
编译时异常,java编译器会实现检查,我们要么通过try-catch来捕获异常,要么就在方法上通过throws关键字抛出该异常,才能编译通过。
常见的编译时异常有:IOException、FileNotFoundException、SQLException。
错误(Error),Error类及其子类。当资源不足、约束失败等导致程序无法继续运行的条件发生时,就会发生错误,程序本身无法修改错误。常见的错误有IndexOutOfMemoryError 内存溢出、StackOverFlow堆栈溢出等。
请你说说线程、程序、进程的基本概念,并说说它们的关系。
程序,它是一个含有静态代码的文件,一般存储在磁盘中。
进程,它是对程序的一次执行,进程拥有独立的内存空间和资源,它是系统资源分配的最小单位。
线程,它是系统调度的最小单位。
一个进程可以拥有多个线程,一个线程只能属于一个进程。进程内的多个线程共享进程中的内存空间和资源。java中实现多线程的方式有许多种,比如继承Thread类、实现Runnable接口,实现Callable接口等,使用多线程,每个线程都有自己的程序计数器,运行栈,局部变量,可以提高程序的执行效率,但也会带来线程安全问题。
请你说说java种的IO流
我认为,IO流可以按以下三种方式区分:
- 按流的流向区分,分为输入流和输出流
- 按流操作的单元区分,分为字节流和字符流
- 按流的角色区分,可以分为节点流和处理流,节点流是一种底层流,节点流可以读取从特定的数据源,比如文件。而处理流则是建立在底层流之上的,为底层流提供更强大的读写功能。
所有输入流的基类是InputStream(字节输入流)或Reader(字符输入流)。
所有输出流的基类是OutputStream(字节输出流)或Writer(字符输出流)。
请你说说红黑树的特征
- 它的节点只有红色和黑色
- 它的根节点是黑色的
- 每个叶子节点都是黑色的
- 如果有一个叶子节点是红色的,那么它的子节点一定都是黑色的
- 从一个节点到它的子孙节点的所有路径都包含相同数目的黑节点
请你说说ArrayList扩容机制
当我们调用ArrayList的add方法时,第一步它会进入一个名叫ensureCapacityInternal()方法,并传入最小容量,最小容量是 当前ArrayList的元素总数 + 1,进入此方法后,它会判断ArrayList维护的动态数组是否需要初始化,是,那么就从传入的最小容量和ArrayList定义的默认容量中选出最大值作为最小容量。之后会进入一个叫做ensureExplicitCapacity()方法判断是否要进行扩容,它通过判断传入的最小容量是否大于动态数组的长度,是,则进入一个叫做grow()的方法进行扩容,进入此方法后,它首先会计算出新容量,新容量等于旧容量的1.5倍,然后新容量与所需的最小容量进行比较,如果新容量小于所需的最小容量,那么,就把最小容量的值作为新容量,然后再做一个判断,比较新容量是否大于ArrayList定义的最大容量,如果大于,那么进入一个叫做hugeCapacity()方法,并传入所需的最小容量,然后判断所需最小容量是否大于ArrayList定义的最大容量,是,则返回整型的最大值,否,则返回,ArrayList定义的最大值。最后在扩容方法中执行数组工具类Arrays中的copyOf方法,将旧数组元素复制到新数组,并返回一个扩容的数组,完成扩容。
请你说一下HashMap的底层原理
HashMap的底层是通过一个哈希表来实现的(节点数组+链表+红黑树),当我们插入一个键值对时,底层会调用一个叫做putVal()方法,第一步先获取key的hash值,然后对hash值和数组长度减1的值,做一个与&操作,算出这个key应该在数组中存放的索引,然后判断这个索引的位置是否已经有元素, 如果没有,那么创建一个节点存入该位置。如果有,那么对比此节点的key是否与用户传来的key完全相同(即hashcode和equals都为true),如果完全相同,那么,后面就更新这个节点value值。不相同,则使用拉链法解决哈希冲突,遍历数组中此索引位置的链表,如果发现有节点的key与用户传来的key完全相同,那么,跳出循环,更新这个节点value的值,如果没有,就在链表的尾部插入一个新的节点,插入新节点后,判断节点数目是否大于等于8,是则,进入一个叫做treeifyBin()的方法,判断是否需要将链表转化红黑树,它会先判断当前数组的长度是否小于HashMap定义的转化为红黑树所需的最小长度,是,则对数组进行扩容,不转换成红黑树,否,则将链表转换成红黑树。最后返回putVal()方法完成键值对的插入。