集合框架简介
集合可以看作是一种容器,用来存储对象信息。 集合类都位于java.util 包下,支持多线程安全的集合类都位于java.util.concurrent 包下
Collection集合框架图 (图示标注错了,是B继承或实现A) Map集合框架图 Java集合类主要由两个根接口Collection和Map派生出来的,Collection派生出 了三个子接口: List、 Set、Queue,因此Java集合大致也可分成List、Set、 Queue、Map四种接口体系。
List代表了有序可重复集合,可直接根据元素的索引来访问;Set代表无序不可重复集合,只能根据元素本身来访问;Queue是队列集合;Map代表的是存储key-value对的集合,可根据元素的key来访问value。
集合和数组的区别?
- 数组声明了它容纳的元素的类型,而集合不声明。
- 数组是静态的,一个数组实例具有固定的大小,一旦创建了就无法改变容量了。而集合是可以动态扩展容量,可以根据需要动态改变大小,集合提供更多的成员方法,能满足更多的需求。
- 数组的存放的类型只能是一种,集合存放的类型可以是任意对象类型(不加泛型时添加的类型是Object),要注意,集合中只能存放对象类型,若添加基本数据类型,会自动转成对应的包装类对象。
List接口
List,列表,有序集合,是Collection接口的子接口。可以通过下标访问集合中的元素。List允许重复元素,同时允许有多个null值。 List的实现类有LinkedList、ArrayList、Vector、Stack。
List常用API:
方法 | 说明 |
---|
boolean add(E e) | 将指定的元素追加到此列表的末尾 | void add(int index, E element) | 将指定的元素插入此列表中的指定位置,原有元素依次后移 | E remove(int index) | 删除该列表中指定位置的元素,返回值为被删除的元素 | boolean remove(Object o) | 从列表中删除指定元素的第一个出现(如果存在) | E set(int index, E element) | 用指定的元素(可选操作)替换此列表中指定位置的元素。 | E get(int index) | 返回此列表中指定位置的元素。 |
对于集合的API,个人推荐看文档,它足够详细
ArrayList
ArrayList是List接口的常用实现类,底层采用数组实现,其容量能自动增长,并且支持索引访问,因此ArrayList特点是随机访问速度快,插入和移除性能较差(与LinkedList比较)。
ArrayList支持存放null元素,存放的元素是有序的,可重复的。
ArrayList线程不安全。
ArrayList容量是自动扩容的,其初始容量为10。 ArrayList添加一个元素,会自动扩容,过程如下: 因为数组是不可变的,所以ArrayList的底层数组在若想在原有基础上扩容,需要先创建一个新的数组,并且这个新的数组要比原数组大一个长度,然后将所有元素放到这个新数组中,最后将这个数组赋给原数组即可。代码实现如下:
Object[] array = {1,2,3,4,5,6,7,8,9,10};
Object[] tempArray = new Object[array.length+1];
tempArray[array.length] = 11;
array = tempArray;
ArrayList的使用
ArrayList arrayList = new ArrayList();
arrayList.add("Hello");
arrayList.add("World");
arrayList.add("Forward");
arrayList.add(666);
System.out.println("集合的长度为" + arrayList.size());
- 删除元素 remove(Object obj),remove(int index)
arrayList.remove(new Integer(666));
arrayList.remove(2);
arrayList.clear();
- 判断是否存在某元素 contains(Object obj)
boolean b = arrayList.contains("Forward");
System.out.println("集合是否包含“Forward”:" + b);
System.out.println("集合是否为空:" + arrayList.isEmpty());
- 取交集 retainAll(Collection)
arrayList.add("Hello");
arrayList.add("World");
ArrayList arrayList1 = new ArrayList();
arrayList1.add("Hello");
arrayList1.add("test");
boolean b1 = arrayList.retainAll(arrayList1);
- 删交集 removeAll(Collection)
arrayList.add("World");
arrayList.removeAll(arrayList1);
ArrayList执行遍历快,增删慢
import java.util.ArrayList;
public class ArrayListDemo2 {
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();
long start = System.currentTimeMillis();
for (int i = 0; i < 500000; i++) {
arrayList.add(i);
}
long end = System.currentTimeMillis();
long time = end - start;
System.out.println("添加500000个数据用时:" + time);
for (int i = 0; i < 500000; i++) {
arrayList.get(i);
}
long end1 = System.currentTimeMillis();
long time1 = end1 - end;
System.out.println("访问500000个数据用时:" + time1);
}
}
ArrayList是线程不安全的
public class ArrayLIstDemo1 {
public static void main(final String[] args) {
final ArrayList arrayList = new ArrayList();
for (int i = 0; i < 30; i++) {
new Thread(new Runnable() {
@Override
public void run() {
arrayList.add(UUID.randomUUID().toString().substring(1,8));
System.out.println(arrayList);
}
}).start();
}
}
}
LinkedList
LinkedList底层的数据结构是基于双向链表的。 LinkedList也是有序的,可重复,非线程安全的。 LinkedList的常用方法要比ArrayLis多一些
LinkedList linkedList = new LinkedList();
linkedList.add("Hello");
linkedList.add("World");
linkedList.add(666);
System.out.println("集合的长度为" + linkedList.size());
linkedList.remove(new Integer(666));
linkedList.remove(2);
System.out.println("集合的长度为" + linkedList.size());
for (int i = 0; i < linkedList.size(); i++) {
System.out.println(linkedList.get(i));
}
linkedList.clear();
boolean b = linkedList.contains("凤文");
System.out.println("集合是否包含“凤文”:" + b);
System.out.println("集合是否为空:" + linkedList.isEmpty());
linkedList.add("Hello");
linkedList.add("World");
ArrayList arrayList1 = new ArrayList();
arrayList1.add("Hello");
arrayList1.add("test");
boolean b1 = linkedList.retainAll(arrayList1);
System.out.println("arrayList中是否包含arrList1中的元素:" + b1);
for (int i = 0; i < linkedList.size(); i++) {
System.out.println(linkedList.get(i));
}
linkedList.add("World");
linkedList.removeAll(arrayList1);
除了这些,LinkedList还有操作链表的方法:
LinkedList<Object> list = new LinkedList<>();
list.add("World");
list.addFirst("Hello");
list.addLast("!");
list.add(1,",");
for (Object e : list) {
System.out.print(e);
}
上面是新增元素的方法,这些是访问和删除元素的方法
System.out.println(list.getFirst());
System.out.println(list.getLast());
System.out.println(list.removeFirst());
System.out.println(list.removeLast());
LinkedList执行遍历慢,增加和删除元素快,正好与ArrayList相对。
import java.util.LinkedList;
public class LinkedListDemo2 {
public static void main(String[] args) {
LinkedList linkedList = new LinkedList();
long start = System.currentTimeMillis();
for (int i = 0; i < 500000; i++) {
linkedList.add(i);
}
long end = System.currentTimeMillis();
long time = end - start;
System.out.println("添加500000个数据用时:" + time);
for (int i = 0; i < 500000; i++) {
linkedList.get(i);
}
long end1 = System.currentTimeMillis();
long time1 = end1 - end;
System.out.println("访问500000个数据用时:" + time1);
}
}
比较 | 随机访问 | 插入删除 | 空间复杂度 |
---|
ArrayList | 通过索引快速定位,快 | 需要创建新数组并重新装入元素,慢 | 只存储数据,内存占用少 | LinkedList | 逐个查找,慢 | 只需要添加一项Entry对象加上指向,快 | 除了存储数据还有节点,内存占用多 |
LinkedList除了链表实现的List结构,还有栈和队列的数据结构。
栈特性
public class LinkedListDemoForStack {
public static void main(String[] args) {
LinkedList<Object> stack = new LinkedList<>();
stack.push(10);
stack.push(20);
stack.push(30);
stack.push(40);
while (!stack.isEmpty()) {
System.out.println(stack.pop());
}
}
}
队列特性
import java.util.LinkedList;
import java.util.Queue;
public class LinkedListDemoForQueue {
public static void main(String[] args) {
Queue<Object> queue = new LinkedList<>();
queue.offer(10);
queue.offer(20);
queue.offer(30);
System.out.println(queue.peek());
while (!queue.isEmpty()) {
System.out.println(queue.poll());
}
}
}
Vector
Vector类称作向量类,它实现了动态数组,用于元素变化的对象数组。 Vector也是以0开始的下标表示元素的位置。 当Vector对象创建后,数组的个数会随着Vector集合中元素个数的增加和缩小而自动变化。 Vector类中的大多数方法是同步的,使用synchronized修饰的,Vector是线程安全的。
在使用者的角度,Vector与ArrayList的主要区别就是Vector是线程安全的,但效率会低很多。因此,他们所选择的应用场景应为,单线程下用ArrayList,多线程下用Vector。
Stack
Stack是栈,它的特性是先进后出。
Java工具包中的Stack是继承于Vector的, 意味着Vector拥有的属性和功能,Stack都拥有。
Stack底层实际上也是通过数组去实现的,具体操作如下:
- 执行push时(将元素推入栈中),是通过将元素追加的数组的末尾中。
- 执行peek时(取出栈顶元素,不执行删除),是返回数组末尾的元素。
- 执行pop时(取出栈顶元素,并将该元素从栈中删除),是取出数组末尾的元素,然后将该元素从数组中删除。
Stack的使用
public class StackDemo {
public static void main(String[] args) {
Stack stack = new Stack();
stack.push(1);
stack.push(2);
stack.push(3);
stack.pop();
System.out.println("查看栈顶元素" + stack.peek());
System.out.println("Stack是否为空:" + stack.isEmpty());
System.out.println("Stack栈顶元素:" + stack.peek());
System.out.println("Stack中所有元素:" + stack);
}
}
Set接口
Set接口继承自Collection接口,但是并没有对方法进行扩充。 Set接口中元素是有序的,不重复的,且最多有一个null。 Set主要有两个实现类,分别是HashSet和TreeSet
Set接口存储元素的过程
HashSet
HashSet是Set接口的一个实现类,它不保证Set的迭代顺序,无序存放。 Set接口的唯一性:底层依赖于hashCode()和equals()方法。
public static void main(String[] args) {
HashSet<String> set = new HashSet<>();
set.add("张三");
set.add("李四");
set.add("王五");
set.add("张三");
System.out.println(set.size());
for (String name : set) {
System.out.println(name);
}
}
String类已经重写过hashCode()和equals方法了。如果要去重属性相同的对象,那需要手动重写这两个方法。当然,这两个方法只需要重写一个就可以,因为Set比较真正依赖的是equals(),而默认情况下(Object),equals()依赖的是hashCode()方法,所以重写一个也可。
public class HashSetDemo2 {
public static void main(String[] args) {
HashSet<Student> set = new HashSet<Student>();
set.add(new Student("张三", "男", 18));
set.add(new Student("张三", "男", 14));
set.add(new Student("张三", "男", 18));
set.add(new Student("李四", "男", 18));
for (Student stu : set) {
System.out.println(stu.toString());
}
}
}
class Student {
private String name;
private String gender;
private int age;
public Student() {
}
public Student(String name, String gender, int age) {
this.name = name;
this.gender = gender;
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o instanceof Student) {
Student stu = (Student) o;
return this.name.equals(stu.getName()) && this.gender.equals(stu.getGender()) && this.age == stu.getAge();
}
return false;
}
@Override
public int hashCode() {
return this.name.hashCode()+this.gender.hashCode()+age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", gender='" + gender + '\'' +
", age=" + age +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
TreeSet
TreeSet是Set接口的一个实现类,主要作用是对象的排序(应对Set的无序性)及确定存入对象的唯一性。
public static void main(String[] args) {
TreeSet<String> tree = new TreeSet<>();
tree.add("张D");
tree.add("张B");
tree.add("张C");
tree.add("张A");
tree.add("张A");
tree.add("李A");
for (String name : tree) {
System.out.println(name);
}
}
很明显,TreeSet会将元素自动排序。
TreeSet中的元素支持2钟排序方式:
- 第一种:自然排序,在元素定义排序规则。元素自身具有比较性,实现Comparable接口,重写compareTo方法
- 第二种:比较器排序,创建TreeSet时提供Comparator进行排序。实现Comparator接口,实现compare方法。
public static void main(String[] args) {
TreeSet<StudentA> set = new TreeSet<>();
set.add(new StudentA("张三", "男", 20));
set.add(new StudentA("张三", "男", 14));
set.add(new StudentA("张三", "男", 16));
set.add(new StudentA("李四", "男", 18));
set.add(new StudentA("王五", "男", 18));
set.add(new StudentA("刘六", "男", 18));
set.add(new StudentA("赵七", "女", 18));
for (StudentA stu : set) {
System.out.println(stu.toString());
}
}
class StudentA implements Comparable {
private String name;
private String gender;
private int age;
public StudentA() {
}
public StudentA(String name, String gender, int age) {
this.name = name;
this.gender = gender;
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o instanceof StudentA) {
StudentA stu = (StudentA) o;
return this.name.equals(stu.getName()) && this.gender.equals(stu.getGender()) && this.age == stu.getAge();
}
return false;
}
@Override
public int hashCode() {
return this.name.hashCode() + this.gender.hashCode() + age;
}
@Override
public String toString() {
return "StudentA{" +
"name='" + name + '\'' +
", gender='" + gender + '\'' +
", age=" + age +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public int compareTo(Object o) {
if (!(o instanceof StudentA)) {
throw new RuntimeException("比较的对象类型不对!");
}
StudentA stu = (StudentA) o;
if (this.age > stu.age) {
return 1;
}
if (this.age < stu.age) {
return -1;
}
if (this.age == stu.age) {
return this.name.compareTo(stu.name);
}
return 0;
}
}
public static void main(String[] args) { {
TreeSet<StudentB> set = new TreeSet<>(new Comparator<StudentB>() {
@Override
public int compare(StudentB o1, StudentB o2) {
if (o1.getAge() == o2.getAge()) {
return o1.getName().compareTo(o2.getName());
}
return o1.getAge() - o2.getAge();
}
});
set.add(new StudentB("张三", "男", 20));
set.add(new StudentB("张三", "男", 14));
set.add(new StudentB("张三", "男", 16));
set.add(new StudentB("李四", "男", 18));
set.add(new StudentB("王五", "男", 18));
set.add(new StudentB("刘六", "男", 18));
set.add(new StudentB("赵七", "女", 18));
for (StudentB stu : set) {
System.out.println(stu.toString());
}
}
class StudentB {
private String name;
private String gender;
private int age;
public StudentB() {
}
public StudentB(String name, String gender, int age) {
this.name = name;
this.gender = gender;
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o instanceof StudentB) {
StudentB stu = (StudentB) o;
return this.name.equals(stu.getName()) && this.gender.equals(stu.getGender()) && this.age == stu.getAge();
}
return false;
}
@Override
public int hashCode() {
return this.name.hashCode() + this.gender.hashCode() + age;
}
@Override
public String toString() {
return "StudentB{" +
"name='" + name + '\'' +
", gender='" + gender + '\'' +
", age=" + age +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
代码中TreeSet的构造器中有个Comparator参数,我这里用的匿名内部类,你也可以用lambda表示。
Set与List的异同
集合 | List | Set |
---|
存储元素 | 有序、可重复 | 无序、不可重复 | 常用类 | ArrayList、LinkedList | HashSet、TreeSet | 访问元素 | 提供索引,效率高 | 无序只能一个个比较,效率低 |
Collection的遍历
增强for循环
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("Hello");
list.add("java");
list.add("world");
list.add("China");
for (String s : list) {
System.out.println(s);
}
int[] arr = {1,2,4,5,6};
for (int i : arr) {
System.out.println(i);
}
}
迭代输出
对于顺序存储方式的ArrayList的迭代输出
public void iteratorArray() {
ArrayList list = new ArrayList();
for (int i = 0; i < 500000; i++) {
list.add(i);
}
long start1 = System.currentTimeMillis();
for (int i = 0; i < list.size(); i++) {
list.get(i);
}
long end1 = System.currentTimeMillis();
System.out.println("for循环遍历用时:" + (end1 - start1));
long start2 = System.currentTimeMillis();
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
iterator.next();
}
long end2 = System.currentTimeMillis();
System.out.println("使用Iterator迭代器遍历用时:" + (end2 - start2));
}
根据结果,对于ArrayList,还是使用传统的遍历方式比较效率。
虽然Iterator迭代器对ArrayList帮助不大,但是对于LinkedList的遍历就有飞跃了。
public void iteratorLinked() {
LinkedList list = new LinkedList();
for (int i = 0; i < 50000; i++) {
list.add(i);
}
long start1 = System.currentTimeMillis();
for (int i = 0; i < list.size(); i++) {
list.get(i);
}
long end1 = System.currentTimeMillis();
System.out.println("for循环遍历用时:" + (end1 - start1));
long start2 = System.currentTimeMillis();
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
iterator.next();
}
long end2 = System.currentTimeMillis();
System.out.println("使用Iterator迭代器遍历用时:" + (end2 - start2));
}
这次的差距明显展现出Iterator的优势了,它非常适合LinkedList的遍历。
遍历Set集合
public static void main(String[] args) {
HashSet<Integer> set = new HashSet<>();
for (int i = 0; i < 1000000; i++) {
set.add(i);
}
long start1 = System.currentTimeMillis();
for (Integer i : set) {
int n = i;
}
long end1 = System.currentTimeMillis();
System.out.println("增强for循环遍历用时:" + (end1 - start1));
Iterator<Integer> iterator = set.iterator();
while (iterator.hasNext()) {
int n = iterator.next();
}
long end2 = System.currentTimeMillis();
System.out.println("使用Iterator迭代器遍历用时:" + (end2 - end1));
}
我测试了很多次,Set增强for循环和迭代器的效率都差不多,增强for循环简单点,所以更推荐它。
结论:Iterator迭代器适合用于LinkedList集合
双向迭代输出
双向迭代输出 ListIterator
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("Hello");
list.add("World");
list.add("Java");
ListIterator<String> listIterator = list.listIterator();
while (listIterator.hasNext()) {
System.out.println(listIterator.next());
}
System.out.println("-----------------------------------------");
while (listIterator.hasPrevious()) {
System.out.println(listIterator.previous());
}
}
双向迭代输出用于List,给List集合添加了逆序遍历效果。 这个功能给我的想法是双指针算法,它可以在很多场景应用。
枚举输出
使用Enumeration接口实例化对象,只能够依靠Vector子类,因为Enumeration的设计就是为Vector服务的。使用hasMoreElements() 方法来判断是否有下一个元素,通过nextElement() 方法取得元素。
public static void main(String[] args) {
Vector<String> list = new Vector<>();
list.add("A");
list.add("B");
list.add("C");
list.add("D");
Enumeration<String> em = list.elements();
while (em.hasMoreElements()) {
System.out.println(em.nextElement());
}
}
Map接口
- Map集合中存储的元素是一组键值对象,是key与value的一个映射。
- Map提供了一个更通用的元素存储方法,每个键映射有一个值。
- Map集合中一个key对应一个value,key是唯一的,但是value可重复。
- key和value之间存在单向一对一关系,即通过指定的key总能找到确定的value,但反过来不行,就类似于数学函数y=f(x)。
- Map接口的常用实现类有HashMap、Hashtable、LinkedHashMap、TreeMap、ConcurrentHashMap。
Map与Collection的区别:
- Map集合存储元素是成对出现的,也就是以键值对为单个元素,且键是不可以重复的。
- Collection集合存储元素是一个个存储的,也就是单一存储的。
Map接口常用方法介绍
方法 | 说明 |
---|
V get(Object key) | 根据键获取值 | V put(K key, V value) | 添加元素。如果是相同的键再次添加,则后添加的Value覆盖之前的 | void putAll(Map<? extends K,? extends V> m) | 将指定m的所有映射复制到此映射(可选操作)。 | void clear() | 移除所有的键值对 | V remove(Object key) | 根据key删除该键值对,并将value返回 | boolean containsKey(Object key) | 判断Map是否包含指定的键 | boolean containsValue(Object value) | 判断Map是否包含指定的值 | boolean isEmpty() | 判断集合是否为空 | Set<Map.Entry<K,V>> entrySet() | 返回集合的键值对对象 | Set keySet() | 返回集合中所有键的集合 | Collection values() | 获取值的集合 | int size() | 返回键值对的数量 |
HashMap
HashMap根据键的hashCode值存储数据,大多数情况下可以直接定位到它的值,因而具有很快的访问速度,但遍历顺序确是不确定的。
HashMap底层是由数组+链表组成,jdk1.8版本改为数组+链表+红黑树。
HashMap最多只允许一条记录的键为null,允许多条记录的值为null。
HashMap线程不安全,即同一时刻多个线程同时操作HashMap,可以会导致数据的不一致。 如果需要满足线程安全,可以用Collections的synchronizedMap方法是HashMap具有线程安全的能力,或者使用ConcurrentHashMap以及HashTable,后面有具体介绍。
HashMap的基本使用
HashMap map = new HashMap();
HashMap(int initialCapacity, float loadFactor) 构造方法,构造一个空的 HashMap具有指定的初始容量和负载因子。 默认初始容量(16)和默认负载系数(0.75)。 HashMap(Map<? extends K,? extends V> m) ,构造方法,构造一个新的 HashMap与指定的相同的映射 Map 。
map.put("key1", "value1");
map.put("name", "张三");
map.put(1, 666);
map.put(2, 667);
System.out.println(map.get("name"));
System.out.println(map.get(1));
map.remove(1);
map.clear();
boolean b1 = map.containsKey("name");
System.out.println("是否包含键name:" + b1);
boolean b2 = map.containsValue("张三");
System.out.println("是否包含值张三:" + b2);
boolean b3 = map.isEmpty();
System.out.println("集合是否为空:" + b3);
Set keys = map.keySet();
for (Object key : keys) {
System.out.println(key + ":" + map.get(key));
}
Collection values = map.values();
Iterator i = values.iterator();
while (i.hasNext()) {
System.out.println(i.next());
}
LinkedHashMap
LinkedHashMap是链表和哈希表组合的一个数据结构; LinkedHashMap存储数据是有序的; LinkedHashMap是线程不安全的。
LinkedHashMap<String, String> map = new LinkedHashMap<>();
map.put("key1", "111");
map.put("key2", "222");
map.put("key3", "333");
map.put("key4", "444");
for (String key : map.keySet()) {
System.out.println(key + ":" + map.get(key));
}
ConcurrentHashMap
ConcurrentHashMap底层采用数组+链表+红黑树的存储结构。 ConcurrentHashMap的key和value都不能为null。 ConcurrentHashMap是线程安全的,支持高并发操作。 ConcurrentHashMap采用分段锁技术有效提升并发访问效率。
public class ConcurrentHashMapDemo1 {
public static void main(String[] args) {
ConcurrentHashMapDemo1 demo1 = new ConcurrentHashMapDemo1();
for (int i = 0; i < 10; i++) {
demo1.testConcurrentHashMap();
}
}
public void testConcurrentHashMap() {
ConcurrentHashMap<Integer, Integer> map = new ConcurrentHashMap<>();
CountDownLatch cd = new CountDownLatch(100);
for (int i = 0; i < 100; i++) {
new MyThread2(map, cd).start();
}
try {
cd.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("多线程操作后HashMap的大小:" + map.size());
}
}
class MyThread2 extends Thread {
ConcurrentHashMap<Integer, Integer> map;
CountDownLatch cd;
public MyThread2(ConcurrentHashMap<Integer, Integer> map, CountDownLatch cd) {
this.map = map;
this.cd = cd;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
this.map.put(i, i);
}
cd.countDown();
}
}
Hashtable
Hashtable是线程同步安全的。 Hashtable的键和值不能为null。
Map的遍历
HashMap<String, String> map = new HashMap<String, String>();
map.put("key1", "Hello");
map.put("key2", "World");
map.put("key3", "java");
通过keySet的方式遍历
for (String key : map.keySet()) {
System.out.println(key + ":" + map.get(key));
}
通过entry的方式遍历集合
Set<Map.Entry<String, String>> set = map.entrySet();
for (Map.Entry<String, String> entry : set) {
System.out.println(entry.getKey() + ":" + entry.getValue());
}
通过迭代器Iterator的方式遍历集合
Iterator<Map.Entry<String, String>> i = map.entrySet().iterator();
while (i.hasNext()) {
Map.Entry<String, String> entry = i.next();
System.out.println(entry.getKey() + ":" + entry.getValue());
}
直接遍历values集合
for (String value : map.values()) {
System.out.println(value);
}
通过迭代器遍历values集合
Iterator<String> iterator = map.values().iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
集合队列
队列Queue接口
队列是一种先进先出的数据结构,只能从队头删除元素,从队尾插入元素。
import java.util.LinkedList;
import java.util.Queue;
public class QueueDemo1 {
public static void main(String[] args) {
Queue<String> queue = new LinkedList<>();
queue.offer("A1");
queue.offer("A2");
queue.offer("A3");
queue.offer("A4");
queue.offer("A5");
queue.offer("A6");
String context = queue.poll();
System.out.println("被提取的元素:" + context);
for (String s : queue) {
System.out.println("剩余元素:" + s);
}
System.out.println(queue.size());
}
}
Queue接口的常用方法
方法 | 说明 |
---|
boolean offer(E e) | 如果在不违反容量限制的情况下立即执行,则将指定的元素插入到此队列中,若没有添加成功返回false | E poll() | 检索并删除此队列的头,如果此队列为空,则返回 null 。 | E peek() | 检索但不删除此队列的头,如果此队列为空,则返回 null 。 |
Java中的Queue的实现主要有三种方式:
- 阻塞队列:阻塞队列是一个可以阻塞的先进先出集合,比如某个线程在空队列获取元素时、或已存满队列存储元素时,都会被阻塞。
- 非阻塞队列:非阻塞队列是使用CAS(compare and set)机制实现,并发性能好。
- 双端队列(Deque):Deque是一个既可以在头部操作元素,又可以在尾部操作元素,俗称双向队列。
阻塞队列
ArrayBlockingQueue基于数组实现的一个阻塞队列,在创建ArrayBlockQueue对象时必须制定容量大小。 ArrayBlockingQueue可以指定公平性与非公平性,默认情况下为非公平的,即不保证等待时间最长的队列最优先能访问队列。
ArrayBlockingQueue常用方法:
- put方法用来向队尾存入元素,如果队列满,则等待。
- take方法用来从队首取元素,如果队列为空,则等待。
- offer方法用来向队尾存入元素,如果队列满,则等待一定的时间,当时间期限达到时,如果还没有插入成功,则返回false;否则返回true。
- poll方法用来从队首取元素,如果队列空,则等待一定的时间,当时间期限达到时,如果取不到,则返回null;否则返回取得的元素。
ArrayBlockingQueue阻塞队列实现的生产者-消费者模式
import java.util.concurrent.ArrayBlockingQueue;
public class Test {
private int queueSize = 10;
private ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue(queueSize);
public static void main(String[] args) {
Test test = new Test();
Provider provider = new Provider(test.queue, test.queueSize);
Consumer consumer = new Consumer(test.queue);
provider.start();
consumer.start();
}
}
import java.util.concurrent.ArrayBlockingQueue;
public class Provider extends Thread {
ArrayBlockingQueue<Integer> queue;
int queueSize;
public Provider(ArrayBlockingQueue<Integer> queue, int queueSize) {
this.queue = queue;
this.queueSize = queueSize;
}
@Override
public void run() {
provide();
}
private void provide() {
while (true) {
try {
synchronized (this) {
queue.put(1);
System.out.println("向队列插入了一个元素,队列剩余空间:" + (queueSize - queue.size()));
}
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
import java.util.concurrent.ArrayBlockingQueue;
public class Consumer extends Thread {
ArrayBlockingQueue<Integer> queue;
public Consumer(ArrayBlockingQueue<Integer> queue) {
this.queue = queue;
}
@Override
public void run() {
consume();
}
private void consume() {
while (true) {
try {
Thread.sleep(3000);
queue.take();
System.out.println("从队列中取出一个元素,队列剩余:"+queue.size());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
生产速率是大于消费速率的,所以队列的空间逐渐变小 当没有空间的时候,生产者被阻塞,需要等待,此时生产消费速率一致。
优先级队列
PriorityQueue优先级队列既可以根据元素的自然顺序来排序,也可以根据Comparator来设置排序规则。 PriorityQueue优先级队列对于自定义的类来说,需要自定义比较器。 PriorityQueue优先队列的大小是不受限制的,但在创建时可以指定初始大小。当我们向优先队列增加元素的时候,队列大小会自动增加。 PriorityQueue优先队列不允许null。 PriorityQueue是非线程安全的,所以提供了PriorityBlockingQueue在多线程环境使用。
import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.Random;
public class PriorityQueueTest {
public static void main(String[] args) {
PriorityQueue<People> queue = new PriorityQueue<>(11, new Comparator<People>() {
@Override
public int compare(People o1, People o2) {
return o1.getAge() - o2.getAge();
}
});
for (int i = 0; i < 10; i++) {
queue.add(new People("张_"+i,new Random().nextInt(100)));
}
while(!queue.isEmpty()){
System.out.println(queue.poll().toString());
}
}
}
public class People {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "People{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public People(String name, int age) {
this.name = name;
this.age = age;
}
}
延时队列
DelayQueue是一个无界的BlockingQueue,用于放置实现了Delayed接口的对象,其中的对象只能在其到期时才能从队列中取走。这种队列是有序的,即对头对象的延迟到期时间最长。 不能将null元素放置到DelayQueue队列中。 DelayQueue只能添加(offer/put/add)实现了Delayed接口的对象。
public class DelayQueueTest {
private static DelayQueue queue = new DelayQueue();
public static void main(String[] args) throws InterruptedException {
new Thread(new Runnable() {
@Override
public void run() {
queue.offer(new MyDelayedTask("task1", 0000));
queue.offer(new MyDelayedTask("task2", 1800));
queue.offer(new MyDelayedTask("task3", 4900));
queue.offer(new MyDelayedTask("task4", 3800));
queue.offer(new MyDelayedTask("task5", 3300));
queue.offer(new MyDelayedTask("task6", 5500));
queue.offer(new MyDelayedTask("task7", 9900));
queue.offer(new MyDelayedTask("task8", 1500));
}
}).start();
while (true) {
MyDelayedTask task = (MyDelayedTask) queue.take();
System.out.println("任务名称:" + task.getName() + "时间是:" + task.getTime());
}
}
}
public class MyDelayedTask implements Delayed {
private long start = System.currentTimeMillis();
private String name;
private long time;
public MyDelayedTask(String name, long time) {
this.name = name;
this.time = time;
}
@Override
public long getDelay(TimeUnit unit) {
long convert = unit.convert((start + time) - System.currentTimeMillis(),
TimeUnit.MILLISECONDS);
return convert;
}
@Override
public int compareTo(Delayed o) {
return (int) (this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS));
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public long getTime() {
return time;
}
public void setTime(long time) {
this.time = time;
}
}
|