常见面试题

– 抽象类与接口的区别

1 使用层面
a 变量:抽象类的变量的修饰符可以是任意的,而接口中的变量是常量,如果你使用public static final 修饰,编译器会提示,修饰符定义是多余的
b 方法:抽象类中可以有抽象方法,也可以没有抽象方法,有抽象方法的类一定得定义为抽象类;接口中的方法一定是抽象的,没有方法体。
c 实例化:抽象类和接口无法直接实例化。抽象类的实例化必须通过实现所有抽象方法的子类来实例化,单继承;接口的实例化得通过实现所有抽象方法的实现类,
多实现。未实现接口的所有方法的实现类必须定义为抽象类。
2 设计层面
a 抽象类是对一类具有相同特征的类进行抽象,抽象类抽象出相同特征,然后由具体的子类实现定制化的内容。描述的是"is a/an"的关系,比如说把食物抽象成抽象类food,
,具有颜色、重量属性,吃的行为,水果苹果apple,香蕉banana,均是抽象类food子类,具有颜色,重量等共同属性,定制不同的吃法。苹果是水果,香蕉是水果。抽象类描述的
“is a/an”关系。
b 接口是对一系列的行为进行定义、约束、规范,具备一定的能力。描述的是“like a/an”关系,比如说飞行器plane定义为接口,具备飞fly的能力,然后鸟类bird实现了接口
飞行器,所以鸟类也具备了飞行的能力,必须实现fly方法。不是说鸟类是飞行器,只能说鸟类像飞行器,接口描述的是“like a/an”的关系

– String、StringBuffer、StringBuilder的区别

1 String字符串,final修饰不可变,无法被继承
StringBuffer和StringBuilder都是用来做字符串拼接的,是可变的。
当使用String通过加号运算符进行字符串的拼接实际底层还是创建
StringBuilder进行拼接
(当定义String变量的使用使用+连接符,编译器会进行优化,直接创建拼接后的
字符串)
2 StringBuffer和StringBuilder的区别
StringBuffer的里面的操作方法都被加上了syncronized方法,是线程安全的
StringBuilder里面的操作方法未被syncronized修饰,是线程不安全的
多线程环境下建议使用StringBuffer进行字符串拼接,而其他场景建议使用
StringBuilder,性能较好

– 双亲委派原则

1 定义
java虚拟机加载类一种原则定义。
就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,
如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,
才自己去加载。
包括四种类加载器,层级自上而下依次是:
bootstrap、extention、system、customer。bootstrap分别加载lib下
rt.jar等,extention主要加载ext下的jar包,system主要加载classpath
下的class文件,customer主要加载开发自定义路径下的class文件。
类加载器们的继承关系并不是通过extend关键字维护,而是子加载器中维护了
parent字段,指向父加载器
2 流程
向上查询缓存
依次从子加载器向父加载器遍历,各个类加载器查询自己的缓存,判断自己是否
已经加载过当前需要加载的类,如果已经加载过则直接返回,如果没有则继续向上
查询父加载器。
向下查找路径
如果所有加载器均未加载过当前需要加载的类,则从父加载器依次向下遍历,各个加载器
查找自己的路径下是否存在当前需要加载的类,如果存在则直接加载,如果不存在则继续
向下,到application或者自定义customer加载器为止。如果各个加载的路径下均没有当前
需要加载的类,则抛出异常
3 目的
双亲委派原则如此设计的目的是为了保证类不被重复加载以及虚拟机安全。一个类已经被加载了
则不会被再次加载。虚拟机中同一个类被不同加载器加载,所生成的两个类是不相同的,这样设计
从而避免了通过自定义加载器加载了java核心类库而发生字节码串改的问题,从而保证虚拟机
安全。

– list和set的区别

1 基本特征
list有序、可重复 按插入顺序,允许多个null值
set无序、不可重复 不是按插入顺序,只允许一个null值
2 遍历方式
list既可以按下标进行取值,也可以使用迭代器进行遍历
set只能通过迭代器进行遍历

