Java容器一览

Java集合框架支持以下两种类型的容器:

  • 一种是为了存储一个元素集合,称为集合(collection)
  • 另一种是为了存储键 / 值对,称为映射(map)

在这里插入图片描述
注:所有 Collection 都实现了 Iterable 接口

Java 库中的具体集合

集合类型描述
ArrayList一种可以动态增长和缩减的索引序列
LinkedList一种可以在任何位置进行高效地插入和删除操作的有序序列
ArrayDeque一种用循环数组实现的双端队列
HashSet一种没有重复元素的无序集合
TreeSet一种有序集
EnumSet一种包含枚举类型值的集
LinkedHashSet一种可以记住元素插入次序的集
PriorityQueue一种允许高效删除最小元素的集合
HashMap一种存储键 / 值关联的数据结构
TreeMap一种键值有序排列的映射表
EnumMap一种键值属于枚举类型的映射表
LinkedHashMap一种可以记住键 / 值项添加次序的映射表
WeakHashMap一种其值无用武之地后可以被垃圾回收器回收的映射表
IdentityHashMap一种用 == 而不是用 equals 比较键值得映射表

集合(Collection)

1 线性表(List)
  • ArrayList

基于动态数组实现,支持随机访问。

因为 ArrayList 是基于数组实现的,所以支持快速随机访问。RandomAccess 接口标识着该类支持快速随机访问。

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable

数组的默认大小为 10

private static final int DEFAULT_CAPACITY = 10;
  • Vector:和 ArrayList 类似,但它是线程安全的。

  • LinkedList:基于双向链表实现,只能顺序访问,但是可以快速地在链表中间插入和删除元素。不仅如此,LinkedList 还可以用作栈、队列和双向队列。

每个链表存储了 first 和 last 指针:

transient Node<E> first;
transient Node<E> last;

Alt
---------------------------------------LinkedList存储结构图----------------------------

两种类型比较ArrayListLinkedList
获取指定元素速度很快需要从头开始查找元素
添加元素到末尾速度很快速度很快
在指定位置添加/删除需要移动元素不需要移动元素
内存占用较大

通常情况下,我们总是优先使用ArrayList

  • List接口允许我们添加重复的元素,即List内部的元素可以重复
 List<String> list = new ArrayList<>();
 list.add("aaa");
 list.add("bbb");
 list.add("aaa");
 for (String str:list         
     ) {
     System.out.println(str);
 }
aaa
bbb
aaa
  • List还允许添加null:
public class Test {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("apple");
        list.add(null);
        list.add("orange");
        System.out.println(list.get(1));
    }
}
null

如果在某个迭代器修改集合时,另一个迭代器对其进行遍历,一定会出现混乱的情况。

List<String> list = ...;
ListIterator<String> iter1 = list.listIterator();
ListIterator<String> iter2 = list.listIterator();
iter1.next();
iter2.remove();
iter2.next();//throws ConcurrentModificationException 并发修改异常
2 规则集(Set)

Set:不保证有序,不可重复
List:有顺序,可重复

  • 如果我们只需要存储不重复的key,并不需要存储映射的value,那么就可以使用Set
  • 因为放入Set的元素和Map的key类似,都要正确实现equals()和hashCode()方法,否则该元素无法正确地放入Set

Set接口并不保证有序,而SortedSet接口则保证元素是有序的

  • HashSet是无序的,因为它实现了Set接口,并没有实现SortedSet接口。
    基于哈希表实现(散列表用链表数组实现,标准类库使用的桶数是 2 的幂,默认值 16),支持快速查找,但不支持有序性操作。并且失去了元素的插入顺序信息,也就是说使用 Iterator 遍历 HashSet 得到的结果是不确定的。
    如果要对散列表再散列,就需要创建一个桶数更多的表。**装填因子(load factor)**决定何时对散列表进行再散列。例如,如果装填因子为 0.75(默认值)而表中超过 75% 的位置已经填入元素,这个表就会用双倍的桶数自动地进行再散列
  • TreeSet是有序的,因为它实现了SortedSet接口。
    基于红黑树实现,支持有序性操作,例如根据一个范围查找元素的操作。但是查找效率不如 HashSet,HashSet 查找的时间复杂度为 O(1),TreeSet 则为 O(logN)。
  • LInkedHashSet 用一个链表实现来扩展 HashSet 类,其中元素按照它们插入规则集的顺序获取。
    具有 HashSet 的查找效率,且内部使用双向链表维护元素的插入顺序

