目录
14.String,StringBuffer,StringBuilder的区别
15.Jdk1.7到Jdk1.8 HashMap发生了什么变化(底层)?
13.List和Set的区别
List:有序,可重复。
有序是指按对象进入的顺序进行保存对象,允许多个Null元素对象,可以使用iterator取出所有元素。
在逐一遍历时,我们知道List是有序的,所有我们可以通过get(int index)的方式获取指定下标的元素
Set:无序,不可重复,
最多允许有一个Null元素对象,我们前面的题分析过,HashSet底层是HashMap,HashMap的put方法中表示如果key为null,那么直接加在第一个索引位置,所以最多有一个Null元素
其次取元素时,只能用iterator接口取得所有元素,不可以用for循环是因为Set没有get(int index)方法,因为Set它是无序的,所以我们无法根据index下标去获取正确的值
14.String,StringBuffer,StringBuilder的区别
1.String是被final修饰的,是不可变的,如果尝试去修改时,是会新生成一个字符串对象,而不是把原来的字符串进行修改。即不是在原对象上进行操作的
但是StringBuffer,StringBuilder是可变的,是在原对象上进行操作的
2.StringBuffer是线程安全的,源码可知:有锁,使得线程同步
StringBuilder是线程不安全的,
使用场景:
如果在单线程的环境下选择效率更高的StringBuilder。
但是多线程并发的时候为了线程安全则应该使用StringBufer
性能:
StringBuilder>StringBuffer>String
15.Jdk1.7到Jdk1.8 HashMap发生了什么变化(底层)?
1. 1.7中底层是数组+链表,然而1.8底层是数组+链表+红黑树,增加红黑树的目的是提高HashMap进行插入或查询元素时的整体效率。【因为如果链表的长度过长,那么遍历起来的效率过低】
2. 1.7中链表插入使用的是头插法,每一次插入元素时插入到链表的头部。但是Jdk1.8中链表插入元素使用的是尾插法,原因是1.8中插入key和value时需要判断链表的元素个数【因为达到一定数量之后会进行树化,转化为红黑树】,所以使用尾插法
3. 1.7中哈希算法比较复杂,存在各种右移或异或运算,但是1.8进行了简化,因为复杂的哈希算法目的就是为了提高散列性【减少哈希冲突】以此来提高HashMap的整体效率。
但是1.8中引入了红黑树,所以适当简化哈希算法【因为这个算法运算也会消耗很多的CPU资源】
16.接口和抽象类的区别
1.抽象类可以存在普通成员函数,而接口中只能存在public abstract方法
2.抽象类中的成员变量可以是各种各样的,而接口中的成员变量只能是public static final类型的,因此接口中的成员变量必须赋初值进行初始化
3.抽象类只能继承一个,接口可以实现多个
4.抽象类可以有抽象的方法也可以有非抽象的方法,而接口中只可存在public abstract方法
多说几句:我们这里接口中为什么必须要直接进行初始化?
如果final修饰的是类变量(即是静态变量),我们只能在静态初始化块中指定初始值或者声明的时候就初始化。
原因:因为类变量(即是静态变量)是当类加载时就应该进行完初始化操作,
如果我们放在构造器中进行初始化的话,就会报错,因为只有创建对象时才会进行构造器初始化。
我们new 一个对象分为两大步:1.进行类加载 2.才是进行new对象(即是创建对象)
但是接口中不可以有static代码块,所以必须在申明成员变量时就直接进行初始化赋值
深度回答如下:
17.泛型中extends和super的区别
1.<? extends T>表示包括T在内的任何T的子类
2.<? super T>表示包括T在内的任何T的父类
18.深拷贝和浅拷贝
深拷贝和浅拷贝就是指对象的拷贝,
一个对象中存在两种类型的属性:一种是基本数据类型,一种是实例对象的引用
1.浅拷贝是指,只会拷贝基本数据类型的值,以及实例对象的引用地址。并不会赋值一份引用地址指向的对象,也就是说我们浅拷贝之后的对象,内部类的属性指向的是同一个对象
2.深拷贝是指,即会拷贝基本数据类型的值,也会针对实例对象的引用地址所指向的对象进行复制,深拷贝出来的对象,内部类的属性指向的不是同一个对象
19.什么是面向对象?谈谈你对面向对象的理解
面向对象的三大特征
多态:基于对象引用所属类不同,外部对同一个方法的调用,实际执行的逻辑不同。
【意思就是说基于这个对象引用对应的运行类型的不同,当这个对象引用对同一个方法进行调用时根据动态绑定机制,运行时看右,实际执行的代码不同】
多态的条件:继承,方法重写,父类引用指向子类对象
格式:
父类类型 变量名=new 子类对象;
变量名.方法名();
注意:我们无法调用子类特有的功能,只可以调用子类和父类公有的方法。
编译时看左边的编译类型,如果父类没有这个方法,那么编译的时候就会直接报错。
运行时看右边的运行类型,也就是子类类型,调用方法时发生运行时绑定,调用同一个方法所展现出结果的多样性,这就是多态
20.重写和重载的区别
重载:
发生在同一个类中,方法名必须相同,
参数类型不同,个数不同,顺序不同。三者中至少有一样不同
方法返回值和访问修饰符可以不同,也可以相同【无要求】,发生在编译时
重写:
发生在父子类中,方法名,参数列表必须相同,
返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类;
如果父类的方法修饰符为private时,则子类不能重写该方法
例子:
编译报错,因为对于重载而言返回值和访问修饰符无要求,可以相同也可以不同
21.为什么用线程池?解释下线程池参数
1.降低资源消耗;提高线程利用率,降低创建和销毁线程所带来的消耗
2.提高响应速度;任务来了,直接有线程可用可执行,而不是先创建线程,再执行
3.提高线程的可管理性;线程是稀缺资源,使用线程池可以统一的分配调优监控
22.简述线程池的处理流程
线程池执行任务,当任务进入时先判断核心线程是否已满,
如果核心线程未满,那么创建核心线程执行任务。如果已满,去看看任务队列是否已满。
如果任务队列未满,那么将任务放进任务队列。如果已满,去看看是否已达到最大线程数
如果最大线程数未达到,那么创建临时线程执行。如果已达到,那么根据拒绝策略处理执行任务
多说一嘴:
当这个临时线程处理完一个任务之后,它会去看任务队列中是否存在等待被处理的任务,如果存在,那么取出来进行执行这个任务。如果不存在,那么它会进行等待,当等待到一定时间间隔【即是keepAliveTime】之后,临时线程就会销毁。
整理不易,希望对大家有帮助。也希望看到的兄弟能给一个点赞,谢谢啦哈哈哈