– ArrayList和LinkedList

1 数据结构
ArrayList底层是动态数组,数据查询较快,插入较慢
LinkedList底层是链表,插入较快,查询较慢
2 内存空间
ArrayList要求内存空间连续,而LinkedList不要求内存空间
连续,就内存空间要求来讲ArrayList的要求比LinkedList要求高
3 查询
由于ArrayList的内存空间连续且数据存储类型一致,支持下标访问,
查询比较快
LinkedList是分散在内存空间,只能通过遍历一个个节点,获取到节点
中指针,获取到元素位置,查询比较慢
4 插入
LinkedList是内存不连续的,插入数据数据直接插入,然后维护链路
每插入元素,需要创建额外的对象Node,如果插入的元素较多,则额外创建
Node对象比较消耗性能
ArrayList插入慢(扩容、复制 指定ArrayList的合适长度并采取尾插法)
5 遍历
LinkedList遍历 for循环效率低 需要一个元素一个元素去遍历
indexOf导致链表整个遍历

– HashMap与HashTable的区别

1 继承实现
HashMap与HashTable均实现了Map、Cloneable、Serializable,但HashMap继承了AbstractMap,
HashTable继承Dictionary属于遗留类,基本不用
2 HashTable中的方法加上了synchronized修饰,是线程安全,HashMap未被synchronized关键词
修饰,非线程安全的,由于HashTable方法都效率低,基本不用,如果要用的可以ConcurrentHashMap
3 其他
HashMap支持null key、nul值,而HashTable不支持
4 底层实现
底层数据结构是数组+链表,1.8之后链表高度到8,数组长度长度大于64转化为红黑树,元素内部以
Node节点存储
计算key的Hash值,二次Hash之后与数组的长度进行取模,计算元素的下标,找到下标判断是否存在
元素,不存在元素则直接存储,如果存在元素则进行equals比较,如果相等则直接替换,不相等说明
此时是链表,则判断链表高度插入链表,如果链表高度大于8且数组长度大于64转变成红黑树,长度低于
6则转换为链表
5 put方法(1.8 https://blog.csdn.net/shumoyin/article/details/80243419)
1、hash(key),取key的hashcode进行高位运算,返回hash值
2、如果hash数组为空,直接resize()
3、对hash进行取模运算计算,得到key-value在数组中的存储位置i
(1)如果table[i] == null,直接插入Node<key,value>
(2)如果table[i] != null,判断是否为红黑树p instanceof TreeNode。
(3)如果是红黑树,则判断TreeNode是否已存在,如果存在则直接返回oldnode并更新;不存在则直接插入红黑树,++size,超出threshold容量就扩容
(4)如果是链表,则判断Node是否已存在,如果存在则直接返回oldnode并更新;不存在则直接插入链表尾部,判断链表长度,如果大于8则转为红黑树存储,++size,超出threshold容量就扩容

– ConcurrentHashMap1.8前后实现对比

1.7
数据结构:ReentrantLock+Segment+HashEntry,Segment包含一个HashEntry
数组,每个HashEntry又是一个链表结构
数据查询:通过二次Hash,第一次Hash确定是哪一个segment,第二次Hash
定位到元素所在的链表头部
锁:Segment分段锁,继承ReentrantLock,锁定操作的Segment,而其他的Segment
不受影响,并发度为Segment的个数,数组的扩容不会影响其他Segment
1.8
数据结构:synchronized+CAS+Node+红黑树
查找、替换、赋值操作都用CAS操作
锁:锁链表的head节点,不影响其他元素的读写,锁粒度更细,效率更高,扩容时阻塞
所有读写操作,并发扩容
读操作无锁:Node的val和next使用volatile修饰,读写线程对该变量互相可见
数组使用volatile修饰,保证扩容呗线程感知

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值