你值得一看--集合

使用集合框架的好处
1.集合自增长
2提供了高性能的数据结构和算法,提高程序的速度和质量
3允许不同 API 之间的互操作,API之间可以来回传递集合;
4可以方便地扩展或改写集合,提高代码复用性和可操作性。
5通过使用JDK自带的集合类,可以降低代码维护和学习新API成本。

常用的集合类
Map接口和Collection接口是所有集合框架的父接口
1Collection的子接口Set接口和List接口
2Map接口的实现类:HashMap,TreeMap,HashTable,ConcurrentHashMap以及Peoperties
3Set接口的实现类:HashSet,TreeSet,LinkedHashSet
4.List接口的实现类:ArrayList,LinkedList,Vector,Stack

哪些集合是线程安全的
vector:就比arraylist多了个同步化机制(线程安全),因为效率较低,现在已经不太建议使用。在web应用中,特别是前台页面,往往效率(页面响应速度)是优先考虑的。
statck:堆栈类,先进后出。
hashtable:就比hashmap多了个线程安全。
enumeration:枚举,相当于迭代器。
Java集合的快速失败机制 “fail-fast”?
是java集合的一种错误检测机制,当多个线程对集合进行结构上的改变的操作时,有可能会产生 fail-fast 机制。

例如:假设存在两个线程(线程1、线程2),线程1通过Iterator在遍历集合A中的元素,在某个时候线程2修改了集合A的结构(是结构上面的修改,而不是简单的修改集合元素的内容),那么这个时候程序就会抛出 ConcurrentModificationException 异常,从而产生fail-fast机制。

原因:迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历。

解决办法:

在遍历过程中,所有涉及到改变modCount值得地方全部加上synchronized。

使用CopyOnWriteArrayList来替换ArrayList

怎么确保一个集合不能被修改?
可以使用 Collections. unmodifiableCollection(Collection c) 方法来创建一个只读集合,这样改变集合的任何操作都会抛出 Java. lang. UnsupportedOperationException 异常。

示例代码如下:

List<String> list = new ArrayList<>();
list. add("x");
Collection<String> clist = Collections. unmodifiableCollection(list);
clist. add("y"); // 运行时此行报错
System. out. println(list. size());

Collection接口
Iterator迭代器
Iterator是单项遍历,更加安全,可以在迭代的时候进行移除iterator.remove()

for(Integer i:list){
list.remove(i);//会报错
}

java不允许一个线程在遍历的时候,另一个线程对他进行修改

ListIterator只能遍历list,可以双向遍历

遍历List的几种方式
1for循环,基于计数器
2Iterator迭代器,是面向对象的一个设计模式
3foreach循环遍历。内部使用了Iterator

ArrayList查询块怎删慢

如何实现List和数组的转化
数组转List Arrays.asList(array)

List转数组:List自带的toArray()方法

ArrayList和LinkedList区别?
一个底层是数组,一个底层是双向链表。

ArrayList查询快,修改慢
Linked查询慢,修改,新增块

ArrayList和Vector的区别?
线程安全:Vector 使用了 Synchronized 来实现线程同步,是线程安全的,而 ArrayList 是非线程安全的。
性能:ArrayList 在性能方面要优于 Vector。
扩容:ArrayList 和 Vector 都会根据实际的需要动态的调整容量,只不过在 Vector 扩容每次会增加 1 倍,而 ArrayList 只会增加 50%。
Vector类的所有方法都是同步的。可以由两个线程安全地访问一个Vector对象、但是一个线程访问Vector的话代码要在同步操作上耗费大量的时间。

Arraylist不是同步的,所以在不需要保证线程安全时时建议使用Arraylist。

多线程场景下如何使用 ArrayList?
ArrayList 不是线程安全的,如果遇到多线程场景,可以通过 Collections 的 synchronizedList 方法将其转换成线程安全的容器后再使
List 和 Set 的区别
List , Set 都是继承自Collection 接口

List 特点:一个有序(元素存入集合的顺序和取出的顺序一致)容器,元素可以重复,可以插入多个null元素,元素都有索引。常用的实现类有 ArrayList、LinkedList 和 Vector。

Set 特点:一个无序(存入和取出顺序有可能不一致)容器,不可以存储重复元素,只允许存入一个null元素,必须保证元素唯一性。Set 接口常用实现类是 HashSet、LinkedHashSet 以及 TreeSet。

另外 List 支持for循环,也就是通过下标来遍历,也可以用迭代器,但是set只能用迭代,因为他无序,无法用下标来取得想要的值。

Set和List对比

Set:检索元素效率低下,删除和插入效率高,插入和删除不会引起元素位置改变。
List:和数组类似,List可以动态增长,查找元素效率高,插入删除元素效率低,因为会引起其他元素位置改变
SET

HashSet的实现原理
HashSet是基于HashMap实现的,HashSet的值存在HashMap的key上,value统一PERIESENT,HashSet不允许重复的值。
先调用hash和equals来验证是否重复

HashMap原理
jdk1.7是数组+链表,使用头插法
jdk1.8是数组+链表+红黑树,直接集成了扩容函数resize(),无冲突时,存放在数组,有冲突存放链表,冲突大于8个,转换成红黑树。尾插法。红黑树把时间复杂度从O(n)变成O(logN)