用一张图表示:
在这里插入图片描述

我们来看下面这个例子

package Collection.Set;

import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.TreeSet;

/**
 * Set:不保证有序,不可重复
 * List:有顺序,可重复
 * HashSet是无序的,因为它实现了Set接口,并没有实现SortedSet接口;
 * TreeSet是有序的,因为它实现了SortedSet接口。
 * TreeSet底层实际是用TreeMap实现的,内部维持了一个简化版的TreeMap,通过key来存储Set的元素。
 * TreeSet内部需要对存储的元素进行排序,因此,我们对应的类需要实现Comparable接口。
 * 这样,才能根据compareTo()方法比较对象之间的大小,才能进行内部排序。
 * 如果我们只需要存储不重复的key,并不需要存储映射的value,那么就可以使用Set
 */
public class TestTreeSet {
    public static void main(String[] args) {
        Set<String> set = new TreeSet<>();//TreeSet有顺序,这个顺序是元素的排序顺序
        System.out.println(set.add("cc"));//true
        System.out.println(set.add("aa"));//true
        System.out.println(set.add("cc"));//false,不可重复
        System.out.println(set);
        set.remove("cc");
        System.out.println(set);
        System.out.println("---------------");
        Set<String> set1 = new TreeSet<>();
        set1.add("cc");
        set1.addAll(set);
        System.out.println(set1);
        System.out.println("---------------");
/**HashSet无序,注意输出的顺序既不是添加的顺序,也不是String排序的顺序,在不同版本的JDK中,这个顺序也可能是不同的。*/
        Set<String> set2 = new HashSet<>();
        set2.add("apple");
        set2.add("orange");
        set2.add("banana");
        for (String s:set2
             ) {
            System.out.println(s);
        }
        System.out.println("---------------");
        Set<String> set3 = new LinkedHashSet<>(set2);
        set3.forEach(e -> System.out.println(e));



    }
}

true
true
false
[aa, cc]
[aa]
---------------
[aa, cc]
---------------
orange
banana
apple
---------------
orange
banana
apple

TreeSet底层实际是用TreeMap实现的,内部维持了一个简化版的TreeMap,通过key来存储Set的元素。

TreeSet内部需要对存储的元素进行排序,因此,我们对应的类需要实现Comparable接口或Comparator接口
这样,才能根据 compareTo(T o) 或 compare(T o1,T o2 )方法比较对象之间的大小,才能进行内部排序。

