面试题之:接口与抽象类以及部分Collection框架
接口与抽象类
接口和抽象类:都有可以约束子类的作用
接口和抽象类的区别
1、抽象类的修饰符是abstract,继承关键字是:extends,而接口是:implments
2、抽象类是有构造函数,有普通方法,抽象方法还有成员变量
接口:没有构造函数,在jdk1.7之后全部都是抽象方法,在jdk1.8之后新增了静态方法和默认方法。所有的属性都是静态常量
3、抽象类是单一继承,接口可以多实现(Java单一继承多实现)
抽象类为什么有构造函数?但又不能new对象
1、抽象类不能自己创建对象。但是有构造函数?
答案:1、就是给抽象类中的成员属性进行赋值操作,让子类通过super()方法把子类的参数传递给父类成员变量 2、同样分担了责任,保证了单一职能原则,一个类只能
2、接口中:所有的方法都是公开的并且抽象的方法,所有属性都是公开的,并且是静态常量且一定要赋值
注意:
接口中类使用default
修饰词,那么实现它的类就可以不用全都重写,可以选择性的重写方法
Java中Collection框架
常见的数据结构
1、数组与链表的区别
2、链表的操作、如反转、链表环路检测、双向链表、循环链表相关操作
3、队列、栈的应用
4、二叉树的遍历方式及其递归和非递归的实现
5、红黑树的旋转
数据结构:是一种内存分配的一种容器,是用来储存数据使用。它可以保证内存是一种连续的结构形式
算法考点
1、内部排序:如递归排序、交换排序(冒泡、快速)、选择排序、插入排序
2、外部排序:应该掌握如何利用有限的内存配合海量的外部存储来处理超大的数据集
3、那些排序是不稳定的,稳定意味着什么?
4、不同数据集,各种排序的最好喝最差情况
5、如何优化算法
Java集合框架示意图
集合框架体系如图所示
set和list的区别
set接口实例存储的是无序的,不重复的数据。
List接口实例储存的是有序的,可以重复的元素
Set检索效率低下,删除和插入效率高,插入和删除不引起元素位置的改变<实现类有HashSet,TreeSet>
List和数组类似,可以动态增长,根据实际存储的数据长度自动增长List的长度。查找元素效率高,插入删除效率低,因为会引起其他元素位置的改变<实现类有ArrayList、LinkedList、Vector>
数组的注意点
1、Java的数组是不可变的,意思就是:长度一旦被固定就不能去添加超过长度位的元素,否则就会报错:数组越界异常
2、Java中数组是不能够动态进行元素追加。
ArrayList分析
ArrayList是动态数组,解决Java数组的动态扩容的问题。这是用数组来实现的
问题:
1、数组的类型是什么?
因为我们数组可以存储一切,比如字符串,数字等,所以是用Object[]类型
2、如何进行动态扩容的呢?扩容的一个时机是什么?
//扩容的方法
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
//扩容的最大边界值
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
3、ArrayList数组的最大极限是多少?初始化长度又是多少?
如果扩容到最大极限的时候数组长度会-8,减少一次扩容的调用,防止越界
初始化默认长度是10
Private static final int DEFAULT_CAPACITY = 10;
ArrayLsist无参构造函数
无论new ArrayList()还是new HashMap()这种无参数的构造函数,都采用一种:延迟加载的机制来完成初始化。
答案:1、节约内存空间
2、jdk1.8之后是怕我们定义10个长度的数组之后不用,空间就浪费了
在jdk1.8之前 new object[initialCapacity];并初始化10个长度的数组
那么是什么时候初始化的?
- JDK1.8之后数组的无参构造初始化是在add()方法的时候,我们用的时候才创建它。
也可以直接使用 new ArrayList(10)有长度的构造函数,但是不建议这样中。
数组最大值-8的原因:
- 防止栈溢出和越界
- 提前达到最大,减少一次扩容的机会
这个时候你数量在集合中以及存储到极限值,任何一次数组迁移和复制都可能引发内存溢出
new Object[Integer.Max_NUMBER]
是如何进行动态扩容的? 扩容时机是什么?
答:时机:add()的时候,扩容的长度是index=10的时候进行一次扩容再一次扩容index=16的时候 ,(16+16>>1) 再下一次index=24;
1、底层是一个全局的数组结构进行元素的存储
2、在实现构造函数,进行对象数组的初始化,在初始化的时候指明了一个定长的扩容。默认长度是10;
3、在调用add()方法添加元素的时候,如果超过固定长度10,就会调用grow方法进行扩容
读就建议用ArrayList ;;;;写就用LinkedList
Arraylist在删除元素的时候
会把后面元素移动到前面,然后在最后一个元素改了null值。
HashSet的TreeSet
底层是HashMap
也能实现自定义排序 如下面代码就是升序排序
public class one {
public static void main(String[] args) {
TreeSet treeSet = new TreeSet(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
return o1.hashCode() - o2.hashCode();
}
});
treeSet.add(1);
treeSet.add(2);
treeSet.add(3);
treeSet.add(4);
treeSet.add(5);
treeSet.add(6);
System.out.println(treeSet);
}
}
Map分析
HashMap的原理
在JDK1.8之前:数组(查询快)+链表(添加删除快) (Entry<K,V>[])
JDK1.8以后 HashMap采用数组+链表+红黑树(Node<K,V>[])
2、长度为16的数组的头部,存储的是链表的头部元素节点
3、通过类似于hash(key%hashCode())%len的hash算法,来确定元素存储在数组的那个位置
4、map的key是使用Set来存储,Values才用Collection来存储,所以key不允许重复,values可以允许重复。
HashMap无参构造函数
hashMap的无参构造函数采用的是延迟加载的方式进行数组的初始化。
长度是16
public HashMap() {
this.loadfactor = DEFAULT_LOAD_FACTOR;
}
上面代码没有初始化声明,因为它的初始化是在put方法的时候。
数组:Node<K,V>[] tab,那么这个数组的就要初始化
初始化代码resize()方法
数组长度默认是多少? 极限值是多少
1、默认情况:HashMap的数组长度是:16
HashMap的数组的最大容量是:2的30次方
static final int MAXIMUM_CAPACITY = 1<<30;
tableSizeFor方法是用来扩容的方法
static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>>1;
n |= n >>>2;
n |= n >>>4;
n |= n >>>8;
n |= n >>>16;
return (n < 0 ) ? 1: (n>=MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY :n +1;
}
ThresHold这个是用来干嘛的?
threshold:扩容因子
如果你是无参构造函数的hashmap的方法
第一次:threshold = 数组DEFAULT_INITIAL_CAPACITY * loadFactor(0.75) =12
第二次:newThr = oldThr <<1 ; //double threshold后续都是两倍进行扩容
如果你是有参构造函数的hashmap的方法
threshold = tableSizeFor()扩容结果
它的作用是:如果你的数组快满了,那么接下来数组扩大多少数组空间。临界判断是通过threshold判断,但是扩容的大小是通过:数组长度的两倍进行扩容
newCap = oldCap<<1;
newCap = 16 <<1 ;
数组存储的是什么?
长度为16的数组的头部,存储的是链表的头部元素的节点。Node<K,V>
Node<K,V>是如何找打数组的位置的?数组的索引是怎么求出来的?