HashMap扩容怎么实现的
1)jdk1.8中resize方法时在hashmap的键值对大于阈值的时候或者初始化的时候,调用resize()方法进行扩容

2)每次扩容的时候,都是扩展二倍

3)扩展后Node对象的位置要么在原来的位置,要么移动到原偏移量二倍的位置。

HashMap怎么解决哈希冲突的?
哈希就是散列
当两个的输入值,根据统一散列函数计算出相同的散列的现象,我们就把它就碰撞。

hashMap的数据结构
数组特点就是寻址块,插入和删除慢,
链表的特点时:寻址困难,插入和删除容易
默认hashmap的初始容量是1<<4 就是16

jdk1.7中4次位运算,5次异或运算
jdk1.8中1次位运算,1次异或运算,降低冲突的几率,引入红黑树降低遍历的时间复杂度,使遍历更快。

能否使用任何类作为Map的key
如果是类重写equals()和hashcode()方法

为什么HashMap中String、Integer这样的包装类作为key
因为都是final类型,不可变性,内部已经重写了equals()和hashcode()方法。

hashCode()是需要计算存储数据的存储位置
equals()方法为了保证key在哈希表中的唯一性

HashMap的长度为什么是2的幂次方
这个算法应该如何设计呢?

我们首先可能会想到采用%取余的操作来实现。但是,重点来了:“取余(%)操作中如果除数是2的幂次则等价于与其除数减一的与(&)操作(也就是说 hash%length==hash&(length-1)的前提是 length 是2的 n 次方;)。” 并且 采用二进制位操作 &,相对于%能够提高运算效率,这就解释了 HashMap 的长度为什么是2的幂次方。

那为什么是两次扰动呢?

答:这样就是加大哈希值低位的随机性,使得分布更均匀,从而提高对应数组存储下标位置的随机性&均匀性,最终减少Hash冲突,两次就够了,已经达到了高位低位同时参与运算的目的;

HashMap与HashTable有什么区别?
1.线程安全:HashMap是非线程安全的,HashTable是线程安全的,HashTable北部方法经过synchronized
(保证线程安全使用concurrentHashMap)

2效率:HashMap的效率更高一些

3HashMap允许只有一个key为null,HashTable的key为null会抛异常

4HashTable初始容量是11,每次扩容为原来的2n+1.HashMap默认初始化大小为16,每次扩容为原来2倍

ConcurrentHashMap
线程安全,使用分段锁,并发性能好,默认分配16个Segment

jdk1.7
将数据分称一段一段存储,每一段数据配一把锁,采用Segment+HashEntry方式实现。

jdk1.8,采用Node+CAS+Synchronized来保证线程安全的实现。

并发编程

为了充分利用cpu的计算能力

优缺点:执行下效率高,会产生内存泄漏,上下问切换,线程安全,死锁等问题。

并发编程的三大要素
原子性:一个或多个操作要么都执行要么都不执行

可见性:一个线程对共享变量的修改,另一个线程可以看见

有序性:程序的执行顺序按照代码的先后顺序执行

守护线程和用户线程的区别
用户线程:运行在前台,执行鱼体任务,如程序的主线程
守护线程(Daemon)线程:运行在后台,
为其他线程服务,一旦用户线程结束,守护线程会随jvm一起结束。

setDaemon(true)方法必须在start()方法之前/

linux查看线程的cpu利用率 top-H

什么是线程死锁
两个以上的线程,在竞争过程中,彼此占用对方的资源,导致一种阻塞现象。相互等待的进程称为死锁进程/

死锁的四个必要条件
互斥条件

请求与保持条件

不可剥夺条件

循环等待条件

创建线程的四种方法
1.集成Thread类

2实现Runnable接口

3.实现Callable接口,重写call方法

4通过Executors工具创建线程池

可创建四种线程池:
newFixedThreadPool固定线程个数的线程池
newCacheThreadPool创建缓存的线程池,默认线程缓存60s
newSingleThreadPool创建单线程的线程池
newScheduleThreadPool创建定时任务线程池

Runnable和Callable接口
Runnable接口run发给发无返回值
Callable接口call方法有返回值,Ruture配合用来获取异步执行结果

进程的run()和start()有什么区别?
start用于启动线程,run方法用于执行线程的运行代码,run方法可以重复调用,start方法只能调用一次

为什么调用start方法不直接执行run方法
new一个Thread,线程进入新建状态,调用start方法就绪抓鬼太,只有分配到cpu时间片后自动调用run方法

线程的生命周期及五种基本状态?
1.新建状态

2可运行状态:调用完start方法

3运行状态

4阻塞状态:放弃cpu的执行权。

5死亡状态

Java中用到的线程调度算法
分时调度算法

抢占式调度算法

sleep和wait的区别
两者都可以暂停线程
类得不同:sleep()是Thread线程类的静态方法,wait是Object的方法。

是否释放锁:sleep不释放锁,wait()释放锁

用途不同:wait用线程的通信/交互 sleep()是暂停执行

用法不通:wait方法线程不会自己苏醒,需要调用notify或者notifyAll()方法。。。sleep()方法到时间自己苏醒

线程通信的方法wait,notify,notifyAll为什么定义在Object类中
java中任何对象都可以作为锁,

原创文章 41 获赞 11 访问量 1489

猜你喜欢

转载自blog.csdn.net/weixin_44038332/article/details/105327839