Java容器一览

文章目录

      • 集合(Collection)
        • 1 线性表(List)
        • 2 规则集(Set)
        • 3 队列(Queue)
      • 映射(Map)
        • 1 HashMap
        • 2 TreeMap
        • 3 LinkedHashMap
        • 三者的使用场景

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

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

Java容器一览_第1张图片

集合(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;

Java容器一览_第2张图片
---------------------------------------LinkedList存储结构图----------------------------

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

通常情况下,我们总是优先使用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

2 规则集(Set)

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

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

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

  • HashSet是无序的,因为它实现了Set接口,并没有实现SortedSet接口。
    基于哈希表实现,支持快速查找,但不支持有序性操作。并且失去了元素的插入顺序信息,也就是说使用 Iterator 遍历 HashSet 得到的结果是不确定的。
  • TreeSet是有序的,因为它实现了SortedSet接口。
    基于红黑树实现,支持有序性操作,例如根据一个范围查找元素的操作。但是查找效率不如 HashSet,HashSet 查找的时间复杂度为 O(1),TreeSet 则为 O(logN)。
  • LInkedHashSet 用一个链表实现来扩展 HashSet 类,其中元素按照它们插入规则集的顺序获取。
    具有 HashSet 的查找效率,且内部使用双向链表维护元素的插入顺序

用一张图表示:
Java容器一览_第3张图片

我们来看下面这个例子

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是有序的!

Java容器一览_第4张图片

1 HashMap

  • 存储结构

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

transient Entry[] table;

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

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

Java容器一览_第5张图片
HashMap 类对于定位一个值、插入一个条目以及删除一个条目而言是高效的。

2 TreeMap

TreeMap 在内部会对 Key 进行排序,这种 Map 就是 SortedMap。注意到SortedMap 是接口,它的实现类是 TreeMap
Java容器一览_第6张图片

  • 使用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。

你可能感兴趣的:(java学习笔记)