3 队列(Queue)
  • LinkedList:可以用它来实现双向队列。

  • PriorityQueue:基于堆结构实现,可以用它来实现优先队列。

 public class Test {
   public static void main(String[] args) {
       Queue<String> queue = new PriorityQueue<>();
       queue.offer("apple");
       queue.offer("banana");
       queue.offer("orange");
       queue.offer("blue");
       while (queue.size()>0) {
           System.out.println(queue.remove() + " ");
       }
   }
apple 
banana 
blue 
orange 

优先队列使用Comparable以元素的自然顺序进行排序。拥有最小数值的元素被赋予最高优先级,因此最先从队列中删除。如果几个元素具有相同的最高优先级,则任意选择一个。

映射(Map)

  • Map中不存在重复的key,因为放入相同的key,只会把原有的key-value对应的value给替换掉,虽然key不能重复,但value是可以重复的
Map<String, Integer> map = new HashMap<>();
map.put("apple", 123);
map.put("pear", 123); // ok

Map不保证顺序,遍历Map时,不可假设输出的key是有序的!

在这里插入图片描述
在这里插入图片描述

1 HashMap
  • 存储结构

内部包含了一个 Entry 类型的数组 table

transient Entry[] table;

Entry 存储着键值对。它包含了四个字段,从 next 字段我们可以看出 Entry 是一个链表。即数组中的每个位置被当成一个桶,一个桶存放一个链表。

HashMap 使用拉链法来解决冲突,同一个链表中存放哈希值和散列桶取模运算结果相同的 Entry

Alt
HashMap 类对于定位一个值、插入一个条目以及删除一个条目而言是高效的。

更新映射项

正常情况下,可以得到一个与一个键关联的原值,完成更新,再放回更新后的值。不过,必须考虑一个特殊情况,即键第一次出现。

使用一个映射统计一个单词在文件中出现的频度,看到一个单词(word)时,我们将计数器增 1, merge 方法可以简化这个操作。如果键原先不存在,下面的调用:

counts.merge(word,1,Integer :: sum);

将把 word 与 1 关联,否则使用 Integer :: sum 函数组合原值和 1*(将原值和 1 求和)

2 TreeMap

TreeMap 在内部会对 Key 进行排序,这种 Map 就是 SortedMap。注意到SortedMap 是接口,它的实现类是 TreeMap
在这里插入图片描述

  • 使用TreeMap时,放入的Key必须实现Comparable接口。
    String、Integer这些类已经实现了Comparable接口,因此可以直接作为Key使用。作为Value的对象则没有任何要求。

  • HashMap效率高于TreeMap;在需要排序的Map时才选用TreeMap。

  • 如果作为Key的class没有实现Comparable接口,那么,必须在创建TreeMap时同时指定一个自定义排序算法:

public class TestTreeMap {
    public static void main(String[] args) {
        Map<Integer, String> map = new TreeMap<>();
        map.put(20, "aa");
        map.put(10, "bb");
        map.put(5, "ccc");
        map.put(5, "cc");//不可重复,直接覆盖


       /**按照key递增的方式排序*/
        for (Integer key : map.keySet()
        ) {
            System.out.println(key + "-->" + map.get(key));
        }

        Map<Emp, String> map02 = new TreeMap<>();

        map02.put(new Emp(10, "小涛", 10000), "小涛好样的");
        map02.put(new Emp(20, "小小涛", 100000), "小小涛好样的");
        map02.put(new Emp(30, "小小小涛", 1000000), "小小小涛好样的");
        map02.put(new Emp(003, "子涛", 1000000), "子涛好样的");

        for (Emp key : map02.keySet()
        ) {
            System.out.println(key + "-->" + map02.get(key));
        }
    }
}

class Emp implements Comparable<Emp> {
    private int id;
    private String name;
    private double salary;

    public Emp(int id, String name, double salary) {
        this.id = id;
        this.name = name;
        this.salary = salary;
    }

    /**
     * 这里用到compareTO方法:负整数->小于,0->等于,正整数->大于
     */
    @Override
    public int compareTo(Emp emp) {
        if (this.salary < emp.salary) {
            return -1;
        } else if (this.salary > emp.salary) {
            return 1;
        } else {
            if (this.id < emp.id) {
                return -1;
            } else if (this.id > emp.id) {
                return 1;
            } else {
                return 0;
            }
        }

    }

    @Override
    public String toString() {
        return "id: " + id + ";name: " + name + ";salary:" + salary;
    }
}
5-->cc
10-->bb
20-->aa
id: 10;name: 小涛;salary:10000.0-->小涛好样的
id: 20;name: 小小涛;salary:100000.0-->小小涛好样的
id: 3;name: 子涛;salary:1000000.0-->子涛好样的
id: 30;name: 小小小涛;salary:1000000.0-->小小小涛好样的

注意到Emp类并未覆写equals()和hashCode(),因为TreeMap不使用equals()和hashCode()

3 LinkedHashMap
  • LinkedHashMap类用链表实现来扩展HashMap类,它支持映射中条目的排序。

  • HashMap类中的条目是无序的,但在LindedHashMap中,元素既可以按它们被最后一次访问时的顺序,从最早到最晚(称为访问顺序,而不是插入顺序)排序。

  • 无参构造方法是以插入顺序来创建LinkedHashMap的。
    要按访问顺序创建LinkedHashMap对象,可以使用构造方法LinkedHashMap(initialCapacity,loadFactor,true).

import java.util.*;

public class Test {
    public static void main(String[] args) {
        Map<String,Integer> hashMap = new HashMap<>();
        hashMap.put("Smith", 30);
        hashMap.put("Anderson", 31);
        hashMap.put("Lewis", 29);
        hashMap.put("Cook", 29);

        System.out.println(hashMap + "\n");//随机排序

        Map<String,Integer> treeMap = new TreeMap<>(hashMap);
        System.out.println(treeMap);//按键的升序排列
        //遍历Map写法一
        treeMap.forEach((name,age) -> System.out.println("name: " + name + "; age: " + age));
        System.out.println();
		//写法二
        Set<Map.Entry<String,Integer>> ss = treeMap.entrySet();
        for (Iterator ite = ss.iterator();ite.hasNext();){
            Map.Entry e = ((Map.Entry) ite.next());
            System.out.println("name\t" + e.getKey() + "-----" + "age\t" + e.getValue());
        }
        System.out.println();

        Map<String,Integer> linkedHashMap = new LinkedHashMap<>();
        linkedHashMap.put("Smith",30);
        linkedHashMap.put("Anderson",31);
        linkedHashMap.put("Lewis",29);
        linkedHashMap.put("Cook",29);
        System.out.println(linkedHashMap);//按插入顺序排列
    }
}
{Lewis=29, Smith=30, Cook=29, Anderson=31}

{Anderson=31, Cook=29, Lewis=29, Smith=30}
name: Anderson; age: 31
name: Cook; age: 29
name: Lewis; age: 29
name: Smith; age: 30

name	Anderson-----age	31
name	Cook-----age	29
name	Lewis-----age	29
name	Smith-----age	30

{Smith=30, Anderson=31, Lewis=29, Cook=29}

如输出所示,HashMap 中条目的顺序是随机的,而TreeMap 中的条目是按键的升序排列的,LinkedHashMap 中的条目则是按元素插入顺序排列的。

三者的使用场景

如果更新映射时不需要保持映射中元素的顺序,就使用 HashMap;如果需要保持映射中元素的插入顺序或访问顺序,就使用LinkedHashMap;如果需要使映射按照键排序,就使用 TreeMap。

补充:
1、WeakHashMap

WeakHashMap 的 Entry 继承自 WeakReference,被 WeakReference 关联的对象在下一次垃圾回收时会被回收。

WeakHashMap 主要用来实现缓存,通过使用 WeakHashMap 来引用缓存对象,由 JVM 对这部分缓存进行回收。

private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V>
2、枚举集与映射

EnumSet 是一个枚举类型元素及的高效实现。由于枚举类型只有有限个实例,所以 EnumSet 内部用位序列实现。如果对应的值在集中,则相应的位被置为1。
EnumSet 类没有公共的构造器。可以使用静态工厂方法构造这个集:

enum Weekday{Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday};
EnumSet<Weekday> always = EnumSet.allof(Weekday.class);//创建一个包含指定元素类型中所有元素的枚举集。 
EnumSet<Weekday> never = EnumSet.noneof(Weekday.class);//使用指定的元素类型创建一个空的枚举集。 
EnumSet<Weekday> workday = EnumSet.range(Weekday.Monday,Weekday.Tuesday);
EnumSet<Weekday> mwf = EnumSet.of(Weekday.Monday,Weekday.Wednesday,Weekday.Friday);

EnumMap 是一个键类型为枚举类型的映射。它可以直接且高效地用一个值数组实现。在使用时,需要在构造器中指定键类型:

EnumMap<Weekday,Employee> personInCharge = new EnumMap<>(Weekday.class);
3、同步视图

如果由多个线程访问集合,就必须确保集不会被意外地破坏。例如,如果一个线程试图将元素添加到散列表中,同时另一个线程正在对散列表进行再散列,其结果将是灾难性的。

类库的设计者使用视图机制来确保常规集合的线程安全,而不是实现线程安全的集合类。

Map<String,Employee> map = Collections.synchronizedMap(new HashMap<String,Employee>());

现在就可以由多线程访问 map 对象了。像 get 和 put 这类方法都是同步操作的。

集合框架中的遗留类

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值