Java面试题day1

说一下Java的数据类型有哪些

整数类型

byte:8位有符号整数。

short:16位有符号整数。

int:32位有符号整数.

long:64位有符号整数。

浮点类型

float:32单精度浮点数。

double:64位双精度浮点数。

字符类型

char:16位Unicode字符,表示单个字符。

布尔类型

Boolean:表示真或假的值,true与false。

list和set实现类有哪些

首先复习Collection方法

增:add(Object obj),addAll(Collection coll)。

删:remove(Object obj),remove(int index)。

改:set(int index, Object ele)。

查:get(int index)。

插:add(int index, Object ele),addAll(int index, Collection else)。

长度:size()。

遍历:iterator(),hasNext(),next()。

回归正题

List的实现类:

ArrayList:list的主要实现类,线程不安全。底层使用Object[]数组进行存储,在添加,查询时效率高,在插入与删除数据时效率低。

LinkedList:底层使用双向链表进行存储,在对数据的插入,删除操作效率高,对数据的查找,添加操作效率低。

vector:List的古老实现类,线程安全,效率低。底层使用Object[]数组进行存储。

Set实现类:

HashSet:主要实现类,底层使用HashMap,即使用数组+单向链表+红黑树结构进行存储。

LinkedHashSet:是HashSet的子类,在数组+单项链表+红黑树的基础上,又添加了双向链表,记录添加元素的先后顺序。所以我们可以按照添加元素的顺序实现遍历,便于频繁查询的操作。

TreeSet:底层使用红黑树,可以按照添加元素的指定属性大小顺序进行遍历。

ArrayList与LinkedList区别

从实现上看:

ArrayList是基于数组实现,LinkedList是基于链表实现。

ArrayList底层使用Object[]数组,LinkedList底层使用双向链表。

从数据处理上看:

ArrayList更擅长查询,添加数据。

LinkedList更擅长插入,删除数据。

HashSet加入元素的过程

假如,我要将元素A假如HashSet,流程为:

1.首先调用元素A所在类的hashCode()方法,计算元素A的哈希值,以哈希值为基础,通过算法计算出HashSet底层数组中存放的位置,判断此位置上是否有元素。

若没有元素,则添加成功。

2.若有元素,假如为元素b,则比较元素A与元素B的哈希值。

  若哈希值不相同,则元素A添加成功(按链表存储在该位置)。

3.若哈希值相同,进而调用元素A所在类的equals()方法,若返回true。

  若返回true,则元素A添加失败,若返回false,则元素A添加成功。

在2,3情况中添加的数据,是按照链表形式来添加:

若在jdk7,则元素A放在数组中,指向原来的元素(头插法)。

若在jdk8,则原来的元素在数组中,指向元素A(尾插法)。

HashMap线程安全吗

不安全

jdk7 HashMap线程不安全体现在死循环,数据丢失,主要发生在扩容函数中,调用transfer方法。在执行扩容操作时,数组会重新定位新数组中的索引,并采用头插法将元素迁移到新数组中。头插法会将链表的顺序翻转,这也是死循环的关键点。

我们知道,HashMap扩容并不是直接复制元素到新数组节点,而是采用每个节点的哈希值与新数组的位置进行与操作,这样只会产生两个结果:继续存放原先位置,原先位置+原先数组长度。

因为jdk7采用头插法,所以数据在进行扩容时,加入它们都要去新数组的同一节点,那么它们和旧数组的顺序是颠倒的。

假如旧数组是 1→2→3

那么新数组就是 3→2→1

要是有一个线程先完成了数据迁移,另一个线程还在进行迁移的过程,就有可能出现循环问题

例如,新的是2→1 更新到了主内存中,而另一个线程刚获得新的时间块进行操作,它从主内存获取的数据为新的2→1 ,但它的旧数据还是1→2,这样就是一个环形链表,死循环。

且在这个过程也有可能出现数据丢失,就是本来1→2→3,在多线程进行头插法时,有可能最后会得到2→1,丢失了3。

而在jdk8中,jdk8采用尾插法,很好地避免了死循环链表问题。

jdk8的HashMap会出现的线程问题为:数据覆盖。

在插入数据时,判断是否出现hash碰撞的情况下,假如A,B两个线程都是put操作,并且hash函数计算出的插入下标是相同的,当线程A执行完判断代码后由于时间片耗尽等原因被挂起,而线程b完成了正常的插入操作。当线程A获得时间片时,由于之前已经执行了hash碰撞的判断,所以它并不会再一次进行判断,而是直接插入,导致线程B插入的数据被线程A覆盖了,从而线程不安全。

解决HashMap的线程安全问题

1.使用HashTable:但已经弃用了。HashTable底层几乎所有方法都是用synchronized,这样能确保它是线程安全的,但它的效率会很差。

2.使用Collections,synchronizedMap():在调用该方法时就需要传入一个Map,维护了一个普通对象的Map,和一个排斥锁mutex。如果构造器不传入mutex参数则把排斥锁为this对象,也就是传入的map对象。

例如:Map<String,String> map = Collections.synchronizedMap(new HashMap<>());

3.使用ConcurrentHashMap:因为该方法是很好的实现方法,这里只作简单介绍。

在jdk1.7中:

采用分段锁的机制,实现并发的更新操作,底层采用数组+链表的存储结构,包括两个核心静态内部类 Segment 和 HashEntry。

分段锁:Segment数组中,一个Segment对象就是一把锁,对应一个HashEntry数组,该数组中的数据同步依赖于同一把锁,不同HashEntry数组的读写互不干扰。

继承了AbstractMap类,并且实现了ConcurrentMap接口,并且线程安全的实现了接口中的增删改查的方法。

在jdk1.8中:

抛弃了原有的 Segment 分段锁,来保证采用Node + CAS + Synchronized来保证并发安全性。取消类 Segment,直接用table 数组存储键值对;当 Node对象组成的链表长度超过TREEIFY_THRESHOLD 时,链表转换为红黑树,提升性能。底层变更为数组 + 链表 + 红黑树。

关于详细的HashMap安全问题,在:为什么HashMap线程不安全?以及实现HashMap线程安全的解决方案_hashmap线程安全吗 什么解决方案-CSDN博客有很详细的解析,这里就不多谈及了。

本文基于作者自身的学习总结。如有错误,恳请指出。 如果对您有帮助的话,请给我点个赞吧。作者在后面也会分享文章,要是感兴趣也可以给我点个关注。

  • 6
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值