写在之前
Hello,大家好,好久没有更新文章了,当了很久的咸鱼~
上一篇和大家分享了当初校招时如何艰难的寻找实习,总体写得比较简单,今天给大家分享一下当时准备面试时的一些面试题目,之前秋招过程中得到了多位优秀大佬的指导,也看了很多经典的书籍(比如《Java编程思想》、《深入理解Java虚拟机》、《Java并发编程艺术》等),总结了一些知识点,分享出来与大家共享,同时自己和温习一些知识点。
这篇文章主题主要是《Java基础相关的面试题目》,讲解的结构主要是题目 + 答案展开,大家可以先看题目,再看答案,不建议直接背诵答案。
核心问题十二问
1. String,StringBuilder 以及 StringBuffer三者区别
关于这三个字符串类的异同之处主要关注可变不可变、安全不安全两个方面:
StringBuffer
(同步的)和String
(不可变的)都是线程安全的,StringBuilder
是线程不安全的;String
是不可变的,StringBuilder
和StringBuffer
是可变的;String
的连接操作的底层是由StringBuilder
实现的;- 三者都是
final
的,不允许被继承; StringBuilder
以及StringBuffer
都是抽象类AbstractStringBuilder
的子类,它们的接口是相同的。
为什么String是不可变的?
String
主要的三个成员变量char value[], int offset, int count
均是private,final
的,并且没有对应的getter/setter
;String
对象一旦初始化完成,上述三个成员变量就不可修改;并且其所提供的接口任何对这些域的修改都将返回一个新对象;
2. 重载,重写
- 重载:类内多态,静态绑定机制(编译时已经知道具体执行哪个方法),方法同名,参数不同
- 重写:类间多态,动态绑定机制(运行时确定),实例方法,遵循两小两同一大原则。
方法签名相同,子类的方法所抛出的异常、返回值的范围不大于父类的对应方法,子类的方法可见性不小于父类的对应方法
3. 抽象,封装,继承,多态
Java 的四大特性总结如下:
- 封装:把对象的属性和行为(数据)封装为一个独立的整体,并尽可能隐藏对象的内部实现细节;
- 继承:一种代码重用机制;
- 多态:分离了做什么和怎么做,从另一个角度将接口和实现分离开来,消除类型之间的耦合关系;表现形式:重载与重写;关于多态,大黄之前拙笔一篇《通俗聊聊什么是多态》
- 抽象:对继承的另一种表述;表现形式:接口(契约)与抽象类(模板)
4. ArrayList(动态数组)、LinkedList(带头结点的双向链表)两者区别
1、ArrayList
主要特性如下:
- 默认初始容量为 10
- 扩容机制:添加元素前,先检查是否需要扩容,一般扩为源数组的 1.5 倍 + 1
- 边界检查(即检查
ArrayList
的Size
):涉及到index
的操作; - 调整数组容量(减少容量):将底层数组的容量调整为当前列表保存的实际元素的大小;
- 在查找给定元素索引值等的方法中,源码都将该元素的值分为null和不为null两种情况处理;
关于ArrayList
可以参考之前大黄拙笔一篇《Java面试必考题-ArrayList常见知识点》
2、LinkedList
核心点LinkedList
不但实现了List
接口,还实现了Dequeue
接口。因此,LinkedList
不但可以当做List
来用,还可以当做Stack(push、pop、peek),Queue(offer、poll、peek)
来使用。
3、ArrayList
与LinkedList
两者比较
ArrayList
是基于数组的实现,LinkedList
是基于带头结点的双向循环链表的实现;ArrayList
支持随机访问,LinkedList
不支持;LinkedList
可作队列和栈使用,实现了Dequeue
接口,而ArrayList
没有;ArrayList
寻址效率较高,插入/删除效率较低;LinkedList
插入/删除效率较高,寻址效率较低
5. 容器的Fail-Fast机制
Fail-Fast 是 Java 集合的一种错误检测机制,为了防止在某个线程在对Collection
进行迭代时,其他线程对该Collection
进行结构上的修改。在面对并发的修改时,迭代器很快就会完全失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险,抛出 ConcurrentModificationException
异常。
private class Itr implements Iterator<E> { int cursor; int lastRet = -1; int expectedModCount = ArrayList.this.modCount; public boolean hasNext() { return (this.cursor != ArrayList.this.size); } public E next() { checkForComodification(); /** 省略此处代码 */ } public void remove() { if (this.lastRet < 0) throw new IllegalStateException(); checkForComodification(); /** 省略此处代码 */ } final void checkForComodification() { if (ArrayList.this.modCount == this.expectedModCount) return; throw new ConcurrentModificationException(); } }
6. Set (HashSet,LinkedHashSet,TreeSet)各个实现类区别
Set
不包含重复的元素,这是Set
最大的特点,也是使用Set
最主要的原因。
常用到的Set
实现有 HashSet
、LinkedHashSet
和 TreeSet
。一般地,如果需要一个访问快速的Set
,你应该使用HashSet
;当你需要一个排序的Set
,你应该使用TreeSet
;当你需要记录下插入时的顺序时,你应该使用LinedHashSet
。
1、HashSet
:底层通过HashMap
进行实现,实现了Set
接口。
HashSet
是采用hash
表来实现的,其中的元素没有按顺序排列,add()
、remove()
以及contains()
等方法都是复杂度为O(1)
的方法。
通过观察底层代码发现,HashSet
底层基本上都是通过HashMap
实现
五个构造方法 = 4(基于HashMap的实现,用于实现HashSet) + 1(基于LinkedHashMap的实现,用于实现LinkedHashSet)
2、LinkedHashSet
:是HashSet
的子类,底层主要由HashMap
的子类LinkedHashMap
进行实现,实现了Set
接口
LinkedHashSet
继承于HashSet
,利用下面的HashSet
构造函数即可,注意到,其为包访问权限,专门供LinkedHashSet
的构造函数调用。LinkedHashSet
性能介于HashSet
和TreeSet
之间,是HashSet
的子类,也是一个hash
表,但是同时维护了一个双链表来记录插入的顺序,基本方法的复杂度为O(1)
3、TreeSet
:底层主要由TreeMap
进行实现。
TreeSet
是采用树结构实现(红黑树算法),元素是按顺序进行排列,但是add()
、remove()
以及contains()
等方法都是复杂度为O(log (n))
的方法,它还提供了一些方法来处理排序的set
,如first()、 last()、 headSet()
和 tailSet()
等。此外,TreeSet
不同于HashSet
和LinkedHashSet
,其所存储的元素必须是可排序的(元素实现Comparable
接口或者传入Comparator
),并且不能存放null
值。
7. Map及Map的三种常用实现
链表数组HashMap
,LinkedHashMap
(HashMap
的子类,带头结点的双向链表 + HashMap
),基于红黑树的TreeMap
(对key排序)]
关于HashMap
极度推荐,美团技术团队的——Java 8系列之重新认识HashMap
8. 如何判断容器中两个对象是否相等
判断两个对象的
hashCode
是否相等:如果不相等,认为两个对象也不相等;如果相等,转入第二步;判断两个对象用
equals
运算是否相等:如果不相等,认为两个对象也不相等;如果相等,认为两个对象相等
9. ConcurrentHashMap,HashMap 与 HashTable区别
本质:三者都实现了
Map
接口,ConcurrentHashMap
和HashMap
是AbstractMap
的子类,HashTable
是Dictionary
的子类;线程安全性:
HashMap
是线程不安全的,但ConcurrentHashMap
和HashTable
是线程安全的,但二者保证线程安全的策略不同;前者采用的是分段锁机制,默认理想情况下,可支持16个线程的并发写和任意线程的并发读,效率较高;HashTable
采用的是同步操作,效率较低键值约束:
HashMap
允许键、值为null
,但ConcurrentHashMap
和HashTable
既不允许键为null
,也不允许值为null
;哈希策略:三者哈希策略不同,
HashTable
是key.hashCode
取余;ConcurrentHashMap
与HashMap
都是先对hashCode
进行再哈希,然后再与(桶数 - 1)进行取余运算,但是二者的再哈希算法不同;扩容机制:扩容检查机制不同,
ConcurrentHashMap
和HashTable
在插入元素前检查,HashMap
在元素插入后检查;初始容量:
HashTable
初始容量 11,扩容 2倍 + 1;HashMap
初始容量16,扩容2倍
10. equals, hashCode, ==区别
大黄在之前的文章中已经详细的阐述了三者之间的区别,可以参见《》
1、总结来看有以下几点:
==
用于判断两个对象是否为同一个对象或者两基本类型的值是否相等;equals
用于判断两个对象内容是否相同;hashCode
是一个对象的 消息摘要函数,一种 压缩映射,其一般与equals()
方法同时重写;若不重写hashCode
方法,默认使用Object
类的hashCode
方法,该方法是一个本地方法,由Object
类定义的hashCode
方***针对不同的对象返回不同的整数。
2、集合中使用可变对象作为Key
带来的问题
HashMap
用Key
哈希值来存储和查找键值对,如果HashMap Key
的哈希值在存储键值对后发生改变,那么Map
可能再也查找不到这个Entry
了。也就是说,在HashMap
中可变对象作为Key
会造成 数据丢失。
因此一般有如下建议:
- 在
HashMap
中尽量使用不可变对象作为Key
,比如,使用String
等不可变类型用作Key
是非常明智的或者使用自己定义的不可变类。 - 如果可变对象在
HashMap
中被用作键,那就要小心在改变对象状态的时候,不要改变它的哈希值了,例如,可以只根据对象的标识属性生成HashCode
。
3、重写equals但不重写HashCode会出现的问题
在使用Set
时,若向其加入两个相同(equals
返回为true
)的对象,由于hashCode
函数没有进行重写,那么这两个对象的hashCode
值必然不同,它们很有可能被分散到不同的桶中,容易造成重复对象的存在。
11. 什么是不可变对象
一个不可变对象应该满足以下几个条件:
- 基本类型变量的值不可变;
- 引用类型变量不能指向其他对象;
- 引用类型所指向的对象的状态不可变;
- 除了构造函数之外,不应该有其它任何函数(至少是任何public函数)修改任何成员变量;
- 任何使成员变量获得新值的函数都应该将新的值保存在新的对象中,而保持原来的对象不被修改。
12. Java的序列化/反序列化机制
1、使用Serializable
序列化/反序列化
将实现了Serializable
接口的对象转换成一个字节序列,并能够在以后将这个字节序列完全恢复为原来的对象,序列化可以弥补不同操作系统之间的差异。
序列化具体的含义:
- 需要序列化的对象必须实现
Serializable
接口; - 只有非静态字段和非
transient
字段进行序列化,与字段的可见性无关;
总结
《Offer快到碗里来》面试准备篇——Java基础篇01到这里结束了,这里提到的十二个知识点只是Java知识点中的一小撮,也是从之前准备秋招中挑选出的,尽量避免想与世面的面试题合集重合,后续有什么问题或者有什么想要了解的也可以私信我,我也会陆陆续续的完善Offer快到碗里来系列。
全部评论
(0) 回帖