【java学习】集合框架
1,常用map
AbstractMap 的成员变量:transient, volatile修饰
transient volatile Set<K> keySet;//keySet, 保存 map 中所有键的 Set
transient volatile Collection<V> values;//values, 保存 map 中所有值的集合
1)HashMap
HashMap 常用方法:
方法 | 描述 | 备注 |
---|---|---|
clear() | 删除 hashMap 中的所有键/值对 | |
Object remove(Object key)
|
从映像中删除与key相关的映射 | |
clone() | 复制一份 hashMap | |
Object put(Object key, Object value)
|
添加。若k存在则覆盖v,并返回关键字的旧值,否则返回null | |
void putAll(Map t) | 将来自特定映像的所有元素添加给该映像 | |
putAll() | 将所有键/值对添加到 hashMap 中。参数为一个Map | |
putIfAbsent() | 如果 hashMap 中不存在指定的键,则将指定的键/值对插入到 hashMap 中。 | |
containsKey() | 检查 hashMap 中是否存在指定的 key 对应的映射关系。 | |
containsValue() | 检查 hashMap 中是否存在指定的 value 对应的映射关系。 | |
replace() | 替换 hashMap 中是指定的 key 对应的 value。 | |
replaceAll() | 将 hashMap 中的所有映射关系替换成给定的函数所执行的结果。 | |
getOrDefault() | 获取指定 key 对应对 value,如果找不到 key ,则返回设置的默认值 | |
forEach() | 对 hashMap 中的每个映射执行指定的操作。 | |
entrySet() | 返回 hashMap 中所有映射项的集合集合视图。 | |
keySet() | 返回 hashMap 中所有 key 组成的集合视图 |
list = new ArrayList(map.value()); map转list
|
values() | 返回 hashMap 中存在的所有 value 值。 | |
merge() | 添加键值对到 hashMap 中 | |
compute() | 对 hashMap 中指定 key 的值进行重新计算 | prices.compute(“Shoes”, (key, value) -> value - value * 10/100)如果 key 对应的 value 不存在,则返回该 null,如果存在,则返回重新计算后的值。 |
computeIfAbsent() | 对 hashMap 中指定 key 的值进行重新计算,如果不存在这个 key,则添加到 hasMap 中 | |
computeIfPresent() | 对 hashMap 中指定 key 的值进行重新计算,前提是该 key 存在于 hashMap 中。 | |
int size() | 返回当前映像中映射的数量 | |
boolean isEmpty() | 判断map是否有元素。建议使用 |
1> 特点
public class HashMap<K,V>extends AbstractMap<K,V>implements Map<K,V>, Cloneable, Serializable
- 访问速度快
- 无序
- k、v都可以为null,k不可重复
对于key为null的值,在talbe[0]链表中存储。 - 非线程安全;
线程安全时用concurrentHashMap。
2>原理—哈希表(数组)+ 单向链表 + 红黑树
- 哈希函数:hash(key)&(len-1)
实现了均匀的散列,但比直接%len效率高,因为len为2的整数次幂,所以len-1为奇数(最后一位为1),保证了hash值通过&运算后,得到和原hash的低位相同,减少碰撞。 - 哈希冲突:链表定址法。
通过equals判断当前key是否存在,存在则覆盖对应key;
key不冲突,jdk1.7时通过头插法插入链表,新结点作为头结点存入哈希表;jdk1.8时使用尾插法插入链表。
get步骤:1)计算hash值得到哈希数组下标;2)通过equals方法确定key值;3)从头结点开始遍历链表看key是否相等,相等返回。
查找和插入复杂度:O(1) - 当hash表长度>64,链表的长度超过8时,链表会自动转化为红黑树
jdk8的时候加入了红黑树查找时间复杂度由O(N)变成O(lgN)。同时链表采用尾插法。之前的头插法虽然效率高一点,但是并发情况下容易死循环,而头插尾插对红黑树没什么影响。
两个条件有一个不满足则退化为链表。 - 扩容
hash表初始长度是16,每次扩容长度必须是 2的幂。默认加载因子是0.75(超过75%就会开始扩容,容量变成原来的2倍+1)。
每一次put新数据时,都会检查当前容量,如果需要扩容,所有元素都需要按照新的hash算法被算一遍。ReHash代价交够。
因为HashMap的key是int类型,所以最大值是231次方,但是查看源码,当到达 230次方,即MAXIMUM_CAPACITY
之后,便不再进行扩容。 - 线程不安全,支持快速失败。
3>高并发情况下,为什么HashMap出现死锁?
ReHash过程:每个元素重新算hash值,将链表翻转(遍历每个bucket上的链表,然后用头插法插入对应的bucket上的链表中)。对于同时准备扩容的两个线程1和2,如下图:
源码如下:
while(null != e) {Entry<K,V> next = e.next; //线程1还没有执行这句 中断了if (rehash) {e.hash = null == e.key ? 0 : hash(e.key);//计算hash值}int i = indexFor(e.hash, newCapacity);//获取扩容后这个结点对应索引位e.next = newTable[i];//这三行代码表示头插法插入newTable[i] = e;e = next;}
(1)线程1,中断,线程2 reHash
(2)线程2将原表 bucket 1 处的链表分发到 新表 bucket 1 和 bucket 3 上(hash值的后2位,第一位不同,则不是01就是11),分散到 bucket 3上的值有两个, key(3), key(7),遍历原表Bucket 1 上的 链表,采用头插法,结果就是 链表反转且还属于新表此bucket的元素放到 此bucket上。此时 key(7) -> key(3) -> null
(3)此时线程 2 被中断,线程 1调度。
此时线程 1 中 e 是 key(3)-> null,根据源码继续将e插入对应的table[3]链表,头插法,结果为:key(3)->key(7)->key(3),这是一个循环链表。
e = next, e是null,才能跳出循环。上面e永远不会空,死循环了。
4>常量集合
public static final Map<String, String> myMap = Collections.unmodifiableMap(new HashMap<String, String>() {private static final long serialVersionUID = 1L;{put("1", "11");put("2", "22");}});
5>4>空集合
//空集合: 这个空集合没有add方法。 emptyList,emptySet同理
return Collections.emptyMap();
2)Hashtable
JDK1.0。
- k v不可为空;
- 线程安全但效率低下;
是线程安全的hashMap,在hash表上加了全局synchronized(并发下只有一个线程能写hash表)。 推荐使用concurentHashMap。 - 哈希函数:直接使用对象的hashCode;
- 扩容:hash数组默认大小是11,增加的方式是old*2 + 1。
3)ConcurrentHashMap(线程安全且高效)
jdk1.5 java.util.concurrent(简称JUC)包下。
public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>implements ConcurrentMap<K,V>, Serializable {
i>(jdk7):ReentrantLock+segment+HashEntry
在hashmap的基础上,对hash表的每个数组分段加锁(ReentrantLock):
- 1个Segment包含了一个HashEntry数组,每个HashEntry又是一个链表结构。
- segment继承自ReentrantLock,ReentrantLock用来保证segment的线程安全,分段锁提高了效率
- 对数组分段加锁,使用segment分片锁,这样一个线程只会锁住数组的一片,其他线程仍可以访问数组的其他片进行写操作和扩容
ii>(jdk8):synchronized+CAS+红黑树
1.8hashmap的基础上做了优化:
- 加锁
用synchronized对链表头结点加锁;(不影响其它元素读写) - 扩容(阻塞所有的读写、扩容操作)
多线程并发扩容,对原始数组进行分片,每个线程对分片进行扩容。 - size计算
并发量小的时候,通过CAS来实现size递增;
如果并发量大,则通过数组维持元素个数做负载。要增加元素个数,随机取数组中一个数来做CAS。 - 多线程写数据
通过CAS修改,失败后(哈希冲突)通过分段synchronized来写。 - 多线程读数据
无锁。
ii>原理
ConcurrentHashMap 的弱一致性主要是为了提升效率,是一致性与效率之间的一种权衡。要成为强一致性,就得到处使用锁,甚至是全局锁,这就与Hashtable 和同步的HashMap 一样了。
- 修改数据CAS
ConcurrentHashMap并发控制的关键在于一个变量,如下所示:private transient volatile int sizeCtl;
sizeCtl被volatile关键字修饰是一个多线程共享的变量,当它的值为负数的时候说明某个线程正在操作这个Map,想要去操作这个Map的线程就要一直去竞争这个sizeCtl,没有得到这个变量的值就要一直自旋等待这个变量,当占用这个变量的线程操作完成后,要将这个变量的值设置回来,以便让其他线程走出自旋,竞争到该变量。
这种同步进制事实上是一种CAS的做法。 - get()方法的弱一致性
正是因为get 操作几乎所有时候都是一个无锁操作( get 中有一个readValueUnderLock 调用,不过这句执行到的几率极小),使得同一个Segment 实例上的put 和get 可以同时进行,这就是get 操作是弱一致的根本原因。 - clear()方法的弱一致性
因为没有全局的锁,在清除完一个segment 之后,正在清理下一个segment 的时候,已经清理的segment 可能又被加入了数据,因此clear返回的时候,ConcurrentHashMap 中是可能存在数据的。因此,clear 方法是弱一致的。 - ConcurrentHashMap 的弱一致性
在遍历过程中,如果已经遍历的数组上的内容变化了,迭代器不会抛出ConcurrentModificationException 异常。如果未遍历的数组上的内容发生了变化,则有可能反映到迭代过程中。这就是ConcurrentHashMap 迭代器弱一致的表现。
在这种迭代方式中,当iterator 被创建后,集合再发生改变就不再是抛出ConcurrentModificationException,取而代之的是在改变时new 新的数据从而不影响原有的数据,iterator 完成后再将头指针替换为新的数据,这样iterator 线程可以使用原来老的数据,而写线程也可以并发的完成改变,更重要的,这保证了多个线程并发执行的连续性和扩展性,是性能提升的关键。
4)TreeMap
- key有序(Unicode升序),key非null;
基于红黑树实现,可以按照自然顺序或者自定义顺序自动排序,不允许插入null值,查找效率比较高,适合需要排序的场景。 - 非线程安全
5)LinkedHashMap
- key有序(插入顺序)
LinkedHashMap的构造函数里有个布尔参数accessOrder,当它为true时,LinkedHashMap会以访问顺序为序排列元素,否则以插入顺序为序排序元素。
6)WeakHashMap
- 基于HashMap
- key不常使用时,允许GC
WeakReference。
7)ImmutableMap
不可变Map,也就是说初始化之后,不能再往里面put元素了,不然会报异常java.lang.UnsupportedOperationException。
初始化:
public class MapTest {Map<String, String> myMapA = ImmutableMap.of("张三", "北京", "李四", "上海");// 或者Map<String, String> myMapB = ImmutableMap.<String, String>builder().put("张三", "北京").put("李四", "上海").build();
}
<dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>28.1-jre</version>
</dependency>
8) ConcurrentSkipListMap
juc下面的类。
- 线程安全的有序的哈希表
相当于线程安全的TreeMap - 原理:跳表
2,常用List
1)常见用法
1>排序
Collections.sort 排序
//简单的list排序
List<String> list = ...
Collections.sort(list);
自定义排序:建立一个第三方类并实现java.util.Comparator接口。
package com.set;import java.util.Set;
import java.util.TreeSet;class MySort implements java.util.Comparator<Student2>{public int compare(Student2 o1, Student2 o2) { //返回值://0: 相等,左值排在上面//负数:左值小于右值,排在上面//正数:左值大于右值,排在下面return o2.id-o1.id; }
}
class Student2{int id;public Student2(int id) {this.id = id;}@Overridepublic String toString() {return this.id+"";}@Overridepublic int hashCode() {return this.id;}@Overridepublic boolean equals(Object obj) {if (obj instanceof Student2){Student2 stu = (Student2) obj;if (stu.id == this.id)return true;}return false;}
}
public class TreeSetTest2 {public static void main(String[] args) {Set<Student2> set = new TreeSet<Student2>(new MySort());Student2 s1 = new Student2(5);Student2 s2 = new Student2(1);Student2 s3 = new Student2(2);Student2 s4 = new Student2(4);Student2 s5 = new Student2(3);set.add(s1);set.add(s2);set.add(s3);set.add(s4);set.add(s5);for (Student2 s : set) {System.out.println(s);}}}
2>初始化
//预防空指针
List<String> list = null;
List<String> newList = Optional.ofNullable(list).orElse(Lists.newArrayList());
3>常用方法
//是一个private全局变量,在添加和删除时进行修改。作为成员变量是保存在堆中的。int size(); boolean isEmpty(); boolean contains(Object o); //以正确的顺序返回list中元素的迭代器 Iterator<E> iterator();boolean add(E e);boolean addAll(Collection<? extends E> c);//在指定位置插入指定集合boolean addAll(int index, Collection<? extends E> c);//如果指定元素存在list中,移除list中第一次出现的指定元素(实现类可以选择具体的实现)boolean remove(Object o);//判断list中是否包含某个集合boolean containsAll(Collection<?> c);//删除list中包含的Collection中的所有元素boolean removeAll(Collection<?> c);//保留list中包含的Collection中的所有元素boolean retainAll(Collection<?> c);//将该列表的每个元素替换为将该运算符应用于该元素的结果。default void replaceAll(UnaryOperator<E> operator);//对list中的元素排列default void sort(Comparator<? super E> c);void clear();boolean equals(Object o);int hashCode();E get(int index);E set(int index, E element);//在指定位置上增加指定元素void add(int index, E element);//删除指定索引上的元素E remove(int index);//获取对象的第一个索引int indexOf(Object o);//获取对象的最后一个索引int lastIndexOf(Object o);//返回list的list 迭代器ListIterator<E> listIterator();//从指定位置返回list的迭代器ListIterator<E> listIterator(int index);//返回list的子listList<E> subList(int fromIndex, int toIndex);
list转为数组:
Object[] toArray();
//在list的末尾插入元素(实现类可以选择插入的位置)
String[] array=list.toArray(new String[list.size()]);//可以指定类型
4>com.google.common.collect.Lists
import com.google.common.collect.Lists;
//将list等分成多个list
List<List<User>> parts = Lists.partition(users, 50);
//new LinkedList();
Lists.newLinkedList()
//new ArrayList()并且addAll
Lists.newArrayList();Lists.newArrayList("a","b");
2)ArrayList(推荐)
JDK1.2。
- 底层的数据结构:动态数组
- 查询快、尾插法修改快
地址连续,下标随机访问。O(1)
尾插法快(指定初始容量,性能甚至会超过LinkedList);
头插法慢(所有元素要后移一位) - 非线程安全;
- 扩容
当ArrayList中的元素超过它的初始大小时,ArrayList只增加50%的大小。(节约内存)
初始容量默认为10,扩容:((旧容量 * 3) / 2) + 1 - 有序(插入顺序);
- 可重复;
1>Double Brace Initialization初始化,增强可读性
ArrayList<String> lists2 = new ArrayList<String>(){ //这个括号相当于派生自ArrayList<String>的匿名类。如果我们将该匿名类实例通过函数调用等方式传到该类型之外,那么对该匿名类的保持实际上会导致外层的类型无法被释放,进而造成内存泄露。{ //这个括号:由于匿名类中不能添加构造函数,因此这里的instance initializer实际上等于构造函数,用来执行对当前匿名类实例的初始化add("test1");add("test2");}};
实际上,任何类型都可以通过它来执行预初始化:
NutritionFacts cocaCola = new NutritionFacts() {{setCalories(100);setSodium(35); setCarbohydrate(27);
5 }};
2>直接采用[]、{}的形式存入对象
JDK1.7对Java集合增强支持。
直接采用[]、{}的形式存入对象,采用[]的形式按照索引、键值来获取集合中的对象,如下 :
List<String> list=["item"]; //向List集合中添加元素String item=list[0]; //从List集合中获取元素Set<String> set={"item"}; //向Set集合对象中添加元素Map<String,Integer> map={"key":1}; //向Map集合中添加对象int value=map["key"]; //从Map集合中获取对象
3>常量集合
public static final List<String> myList = Collections.unmodifiableList(new ArrayList<String>() {private static final long serialVersionUID = 1L;{add("a");add("b");}});public static final List<String> myList1 = new ArrayList<String>(Arrays.asList("Tom", "Jerry", "Mike"));//常量数组 不允许修改 (没有add方法)
//list = Arrays.asList(str1,str2);str1和str2可以是public static final String
public static final List<String> OBJECTS_LIST = Collections.unmodifiableList(list);//不可变的集合且长度只有1,可以减少内存空间
List<String> list=Collections.singletonList("111");//返回的不可变列表组成的n个拷贝的指定对象。<T> List<T> nCopies(int n, T o)
list = Collections.nCopies(3, 0); //输出list [0, 0, 0]
4>空集合
//空集合: 这个空集合没有add方法。 emptyMap,emptySet同理
return Collections.emptyList();
5>判断两个List集合是否相等
public static boolean CollectionEqual(List<String> list, List<String> list1) {list.sort(Comparator.comparing(String::hashCode));list1.sort(Comparator.comparing(String::hashCode));return list.toString().equals(list1.toString());}
3)LinkedList
- 底层的数据结构:基于双向链表结构;
常用堆栈与队列的实现,地址不连续,查询慢,增删快(性能主要耗费在创建node节点)。 - 非线程安全
- 必须使用迭代器进行访问
使用IndexOf会遍历所有node,性能很差
4)Vector
JDK1.0。
- 底层是数据结构:动态数组
在内存中占用连续的空间; - 线程安全
是线程安全的ArrayList,通过Synchronized修饰。 - 可以设置增长因子;
当Vector中的元素超过它的初始大小时,Vector会将它的容量翻倍。
6)Stack(栈)
- 先进后出(FILO, First In Last Out)。
- 线程安全
继承于Vector。
7)CopyOnWriteArrayList
jdk1.5 java.util.concurrent(简称JUC)包。
i>概念
- 线程安全
相当于线程安全的ArrayList,它实现了List接口。
add方法是加锁的(ReentrantLock),适用于读多写少的场景。 - 写时复制(适用于数据量小的并发)
当有新元素添加到CopyOnWriteArrayList时,先从原有的数组中拷贝一份出来,然后在新的数组做写操作,写完之后,再将原来的数组引用指向到新数组。
慎用!如果数据量过大,每次add/set都要重新复制数组,代价高昂容易引起故障。 - 读的时候并发写读的是旧数据
public boolean add(E e) {final ReentrantLock lock = this.lock;//加的是lock 锁lock.lock();try {Object[] elements = getArray();int len = elements.length;Object[] newElements = Arrays.copyOf(elements, len + 1);newElements[len] = e;setArray(newElements);//将原容器的引用指向新的容器;return true;} finally {lock.unlock();}}
8)Collections 类中提供的静态工厂方法创建的类
Collections.synchronizedXXX(),可以将指定的集合包装成线程同步的集合。比如,
List list = Collections.synchronizedList(new ArrayList());
Set set = Collections.synchronizedSet(new HashSet());
3,Set
Java.util.Set。
1)特点
- 不可重复
作用:询问某个对象是否在某个Set; - 允许null值;
- 无序。
2)方法
- 新建实例
Set<String> set = new LinkedHashSet<String>();
- 遍历
Iterator遍历; - 删除
移除的是值为"a"的项。不能按索引移除。
set.remove("a");
3)HashSet
- 基于hash表实现
地址不连续。
采用hashCode计算hash值。
自定义对象存入hashSet需要重新hashCode和equals方法。 - 非线程安全
- 无序
4)TreeSet
- 有序(Unicode升序)
存放对象不能排序则报错、可指定排序规则。
自定义的对象需要实现Comparable接口的 compareTo(object o)方法 - 原理:红黑树
- 非线程安全
- 不允许插入null值
5)LinkedHashSet
- 有序(插入顺序)
根据元素的hashCode值来决定元素的存储位置,但是它同时使用链表维护元素的次序。
6)CopyOnWriteArraySet
juc包下。
- 线程安全
- 写时复制,读写分离
相当于线程安全的HashSet。
内部包含一个CopyOnWriteArrayList对象。
7)ConcurrentSkipListSet
juc包下。
- 线程安全
- 写时复制,读写分离
是线程安全的有序的集合(相当于线程安全的TreeSet);
通过ConcurrentSkipListMap实现。
4,Arrays数组
1)Array与Arrays的区别
①Array(数组类)
- 效率高,但容量固定且无法动态改变。
- 它无法判断其中实际存有多少元素,length只是告诉我们array的容量。
- 因此除了结构的初始化和销毁之外,数组只有存取元素和修改元素值的操作。
- Array可以存放对象类型、基本数据类型的数据,其中的元素的类型必须相同。
②Arrays(静态类)
此静态类专门用来操作array ,提供搜索、排序、复制等静态方法。
2)数组定义
int [] a;//一开始不定义长度
int [] a = new int[10];
float f[][] = new float[6][6];
float []f[] = new float[6][6];
float [][]f = new float[6][6];
float [][]f = new float[6][];
3)常用方法
①Arrays.asList()
将数组转换成java.util.ArrayList类型;
注意:返回的是Arrays的内部类java.util.Arrays A r r a y L i s t ,而不是 j a v a . u t i l . A r r a y L i s t 。 j a v a . u t i l . A r r a y s ArrayList, 而不是java.util.ArrayList。java.util.Arrays ArrayList,而不是java.util.ArrayList。java.util.ArraysArrayList和java.util.ArrayList都是继承AbstractList,remove、add等方法AbstractList中是默认throw UnsupportedOperationException而且不作任何操作。java.util.ArrayList重了写这些方法而Arrays的内部类ArrayList没有重写,所以使用时会抛出异常。
②Arrays.sort()
数组的排序;
③Arrays.binarySearch()
在排好序的array中二分查找元素。
④Arrays.equals()
比较两个array是否相等。array拥有相同元素个数,且所有对应元素两两相等。
⑤Arrays.fill()
给数组赋初值。
类似C语言中的memset()应用
char *p = new char[90];
memset((void *)p, -2, 90);
//把90个char都赋成-2,因为C++里的char是一个byte(8bit);
java写法:
java.util.Arrays.fill( float[], float)
⑥复制数组
当数据量很大时,复制的效率:System.arraycopy > clone > Arrays.copyOf > for循环
。
1>System.arraycopy
System类源码中给出了arraycopy的方法,是native方法,也就是本地方法,肯定是最快的。
//数据量小的时候,for可能快。
public static void arraycopy(Object src, //源数组int srcPos, //源数组中的起始位置Object dest, //目标数组int destPos, //目标数据中的起始位置int length) //要复制的数组元素的数量
2>clone
java.lang.Object类的clone()方法为protected类型,不可直接调用,需要先对要克隆的类进行下列操作:
首先被克隆的类实现Cloneable接口;
然后在该类中覆盖clone()方法,并且在该clone()方法中调用super.clone();
这样,super.clone()便可以调用java.lang.Object类的clone()方法。
//被克隆的类要实现Cloneable接口class Cat implements Cloneable {private String name;private int age;public Cat(String name, int age) {this.name = name;this.age = age;}//重写clone()方法protected Object clone() throws CloneNotSupportedException {return super.clone();}}public class Clone {public static void main(String[] args) throws CloneNotSupportedException {Cat cat1 = new Cat("xiaohua", 3);System.out.println(cat1);//调用clone方法Cat cat2 = (Cat) cat1.clone();System.out.println(cat2);}}
3>Arrays.copyOf
Arrays.copyOf有十种重载方法,复制指定的数组,返回原数组的副本。具体可以查看jdk api。
4>Arrays.copyOfRange
将数组拷贝至另外一个数组
arr1 = Arrays.copyOfRange(arr, 0, index);参数:
original:第一个参数为要拷贝的数组对象
from:第二个参数为拷贝的开始位置(包含)
to:第三个参数为拷贝的结束位置(不包含)
4)数组名
数组名不等价于指针,只有数组名作为函数参数时,才退化为指针,此时数组名的sizeof()就是指针大小,除了这种情况外,均是整个指整个数组的大小。
char *string_a=(char *)malloc(100*sizeof(char));//对于64位机:sizeof(string_a)为8
char string_b[100];//sizeof(string_b)为100.
6)Array和List区别
- 数组在内存中顺序存储,需要提前知道大小。
- List存储数据都转换为对象,存在不安全类型且需要拆箱装箱。
5,Iterator接口
1)概念
Iterator是所有集合的总接口,其他所有接口都继承于它,该接口定义了集合的遍历操作,Collection接口继承于Iterator,是集合的次级接口(Map独立存在,除外),定义了集合的一些通用操作。
java.util.Iterator其接口定义如下:
public interface Iterator { boolean hasNext(); //判断容器内是否还有可供访问的元素 Object next(); //返回迭代器刚越过的元素的引用,返回值是Object,需要强制转换成自己需要的类型void remove(); //删除迭代器刚越过的元素
}
2)使用
①遍历List、Set、Map
Iterator iterator = list.iterator(); while(iterator.hasNext()){ String s = iterator.next(); if (s.contains("1")) {iterator.remove();//安全删除}}
②安全删除
Iterator支持从源集合中安全地删除对象(在Iterator上调用remove方法)。
Iterator的remove()方法不仅会删除元素,还会维护一个标志,用来记录目前是不是可删除状态,例如,你不能连续两次调用它的remove()方法,调用之前至少有一次next()方法的调用。
如果在循环的过程中调用集合的remove()方法,就会导致循环出错,引发ConcurrentModificationException异常。
原因:Iterator 被创建之后会建立一个指向原来对象的单链索引表,当原来的对象数量发生变化时,这个索引表的内容不会同步改变,所以当索引指针往后移动的时候就找不到要迭代的对象,所以按照 fail-fast原则 Iterator 会马上抛出java.util.ConcurrentModificationException 异常。所以 Iterator 在工作的时候是不允许被迭代的对象被改变的。正确的做法是不用list.remove(),使用Iterator.remove();
4) ListIterator
功能:
- 双向遍历:next() 、 previous()
- 在遍历时修改 List 的元素;
- 遍历时获取迭代器当前游标所在位置。
6,Queue 接口
非阻塞队列:LinkedList
阻塞队列:见JUC
1)java.util.List
jdk5.0以前使用。用来模拟队列。
2)PriorityQueue(优先队列)
1>特性
- 自动排序
对于基本数据类型的包装类(如:Integer,Long等),默认asc排序。
对于自定义类,需要自定义比较器。
PriorityQueue<MaxNum> pq = new PriorityQueue(new Comparator<MaxNum>(){//大于0表示n2>n1public int compare(MaxNum n1, MaxNum n2){ //降序排列return n2.num - n1.num;}});
- 非阻塞队列
- 线程不安全
2>使用
方法 | 说明 | 备注 |
---|---|---|
peek() | 返回队首元素 | |
poll() | 返回队首元素,队首元素出队列。 | 默认asc排序,出min |
add() | 添加元素 | |
size() | 返回队列元素个数 | |
isEmpty() | /判断队列是否为空,为空返回true,不空返回false |
3>原理
二叉堆、小根堆
Object[]
存储数据;
- 跟节点:数组实现的二叉堆,根节点在index=0位置上。
- 左孩子:数组n位置上的元素,其左孩子在[2n+1]位置上。
- 右孩子:数组n位置上的元素,其右孩子在 2(n+1) 位置上。
- 父节点:数组n位置上的元素,其父节点在 (n-1)/2 位置上。
- 最大不能超过Integer.MAX_VALUE - 8
3)阻塞队列(JUC包)
①ArrayBlockingQueue
是数组实现的线程安全的有界的阻塞队列。
在读写操作上都需要锁住整个容器,因此吞吐量与一般的实现是相似的,适合于实现“生产者消费者”模式。
ArrayBlockingQueue 内部还保存着两个整形变量,分别标识着队列的头部和尾部在数组中
的位置。
生产者和消费者共用一把锁。
②LinkedBlockingQueue
概念
是单向链表实现的(指定大小)阻塞队列,该队列按 FIFO(先进先出)排序元素。
能够高效的处理并发数据:对生产者端和消费者端分别采用了独立的锁来控制数据同步,这也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能。
方法
Put()和take()方法:都可以实现阻塞的功能。
Put()方法:把元素加入到阻塞队列中,如果阻塞队列没有空间,则调用此方法的线程被阻塞,直到有空间的时候再继续。
take()方法:取出排在阻塞队列首位的对象,若阻塞队列为空,则调用此方法的线程被阻塞,直到有新的对象被加入的时候再继续。
offer()和poll()方法:不具有阻塞的功能。
offer()方法:把元素加入到阻塞队列中,如果可以容纳,则返回true。如果不可以容纳,则返回false。
poll()方法:取出排在阻塞队列首位的对象,若阻塞队列为空,则返回null,如果不为空,则返回取出来的那个元素。
ArrayBlockingQueue 和LinkedBlockingQueue 的区别:
- 队列大小的初始化方式不同
ArrayBlockingQueue 是有界的,必须指定队列的大小;
LinkedBlockingQueue 是分情况的,指定队列的大小时,就是有界的;不指定队列的大小时,默认是Integer.MAX_VALUE,看成无界队列,但当生产速度大于消费速度时候,有可能会内存溢出。 - 队列中锁的实现不同
ArrayBlockingQueue 实现的队列中的锁是没有分离的,即生产和消费用的是同一个锁;进行put 和take 操作,共用同一个锁对象。也即是说,put 和take 无法并行执行!
LinkedBlockingQueue 实现的队列中的锁是分离的,即生产用的是putLock,消费是takeLock。也就是说,生成端和消费端各自独立拥有一把锁,避免了读(take)写(put)时互相竞争锁的情况,可并行执行。 - 在生产或消费时操作不同
ArrayBlockingQueue 基于数组,在插入或删除元素时,是直接将枚举对象插入或移除的,不会产生或销毁任何额外的对象实例;
LinkedBlockingQueue 基于链表,在插入或删除元素时,需要把枚举对象转换为Node<E>
进行插入或移除,会生成一个额外的Node 对象,这在长时间内需要高效并发地处理大批量数据的系统中,其对于GC 的影响还是存在一定的区别,会影响性能。
③LinkedBlockingDeque
是双向链表实现的(指定大小)双向并发阻塞队列,该阻塞队列同时支持FIFO和FILO两种操作方式。
④ConcurrentLinkedQueue
是单向链表实现的无界队列,该队列按 FIFO(先进先出)排序元素。
⑤ConcurrentLinkedDeque
是双向链表实现的无界队列,该队列同时支持FIFO和FILO两种操作方式。
⑥PriorityBlockingQueue VS PriorityQueue
此阻塞队列为基于数组的无界阻塞队列。它会按照元素的优先级对元素进行排序,按照优先级顺序出队,每次出队的元素都是优先级最高的元素。注意,不会阻塞生产者,但会阻塞消费者。
PriorityBlockingQueue 里面存储的对象必须是实现Comparable 接口,队列通过这个接口的compare 方法确定对象的priority。
队列的元素并不是全部按优先级排序的,但是队头的优先级肯定是最高的。每取一个头元素时候,都会对剩余的元素做一次调整,这样就能保证每次队头的元素都是优先级最高的元素。
⑦DelayQueue
DelayQueue 是一个无界阻塞队列,用于放置实现了Delayed 接口的对象,只有在延迟期满时才能从中提取元素。该队列的头部是延迟期满后保存时间最长的Delayed 元素。这个队列里面所存储的对象都带有一个时间参数,采用take 获取数据的时候,如果时间没有到,取不出来任何数据。而加入数据的时候,是不会阻塞的(不会阻塞生产者,但会阻塞消费者)。
DelayQueue 内部使用PriorityQueue 实现的。DelayQueue 是一个使用PriorityQueue实现的BlockingQueue,优先队列的比较基准值是时间。本质上即:DelayQueue = BlockingQueue +PriorityQueue + Delayed。
优势:
如果不使用DelayQueue,那么常规的解决办法就是:使用一个后台线程,遍历所有对象,挨个检查。这种笨笨的办法简单好用,但是对象数量过多时,可能存在性能问题,检查间隔时间不好设置,间隔时间过大,影响精确度,过小则存在效率问题。而且做不到按超时的时间顺序处理。
应用场景:
缓存系统的设计。缓存中的对象,超过了有效时间,需要从缓存中移出。使用一个线程循环查询DelayQueue,一旦能从DelayQueue 中获取元素时,表示缓存有效期到了。
7,快速失败和安全失败
1)快速失败(fail—fast)
①概念
在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增加、删除、修改),则会抛出Concurrent Modification Exception。
fail-fast是Java集合的一种错误检测机制。当多个线程对集合进行结构上的改变的操作时,有可能会产生fail-fast机制。
②原理
迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历。
注意:这里异常的抛出条件是检测到 modCount!=expectedmodCount 这个条件。如果集合发生变化时修改modCount值刚好又设置为了expectedmodCount值,则异常不会抛出。因此,不能依赖于这个异常是否抛出而进行并发操作的编程,这个异常只建议用于检测并发修改的bug。
③场景
java.util包下的集合类都是快速失败的,不能在多线程下发生并发修改(迭代过程中被修改)。
2)安全失败(fail—safe)
①概念
采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。
②原理
由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发Concurrent Modification Exception。
③缺点
基于拷贝内容的优点是避免了Concurrent Modification Exception,但同样地,迭代器并不能访问到修改后的内容,即:迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在遍历期间原集合发生的修改迭代器是不知道的。
④场景
java.util.concurrent包下的容器都是安全失败,可以在多线程下并发使用,并发修改。
8,JUC(java.util.concurrent)
在 Java 5.0 提供了 java.util.concurrent(简称JUC)包,在此包中增加了在并发编程中很常用的工具类,用于定义类似于线程的自定义子系统,包括线程池,异步 IO 和轻量级任务框架;还提供了设计用于多线程上下文中的 Collection 实现等;
1)copyOn系列的List和Set
2)阻塞队列
3)ConcurrentHashMap
4)Condition
①Condition依赖于Lock接口,生成一个Condition的基本代码是lock.newCondition()
②作用
对锁进行更精确的控制。
对于同一个锁,我们可以创建多个Condition,在不同的情况下使用不同的Condition。
例如:
假如多线程读/写同一个缓冲区:当向缓冲区中写入数据之后,唤醒"读线程";当从缓冲区读出数据之后,唤醒"写线程";并且当缓冲区满的时候,"写线程"需要等待;当缓冲区为空时,“读线程"需要等待。
如果采用Object类中的wait(), notify(), notifyAll()实现该缓冲区,当向缓冲区写入数据之后需要唤醒"读线程"时,不可能通过notify()或notifyAll()明确的指定唤醒"读线程”,而只能通过notifyAll唤醒所有线程(但是notifyAll无法区分唤醒的线程是读线程,还是写线程)。 但是,通过Condition,就能明确的指定唤醒读线程。
③方法
// 造成当前线程在接到信号或被中断之前一直处于等待状态。
void await()
// 造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。 对应Object的wait();
boolean await(long time, TimeUnit unit)
// 造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。
long awaitNanos(long nanosTimeout)
// 造成当前线程在接到信号之前一直处于等待状态。
void awaitUninterruptibly()
// 造成当前线程在接到信号、被中断或到达指定最后期限之前一直处于等待状态。
boolean awaitUntil(Date deadline)
// 唤醒一个等待线程。对应Object的notify();
void signal()
// 唤醒所有等待线程。对应Object的notifyAll()
void signalAll()
不同的是,Object中的wait(),notify(),notifyAll()方法是和"同步锁"(synchronized关键字)捆绑使用的;而Condition是需要与"互斥锁"/"共享锁"捆绑使用的。
5)Semaphore类
控制某个资源可被同时访问的个数,通过构造函数设定一定数量的许可,通过acquire() 获取一个许可,如果没有就等待,而release() 释放一个许可。
下面的例子只允许5 个线程同时进入执行acquire()和release()之间的代码:
public class SemaphoreTest {public static void main(String[] args) {// 线程池ExecutorService exec = Executors.newCachedThreadPool();// 只能5 个线程同时访问final Semaphore semp = new Semaphore(5);// 模拟20 个客户端访问for (int index = 0; index < 20; index++) {final int NO = index;Runnable run = new Runnable() {public void run() {try {// 获取许可semp.acquire();System.out.println("Accessing: " + NO);Thread.sleep((long) (Math.random() * 10000));// 访问完后,释放,如果屏蔽下面的语句,则在控制台只能打印5 条记录,之后线程一直阻塞semp.release();} catch (InterruptedException e) {}}};exec.execute(run);} // 退出线程池exec.shutdown();}
}
6)ReentrantLock类
具有与使用synchronized方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大;
7)Future接口
表示异步计算的结果;
8)java.util.concurrent.CountDownLatch类(锁计数器)
①功能
可以用来在一个线程中等待多个线程完成任务的类。
②原理
CountDownLatch 是通过“共享锁”实现的。
i>创建
CountDownLatch latch = new CountDownLatch(3);
创建时传入一个int 类型参数,表示该“共享锁”最多能被count 个线程同时获取, 这个值只能被设置一次, 而且CountDownLatch 没有提供任何机制去重新设置这个计数值。
ii>await()函数
让线程阻塞等待其他线程,直到CountDownLatch 的计数值变为0,才继续执行之后的操作。
主线程必须在启动其他线程后立即调用await()方法:
WorkerThread t1 = new WorkerThread(latch);
WorkerThread t2 = new WorkerThread(latch);
WorkerThread t3 = new WorkerThread(latch);
t1.start();
t2.start();
t3.start();
latch.await();
iii>countDown()函数
这个函数用来将CountDownLatch 的计数值减1,如果计数达到0,则释放所有等待的线程。
由其他线程调用:latch.countDown();
③场景
一个任务,它需要等待其他的一些任务都执行完毕之后它才能继续执行。
比如:开5 个多线程去下载,当5 个线程都执行完了才算下载成功。
9)java.util.concurrent.CyclicBarrier(循环屏障)
①功能
这个类是一个可以重复利用的屏障类。它允许一组线程相互等待,直到全部到达某个公共屏障点,然后所有的这组线程再同步往后执行。
②原理
await()函数:每被调用一次,计数便会减少1(CyclicBarrier 设置了初始值),并阻塞住当前线程。当计数减至0 时,阻塞解除,所有在此CyclicBarrier 上面阻塞的线程开始运行。
③CountDownLatch 和CyclicBarrier 区别
(1) CountDownLatch 的作用是允许1 个线程等待其他线程执行完成之后,它才执行;而CyclicBarrier 则是允许N 个线程相互等待到某个公共屏障点,然后这一组线程再同时执行。
(2) CountDownLatch 的计数器的值无法被重置,这个初始值只能被设置一次,是不能够重用的;CyclicBarrier 是可以重用的。
④使用
private static final CyclicBarrier cb=new CyclicBarrier(4,new Runnable() {public void run(){System.out.println("寝室四兄弟一起出发去球场");}});//cb传入线程中,线程中通过
//线程通过cb.await();使cb计数器减一
10)生产者消费者问题多种实现
①使用阻塞队列 LinkedBlockingQueue实现
class Producer implements Runnable {private final BlockingQueue sharedQueue;public Producer(BlockingQueue sharedQueue) {this.sharedQueue = sharedQueue;}public void run() {for (int i = 0; i < 10; i++) {try {System.out.println("Produced: " + i);sharedQueue.put(i);} catch (InterruptedException ex) {System.out.println(ex);}}}}class Consumer implements Runnable {private final BlockingQueue sharedQueue;public Consumer(BlockingQueue sharedQueue) {this.sharedQueue = sharedQueue;}public void run() {while (true) {try {int i = (Integer) sharedQueue.take();System.out.println("Consumed: " + i);} catch (InterruptedException ex) {System.out.println(ex);}}}}public class ProducerConsumerPattern {public static void main(String args[]) { BlockingQueue sharedQueue = new LinkedBlockingQueue();Thread prodThread = new Thread(new Producer(sharedQueue));Thread consThread = new Thread(new Consumer(sharedQueue)); prodThread.start();consThread.start();}}
②使用Object 的wait()和notify()实现
PriorityQueue<Integer> queue = new PriorityQueue<Integer>(10);//充当缓冲区class Consumer extends Thread {public void run() {while (true) {synchronized (queue) {while (queue.size() == 0) {//队列空的条件下阻塞try {queue.wait();} catch (InterruptedException e) {e.printStackTrace();queue.notify();}}queue.poll(); // 每次移走队首元素queue.notify();}}}}class Producer extends Thread {public void run() {while (true) {synchronized (queue) {while (queue.size() == 10) {//队列满了的条件下阻塞try {queue.wait();} catch (InterruptedException e) {e.printStackTrace();queue.notify();}}queue.offer(1); // 每次插入一个元素queue.notify();}}}}
③使用Condition 实现
private PriorityQueue<Integer> queue = new PriorityQueue<Integer>(10);private Lock lock = new ReentrantLock();private Condition notFull = lock.newCondition();private Condition notEmpty = lock.newCondition();class Consumer extends Thread {public void run() {while (true) {lock.lock();try {while (queue.size() == 0) {try {notEmpty.await();} catch (InterruptedException e) {e.printStackTrace();}}queue.poll(); // 每次移走队首元素notFull.signal();} finally {lock.unlock();}}}}class Producer extends Thread {public void run() {while (true) {lock.lock();try {while (queue.size() == 10) {try {notFull.await();} catch (InterruptedException e) {e.printStackTrace();}}queue.offer(1); // 每次插入一个元素notEmpty.signal();} finally {lock.unlock();}}}}
【java学习】集合框架相关推荐
- Java学习----集合框架总结
集合框架总结: Collection接口:Set接口:HashSet//对象必须实现hashCode方法,元素没有顺序呢,效率比LinkedHashSet高LinkedHashSet//是HashSe ...
- java三大集合框架(面试知识储备精华篇)
java三大集合框架 : set list map 如上图 set list 都属于collection的子接口(collection为顶层接口) Map 不属于collection接口 Se ...
- java基础—集合框架
java基础-集合框架 JDK1.2开始引入了集合框架的概念,以弥补java中只有数组这种容器的单一问题,这些框架多数由接口构成,另外也包含了一些对于接口实现的类,其中这些接口的最上层接口为java. ...
- java之集合框架一Collection接口
1.集合的由来: 我们学习的是面向对象的语言.而面向对象语言对事物的描述是通过对象体现的.为了方便对多个对象进行操作,我们就必须把多个对象进行存储.而要想存储多个对象,就不能是一个基本的变量,而应该是 ...
- [转载]Java-集合框架完全解析
在简书上看到一篇介绍Java集合框架的文章,写得挺详细的,http://www.jianshu.com/p/63e76826e852这是原文地址. 数据结构是以某种形式将数据组织在一起的集合,它不仅存 ...
- 【JAVA】集合框架及复杂度
初识数据结构 补充 一.初识集合框架 1. 什么是集合框架 2. 集合框架的重要性 3. 背后所涉及的数据结构以及算法 二.时间和空间复杂度 1.如何衡量一个算法的复杂度 2. 算法效率 3. 时间复 ...
- Java程序设计——集合框架
目录 一.概述 Collection:单列集合类的根接口,用于存储一系列符合某种规则的元素,它有两个重要的子接口,分别是List和Set.其中,List的特点是元素有序.元素可重复.Set的特点是元素 ...
- Java 容器集合框架概览
Java Collections Framework 集合的概念 集合collection,有时叫做容器container,把多个元素组成一个单元. 早期的Java (pre-1.2) 中包含了Vec ...
- java hashmap 的api_JAVA基础--JAVA API集合框架(ArrayList、HashSet、HashMap使用)
一.集合Collection 1. 集合介绍 变量:表示的内存中的一个空间,只能保存确定类型的单个数据 数组:表示的是内存中的多个连续的空间,这些空间中可以存储多个同类型的数据. 后期继续学习面向对象 ...
- Java基础-------集合框架
一,集合简介 1.1 集合由来: 对象的存储:①数组(基本数据类型 & 引用数据类型) ②集合(引用数据类型) >数组存储数据的弊端:长度一旦初始化以后,就不可变:真正给数组 ...
最新文章
- linux kbhit扫描键盘,(转)检测按键(Linux中kbhit()函数的实现)
- vue中实现双向数据绑定原理,使用了Object.defineproperty()方法,方法简单
- TF之CNN:Tensorflow构建卷积神经网络CNN的简介、使用方法、应用之详细攻略
- NCTF2019 -- PWN部分writeup
- oracle视图执行脚本,oracle 视图,函数,过程,触发器自动编译脚本
- 安装 Windows 自动化 API 3.0 后,Visual Studio 2010 的运行速度更快
- 中芯国际能靠14nm工艺翻身么?
- 批处理学习笔记6 - 重定向符和
- Unity3d基本优化条目
- keras中无法用save保存模型的问题
- 【优化算法】粒子群优化算法(PSO)【含Matlab源码 1073期】
- 博图导入的程序用step7读出_博图编程与STEP7编程区别
- MATLAB中load用法
- Arcgis空间连接
- QQ聊天记录统计可视化分析
- 对“淡泊以明志,宁静以致远”的理解
- bak 安全牛 kali link
- 关于tomcat启动报错Error deploying web application directory [C:\......]出现的其中一种问题解决:
- 小白也能学会装“win10系统”,轻松撩妹
- STM32Cube工具学习笔记(一)Cube配置