java-题库
- 一、hashMap的底层实现
- 1.1、hashMap概述
- 1.2、JDK1.7-扩容源码
- 1.3、JDK1.7链表迁移过程
- 1.4、JDK1.8下的扩容机制
- 二、java面向对象的三大特征
- 三、JVM相关
- 四、集合相关
- 五、JDK1.8新特性
- 5.1、接口内可以添加非抽象的方法实现
- 5.2、Lambda表达式
- 5.3、函数式接口
- 5.4、Stream API
- 5.6、引入了ForkJoin框架
- 5.7、引入了新的日期API LocalDate | LocalTime | LocalDateTime
- 六、抽象类和接口的区别
- 七、==和eques区别
一、hashMap的底层实现
1.1、hashMap概述
- HashMap基于Map接口实现,键值对存储,允许使用null 建和null值。
- HashMap无序的,HashMap线程不安全。
1.2、JDK1.7-扩容源码
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
//......
// 扩容方法
void resize(int newCapacity) {
// 1、创建临时变量,将HashMap数组数据赋值给新数组作临时存储
Entry[] oldTable = table;
// 2、判断老数组长度是否超过了允许的最大长度,最大长度为 1 << 30
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
// 3、创建新的Entry数组,并扩容
Entry[] newTable = new Entry[newCapacity];
// 4、扩容赋值,即将老数组中的数据赋值到新数组中
// initHashSeedAsNeeded(newCapacity) 得到的是一个hash的随机值(哈希种子),在计算哈希码时会用到这个种子,作用是减少哈希碰撞
transfer(newTable, initHashSeedAsNeeded(newCapacity));
// 6、扩容后赋值
table = newTable;
threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}
// newTable : 表示新数组,即扩容后创建的新数组
// rehash : 是否需要重新计算哈希值
void transfer(Entry[] newTable, boolean rehash) {
int newCapacity = newTable.length;
// 5、将老map中的数据赋值到新map中(数组和链表复制迁移)
for (Entry<K,V> e : table) {
while(null != e) {
Entry<K,V> next = e.next;
if (rehash) {
e.hash = null == e.key ? 0 :hash(e.key);
}
// 计算Entry元素在Entry[]数组中的位置
int i = indexFor(e.hash, newCapacity);
// 链表头插法赋值过程
e.next = newTable[i];
newTable[i] = e;
e = next;
}
}
}
//......
}
1.3、JDK1.7链表迁移过程
- 主要的代码
e.next = newTable[i];
newTable[i] = e;
e = next;
- 假设HashMap的存储状态如下
- 第一次处理e
//将e的next置为null
e.next = newTable[i]
对oldTable进行遍历的过程中,取出元素e,假设先取出图中的元素e,
在执行这行代码时,相当于断开x位置e与e1的链表关系,并与newTable[i]建立链表关系,
此时newTable[i]位置为null
//将e的赋值给,新数组newTable[i]
newTable[i] = e
此时将oldTable中的e复制到newTable中的i位置,同时链表e指向null
- 第二次处理e1
while(null != e) {
// 这里已经将e.next存储为一个临时变量,也就是e1和e2形成的链表
Entry<K,V> next = e.next;
if (rehash) {
e.hash = null == e.key ? 0 :hash(e.key);
}
......
}
// 此时为 e1,组成的链表
Entry<K,V> next = e.next;
//再进行一次遍历分析,而这次遍历分析从e1开始 即e1.next
e.next = newTable[i]//此为e
newTable[i] = e//此e为e1,他的nect 挂的e,
//即 newTable[i]的第一个元素为e1
- 第n次处理后
链表顺序:
开始为e-->e1-->e2
完成扩容后:
e2-->e1-->e
1.4、JDK1.8下的扩容机制
- 底层实现由之前的 “数组+链表” 改为 “数组+链表+红黑树”。
- 当链表节点较少时仍然是以链表存在,当链表节点较多时(大于8)且数组的长度大于64,时会转为红黑树
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
/**
* put()方法真正逻辑所在
*
* @param hash key的hash值
* @param key key
* @param value value值
* @param onlyIfAbsent true表示不更新现有值
* @param evict 如果为false,则表处于创建模式。HashMap中暂未使用
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
// tab 哈希数组,p 该哈希桶的首节点,n hashMap的长度,i 计算出的数组下标
Node<K,V>[] tab; Node<K,V> p; int n, i;
// 如果数组还没初始化,进行数组初始化。使用懒加载机制,table一开始是没有加载的,等put后才开始加载
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
// 如果根据key计算出的数组位置没有数据
if ((p = tab[i = (n - 1) & hash]) == null)
// 新建一个Node节点插入到相应数据位置。
tab[i] = newNode(hash, key, value, null);
// key的hash值对应的数组下标位置不为空的情况
else {
// e 临时节点 ,k 当前节点的key
Node<K,V> e; K k;
//第一种,数组hash槽首节点的key与当前节点的相等,将当前节点赋值给临时节点e = p
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
// hash槽的数据结构为红黑树
else if (p instanceof TreeNode)
// 在红黑树中进行添加,如果该节点已经存在,则返回该节点(不为null),用与判断put操作是否成功,如果添加成功返回null
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
// 链表节点
else {
// 遍历链表
for (int binCount = 0; ; ++binCount) {
// 如果在链表尾部还没有找到当前key值,则在链表尾部新增节点
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
// 判断是否转红黑树
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
//如果链表中有重复的key,e则为当前重复的节点,并结束链表的循环遍历
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
// 如果key值以前已经存在,则对value进行覆盖,并返回value的旧值。
if (e != null) {
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
// 将红黑树的root节点放到链表队首,并将root Node存储在table数组中。
afterNodeAccess(e);
return oldValue;
}
}
// 走到这说明新增了一个Node节点,对Map的修改次数进行+1
++modCount;
// Map的元素容量+1,如果大于扩容阈值,进行扩容
if (++size > threshold)
resize();
// 模板方法,后置处理器
afterNodeInsertion(evict);
return null;
}
二、java面向对象的三大特征
1、封装:属性私有化,提供setter和getter方法
2、继承:继承是指将多个相同的属性和方法提取出来,新建一个父类
3、多态:分为两种:设计时多态、运行时多态
三、JVM相关
四、集合相关
第一代线程安全集合类
Vector. Hashtable
是怎么保证线程安排的:使用synchronized修饰方法
缺点:效率低下
第二代线程非安全集合类
ArrayList, HashMap
线程不安全,但是性能好,用来替代Vector、Hashtable
使用ArrayList、HashMap,需要线程安全怎么办呢?
使用 Collections.synchronizedList(list);
Collections.synchronizedMap(m);
第三代线程安全集合类
在大量并发情况下如何提高集合的效率和安全呢?
java.utll.concurrent.*
ConcurrentHashMap:
CopyOnWriteArrayList :
CopyOnWriteArraySet: 注意不是 CopyOnWriteHashSet*
底层大都采用Lock锁(1.8的ConcurrentHashMap不使用LocktW ,保证安全的同时,
性能也很高.
五、JDK1.8新特性
5.1、接口内可以添加非抽象的方法实现
java 8允许我们给接口添加一个非抽象的方法实现,只需要使用ddefault关键字即
可,这个特征又叫做犷展方法,
代码如下:
interface Formula {
double calculatefint a);
default double sqrt(int a) {return Math.sqrt(a);}
}
Formula接口在拥有calculate方法之外同时还定义了sqrt方法,实现了Formula接口的子类
只需要实现calculate方法,默认方法sqrl将在子类上可以直接使用.
5.2、Lambda表达式
Lambda 规定接口中只能有一个需要被实现的方法。
5.3、函数式接口
- 函数式接口的提出是为了给Lambda表达式的使用提供更好的支持。
- 简单来说就是只定义了一个抽象方法的接口(Object类的public方法除外),就是函数式接口,并且还提供了注解@FunctionalInterface
5.4、Stream API
- Stream操作的三个步骤:创建stream,中间操作,终止操作
- 创建stream
// 1,校验通过Collection 系列集合提供的stream()或者paralleStream()
List<String> list = new ArrayList<>();
Strean<String> stream1 = list.stream();
// 2.通过Arrays的静态方法stream()获取数组流
String[] str = new String[10];
Stream<String> stream2 = Arrays.stream(str);
// 3.通过Stream类中的静态方法of
Stream<String> stream3 = Stream.of("aa","bb","cc");
// 4.创建无限流
// 迭代
Stream<Integer> stream4 = Stream.iterate(0,(x) -> x+2);
//生成
Stream.generate(() ->Math.random());
- 中间操作(过滤、map)
/**
* 筛选 过滤 去重
*/
emps.stream()
.filter(e -> e.getAge() > 10)
.limit(4)
.skip(4)
// 需要流中的元素重写hashCode和equals方法
.distinct()
.forEach(System.out::println);
/**
* 生成新的流 通过map映射
*/
emps.stream()
.map((e) -> e.getAge())
.forEach(System.out::println);
/**
* 自然排序 定制排序
*/
emps.stream()
.sorted((e1 ,e2) -> {
if (e1.getAge().equals(e2.getAge())){
return e1.getName().compareTo(e2.getName());
} else{
return e1.getAge().compareTo(e2.getAge());
}
})
.forEach(System.out::println);
- 终止操作
/**
* 查找和匹配
* allMatch-检查是否匹配所有元素
* anyMatch-检查是否至少匹配一个元素
* noneMatch-检查是否没有匹配所有元素
* findFirst-返回第一个元素
* findAny-返回当前流中的任意元素
* count-返回流中元素的总个数
* max-返回流中最大值
* min-返回流中最小值
*/
/**
* 检查是否匹配元素
*/
boolean b1 = emps.stream()
.allMatch((e) -> e.getStatus().equals(Employee.Status.BUSY));
System.out.println(b1);
boolean b2 = emps.stream()
.anyMatch((e) -> e.getStatus().equals(Employee.Status.BUSY));
System.out.println(b2);
boolean b3 = emps.stream()
.noneMatch((e) -> e.getStatus().equals(Employee.Status.BUSY));
System.out.println(b3);
Optional<Employee> opt = emps.stream()
.findFirst();
System.out.println(opt.get());
// 并行流
Optional<Employee> opt2 = emps.parallelStream()
.findAny();
System.out.println(opt2.get());
long count = emps.stream()
.count();
System.out.println(count);
Optional<Employee> max = emps.stream()
.max((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));
System.out.println(max.get());
Optional<Employee> min = emps.stream()
.min((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));
System.out.println(min.get());
5.6、引入了ForkJoin框架
- 就是在必要的情况下,将一个大任务,进行拆分(fork)成若干个小任务(拆到不可再拆时),再将一个个的小任务运算的结果进行 join 汇总
/**
* 要想使用Fark—Join,类必须继承
* RecursiveAction(无返回值)
* Or
* RecursiveTask(有返回值)
*
*/
public class ForkJoin extends RecursiveTask<Long> {
/**
* 要想使用Fark—Join,类必须继承RecursiveAction(无返回值) 或者
* RecursiveTask(有返回值)
*
* @author Wuyouxin
*/
private static final long serialVersionUID = 23423422L;
private long start;
private long end;
public ForkJoin() {
}
public ForkJoin(long start, long end) {
this.start = start;
this.end = end;
}
// 定义阙值
private static final long THRESHOLD = 10000L;
@Override
protected Long compute() {
if (end - start <= THRESHOLD) {
long sum = 0;
for (long i = start; i < end; i++) {
sum += i;
}
return sum;
} else {
long middle = (end - start) / 2;
ForkJoin left = new ForkJoin(start, middle);
//拆分子任务,压入线程队列
left.fork();
ForkJoin right = new ForkJoin(middle + 1, end);
right.fork();
//合并并返回
return left.join() + right.join();
}
}
/**
* 实现数的累加
*/
@Test
public void test1() {
//开始时间
Instant start = Instant.now();
//这里需要一个线程池的支持
ForkJoinPool pool = new ForkJoinPool();
ForkJoinTask<Long> task = new ForkJoin(0L, 10000000000L);
// 没有返回值 pool.execute();
// 有返回值
long sum = pool.invoke(task);
//结束时间
Instant end = Instant.now();
System.out.println(Duration.between(start, end).getSeconds());
}
/**
* java8 并行流 parallel()
*/
@Test
public void test2() {
//开始时间
Instant start = Instant.now();
// 并行流计算 累加求和
LongStream.rangeClosed(0, 10000000000L).parallel()
.reduce(0, Long :: sum);
//结束时间
Instant end = Instant.now();
System.out.println(Duration.between(start, end).getSeconds());
}
@Test
public void test3(){
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
list.stream().forEach(System.out::print);
list.parallelStream()
.forEach(System.out::print);
}
5.7、引入了新的日期API LocalDate | LocalTime | LocalDateTime
- LocalDateTime
@Test
public void test(){
// 从默认时区的系统时钟获取当前的日期时间。不用考虑时区差
LocalDateTime date = LocalDateTime.now();
//2018-07-15T14:22:39.759
System.out.println(date);
System.out.println(date.getYear());
System.out.println(date.getMonthValue());
System.out.println(date.getDayOfMonth());
System.out.println(date.getHour());
System.out.println(date.getMinute());
System.out.println(date.getSecond());
System.out.println(date.getNano());
// 手动创建一个LocalDateTime实例
LocalDateTime date2 = LocalDateTime.of(2017, 12, 17, 9, 31, 31, 31);
System.out.println(date2);
// 进行加操作,得到新的日期实例
LocalDateTime date3 = date2.plusDays(12);
System.out.println(date3);
// 进行减操作,得到新的日期实例
LocalDateTime date4 = date3.minusYears(2);
System.out.println(date4);
}
- LocalDate
//获取当前日期,只含年月日 固定格式 yyyy-MM-dd 2018-05-04
LocalDate today = LocalDate.now();
// 根据年月日取日期,5月就是5,
LocalDate oldDate = LocalDate.of(2018, 5, 1);
// 根据字符串取:默认格式yyyy-MM-dd,02不能写成2
LocalDate yesteday = LocalDate.parse("2018-05-03");
// 如果不是闰年 传入29号也会报错
LocalDate.parse("2018-02-28");
六、抽象类和接口的区别
- 描述特征用接口
- 描述概念用抽象类
七、==和eques区别
==号在比较基本数据类型时比较的是值,
而用==号比较两个对象时比较的是两个对象的地址值。
- equals()方法存在于Object类中,因为Object类是所有类的直接或间接父类,也就是说所有的类中的equals()方法都继承自Object类,而通过源码我们发现,Object类中equals()方法底层依赖的是==号,那么,在所有没有重写equals()方法的类中,调用equals()方法其实和使用==号的效果一样,也是比较的地址值,然而,Java提供的所有类中,绝大多数类都重写了equals()方法,重写后的equals()方法一般都是比较两个对象的值.