集合基础(一)

java容器简图

image

对象引用保存

  1. 数组
  • 编译器支持类型
  • 保存一组对象最有效的方式
  • 保存基本类型数据时推荐使用
  • 大小固定,不知道需要多少对象时受限
  1. 容器

    用于解决在任意时刻,任意位置,创建任意数量的对象的问题。

容器基本概念

java容器类库的用途是“保存对象”,并将其分为两个不同的概念

  1. Collection: 一个独立元素序列,这些元素都服从一条或多条规则
    • List:必须按照插入顺序保存元素
    • Set:不能有重复元素
    • Queue:按照队列规则来确定对象产生的顺序(通常与他们的插入顺序相同)
  2. Map:一组成对的“键值对”对象,允许你使用键来查找值

Collection



public interface Collection<E> extends Iterable<E> {
    // Query Operations
  
    int size();
  
    boolean isEmpty();
  
    boolean contains(Object o);

    Iterator<E> iterator();

    Object[] toArray();

    <T> T[] toArray(T[] a);

    // Modification Operations

    boolean add(E e);

    boolean remove(Object o);

    // Bulk Operations

    boolean containsAll(Collection<?> c);

    boolean addAll(Collection<? extends E> c);

    boolean removeAll(Collection<?> c);

    boolean retainAll(Collection<?> c);

    void clear();

    // Comparison and hashing

    boolean equals(Object o);

    int hashCode();
}

使用方法

创建容器

  1. 声明接口,创建实现,修改时仅修改创建位置即可
List<Apple> apples = new ArrayList<>();
  1. 声明和创建具体实现(需要具体实现中额外的功能)
ArrayList<Apple> apples = new ArrayList<>();

添加元素

单个元素

import java.util.ArrayList;
import java.util.Collection;

public class SimpleCollection {

    public static void main(String[] args) {
        Collection<Integer> c = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            // autoboxing
            c.add(i);
        }
        for (Integer i : c) {
            System.out.print(i + ", ");
        }
    }

}

Set: 元素不存在时才添加

List:将对象放入容器,不关注是否重复

一组元素

import java.util.*;

public class AddGroups {

    public static void main(String[] args) {
        Collection<Integer> collection = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
        Integer[] moreInts = {6, 7, 8, 9, 10};
        collection.addAll(Arrays.asList(moreInts));
        // runs significantly faster, buy you can`t
        // construct a Collect this way
        Collections.addAll(collection, 11, 12, 13, 14, 15);
        Collections.addAll(collection, moreInts);
        // Produces a list "backed by " an array:
        List<Integer> list = Arrays.asList(16, 17, 18, 19, 20);
        // Ok -- modify an element
        list.set(1, 99);
        // Runtime error because the underlying array cannot be resized
        list.add(21);
    }


}
  • 注意collection.addAll()与Collections.addAll()的区别
  • 注意可选参数
  • 注意不可变数组

容器打印

import java.util.*;


public class PrintContainers {

    /**
     * fill collection
     *
     * @param collection collection wait to fill
     * @return
     */
    static Collection<String> fill(Collection<String> collection) {
        collection.add("rat");
        collection.add("cat");
        collection.add("dog");
        collection.add("dog");
        return collection;
    }

    /**
     * fill map
     *
     * @param map map wait to fill
     * @return
     */
    static Map<String, String> fill(Map<String, String> map) {
        map.put("rat", "Fuzzy");
        map.put("cat", "Rags");
        map.put("dog", "Bosco");
        map.put("dog", "Spot");
        return map;

    }

    public static void main(String[] args) {
        System.out.println(fill(new ArrayList<String>()));
        System.out.println(fill(new LinkedList<String>()));
        System.out.println(fill(new HashSet<String>()));
        System.out.println(fill(new TreeSet<String>()));
        System.out.println(fill(new LinkedHashSet<String>()));
        System.out.println(fill(new HashMap<String, String>()));
        System.out.println(fill(new TreeMap<String, String>()));
        System.out.println(fill(new LinkedHashMap<String, String>()));
    }
}

  • 默认调用容器的toString()方法即可生成可读性很好的结果

List

承诺将元素维护在特定的序列中;

添加方法以实现在list的中间插入、移除元素。

public interface List<E> extends Collection<E> {
    // Query Operations
  
    int size();

    boolean isEmpty();

    boolean contains(Object o);

    Iterator<E> iterator();

    Object[] toArray();

    <T> T[] toArray(T[] a);

    // Modification Operations

    boolean add(E e);

    boolean remove(Object o);

    // Bulk Modification Operations

    boolean containsAll(Collection<?> c);

    boolean addAll(Collection<? extends E> c);

    boolean addAll(int index, Collection<? extends E> c);

    boolean removeAll(Collection<?> c);

    boolean retainAll(Collection<?> c);

    void clear();

    // Comparison and hashing

    boolean equals(Object o);

    int hashCode();

    // Positional Access Operations

    E get(int index);

    E set(int index, E element);

    void add(int index, E element);

    E remove(int index);

    // Search Operations

    int indexOf(Object o);

    int lastIndexOf(Object o);

    // List Iterators

    ListIterator<E> listIterator();

    ListIterator<E> listIterator(int index);

    // View

    List<E> subList(int fromIndex, int toIndex);
}


ArrayList

基于数组,下标随机访问元素快,插入、移除元素慢(数据的连续性)

LinkedList

  • List中间插入和移除是高效
  • 下标随机访问较低
  • 支持栈、队列、双端队列的方法

迭代器

面对具体的容器编写的代码,并不能很好的用于另外一种容器,我们需要更高层次的抽象,迭代器能能够帮助我们达成目的。

迭代器是一个对象,他的工作是遍历并选择序列中的对象,而客户端程序员不必关心序列底层的结构。统一了对容器的访问方式。

public interface Iterable<T> {

    /**
     * Returns an iterator over a set of elements of type T.
     *
     * @return an Iterator.
     */
    Iterator<T> iterator();
}

public interface Iterator<E> {
    
    boolean hasNext();

    E next();

    void remove();
}

ListIterator

ListIterator是一个更加强大的Iterator子类型,它只能用于各种List的访问,

public interface ListIterator<E> extends Iterator<E> {
   
    boolean hasNext();

    E next();

    boolean hasPrevious();

    E previous();

    int nextIndex();

    int previousIndex();

    void remove();

    void set(E e);

    void add(E e);
}

Stack

后进先出容器(LIFO)

public interface Stack<E>{

    E push(E item);

    E pop();

    E peek();

    boolean empty();

}

LinkedList已经实现栈相关功能,我们可以使用组合的方式,完成自定义Stack,屏蔽LinkedList中的其他方法;

public class Stack<E> {
  
  private LinkedList storate = new LinkedList<E>();
  
  public E push(E e) {
    return storate.addFirst(v);
  }
  
  pulic E pop() {
    return storate.removeFirst();
  }
  
  public E peek() {
    return return storate.getFirst();
  }
  
  public boolean empty() {
    return storage.empty();
  }
  
}

Set

不保存重复的元素

public interface Set<E> extends Collection<E> {
    // Query Operations
    int size();

    boolean isEmpty();

    boolean contains(Object o);

    Iterator<E> iterator();

    Object[] toArray();

    <T> T[] toArray(T[] a);

    // Modification Operations
    boolean add(E e);

    boolean remove(Object o);

    // Bulk Operations
    boolean containsAll(Collection<?> c);

    boolean addAll(Collection<? extends E> c);

    boolean retainAll(Collection<?> c);

    boolean removeAll(Collection<?> c);

    void clear();


    // Comparison and hashing

    boolean equals(Object o);

    int hashCode();
}

  • Set具有和Collection完全一样的接口,因此没有任何额外的功能
  • Set就是Collection,只是行为不同(继承与多态的典型应用)
  • Set基于对象的值来确定归属性

HashSet

import java.util.HashSet;
import java.util.Random;
import java.util.Set;

public class SetOfInteger {

    public static void main(String[] args) {
        Random random = new Random(47);
        Set<Integer> integerSet = new HashSet<>();
        for (int i = 0; i < 10000; i++) {
            integerSet.add(random.nextInt(30));
        }
        System.out.println(integerSet);
    }

}

  • 注意打印结果,无重复
  • 输出顺序无规律

使用散列函数,HashMap

TreeSet

将元素存储在红黑树中,TreeMap

LinkedHashSet

同样适用了散列函数,HashMap,但使用了链表来维护元素的插入顺序

使用场景

优先使用HashSet,需要对结果排序可以考虑使用TreeSet

Map

将对象映射到其他对象的能力是解决编程问题的杀手锏。

public interface Map<K,V> {

    // Query Operations
    int size();

    boolean isEmpty();

    boolean containsKey(Object key);

    boolean containsValue(Object value);

    V get(Object key);

    // Modification Operations

    V put(K key, V value);

    V remove(Object key);

    // Bulk Operations

    void putAll(Map<? extends K, ? extends V> m);

    void clear();

    // Views

    Set<K> keySet();

    Collection<V> values();

    Set<Map.Entry<K, V>> entrySet();

    interface Entry<K,V> {

        K getKey();

        V getValue();

        V setValue(V value);

        boolean equals(Object o);

        int hashCode();
    }

    // Comparison and hashing

    boolean equals(Object o);

    int hashCode();

}

用于统计

import java.util.HashMap;
import java.util.Map;
import java.util.Random;

public class Statistics {

    public static void main(String[] args) {
        Random rand = new Random(47);
        Map<Integer, Integer> m = new HashMap<Integer, Integer>();
        for (int i = 0; i < 10000; i++) {
            // Produce a number between 0 and 20
            int r = rand.nextInt(20);
            Integer freq = m.get(r);
            freq = freq == null ? 1 : freq + 1;
            m.put(r, freq);
        }
        System.out.println(m);
    }

}

Queue

典型的先进先出容器,从容器的一端放入,从另一端取出,并且放入容器的顺序与取出顺序一致。

public interface Queue<E> extends Collection<E> {
     /**
     * Inserts the specified element into this queue if it is possible to do so
     * immediately without violating capacity restrictions, returning
     * {@code true} upon success and throwing an {@code IllegalStateException}
     * if no space is currently available.
     *
     * @param e the element to add
     * @return {@code true} (as specified by {@link Collection#add})
     * @throws IllegalStateException if the element cannot be added at this
     *         time due to capacity restrictions
     * @throws ClassCastException if the class of the specified element
     *         prevents it from being added to this queue
     * @throws NullPointerException if the specified element is null and
     *         this queue does not permit null elements
     * @throws IllegalArgumentException if some property of this element
     *         prevents it from being added to this queue
     */
    boolean add(E e);
    /**
     * Inserts the specified element into this queue if it is possible to do
     * so immediately without violating capacity restrictions.
     * When using a capacity-restricted queue, this method is generally
     * preferable to {@link #add}, which can fail to insert an element only
     * by throwing an exception.
     *
     * @param e the element to add
     * @return {@code true} if the element was added to this queue, else
     *         {@code false}
     * @throws ClassCastException if the class of the specified element
     *         prevents it from being added to this queue
     * @throws NullPointerException if the specified element is null and
     *         this queue does not permit null elements
     * @throws IllegalArgumentException if some property of this element
     *         prevents it from being added to this queue
     */
    boolean offer(E e);
    /**
     * Retrieves and removes the head of this queue.  This method differs
     * from {@link #poll poll} only in that it throws an exception if this
     * queue is empty.
     *
     * @return the head of this queue
     * @throws NoSuchElementException if this queue is empty
     */
    E remove();
    /**
     * Retrieves and removes the head of this queue,
     * or returns {@code null} if this queue is empty.
     *
     * @return the head of this queue, or {@code null} if this queue is empty
     */
    E poll();
      /**
     * Retrieves, but does not remove, the head of this queue.  This method
     * differs from {@link #peek peek} only in that it throws an exception
     * if this queue is empty.
     *
     * @return the head of this queue
     * @throws NoSuchElementException if this queue is empty
     */
    E element();
       /**
     * Retrieves, but does not remove, the head of this queue,
     * or returns {@code null} if this queue is empty.
     *
     * @return the head of this queue, or {@code null} if this queue is empty
     */
    E peek();
}

  • 注意add()与offer()

  • 注意remove()与poll()

  • 注意element()与peek()

  • LinkedList提供了与Queue相关的方法,对于queue继承的Collection,在不需要其他任何方法的情况下,就可以拥有一个可用的Queue

PriorityQueue

先进先出描述了最典型的队列规则,队列规则是指在给定一组队列中元素的情况下,确定下一个弹出队列元素的规则。

先进先出声明的是下一个元素应该是等待时间最长的元素;

优先级队列声明下一个弹出元素是最高优先级的元素;

默认顺序将使用对象的自然顺序,但是你可以使用自己的Comparator来修改这个顺序;

PriorityQueue可以确保你调用peak()、poll()、和remove()方法时,获取到的元素是队列中优先级最高的元素

使用案例

import java.util.*;

public class PriorityQueueDemo {

    public static <E> void printQ(Queue<E> queue) {
        while (queue.peek() != null) {
            System.out.print(queue.remove() + " ");
        }
        System.out.println();

    }

    public static void main(String[] args) {
        PriorityQueue<Integer> priorityQueue = new PriorityQueue<>();
        Random random = new Random(47);
        for (int i = 0; i < 10; i++) {
            priorityQueue.offer(random.nextInt(i + 10));
        }
        printQ(priorityQueue);

        List<Integer> ints = Arrays.asList(25, 22, 20, 10, 14, 9, 3, 1, 1, 2, 3, 9, 14, 18, 21, 23, 25);
        priorityQueue = new PriorityQueue<>(ints);
        printQ(priorityQueue);

        priorityQueue = new PriorityQueue<>(ints.size(), Collections.reverseOrder());
        priorityQueue.addAll(ints);
        printQ(priorityQueue);

        String fact = "EDUCATION SHOULD ESCHEW OBFUSCATION";
        List<String> strings = Arrays.asList(fact.split(""));
        PriorityQueue<String> stringPriorityQueue = new PriorityQueue<>(strings);
        printQ(stringPriorityQueue);

        stringPriorityQueue = new PriorityQueue<>(strings.size(), Collections.reverseOrder());
        stringPriorityQueue.addAll(strings);
        printQ(stringPriorityQueue);

    }

}

Collection和Iterator

Collection是描述所有序列容器共性的根接口

java.util.AbstractCollection类提供了Collection的默认实现,其中没有不必要的代码重复

实现Collection接口就意味着需要提供iterator()方法

iterable比iterator()方法要方便,可以使用foreach结构,从而使代码更加清晰

实现遍历的方式

  • 实现Collection接口 -> 强制去实现遍历用不到的collection相关方法
  • 继承AbstractCollection抽象类 -> 已经继承其他类时不能再继承
  • 生成Iterator -> 将队列与消费队列的方法连接在一起的耦合度最小的方式与实现collection相比,在序列类上添加的约束也少得多

Foreach与迭代器

foreach语法主要用于集合与数组

Collection与foreach

编译时利用Iterable的iterator()方法产生一个iterator,用于集合的迭代遍历

public interface Iterable<T> {
    /**
     * Returns an iterator over elements of type {@code T}.
     *
     * @return an Iterator.
     */
    Iterator<T> iterator();
}
public interface Collection<E> extends Iterable<E> {
    
    Iterator<E> iterator();

}

测试代码

import java.util.Arrays;
import java.util.Collection;

public class CollectionForEachTest {

    public static void main(String[] args) {
        Collection<Integer> collections = Arrays.asList(1, 2, 3, 4);
        for (Integer i : collections) {
            System.out.println(i);
        }
    }

}

编译后测试代码

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;

public class CollectionForEachTest {
    public CollectionForEachTest() {
    }

    public static void main(String[] args) {
        Collection<Integer> collections = Arrays.asList(1, 2, 3, 4);
        Iterator var2 = collections.iterator();

        while(var2.hasNext()) {
            Integer i = (Integer)var2.next();
            System.out.println(i);
        }

    }
}

数组与foreach

编译时利用简单for循环和下标自增的方法实现数组遍历

测试代码

public class ArrayForEachTest {

    public static void main(String[] args) {
        int[] arr = {1, 2, 3, 4, 5};
        for (Integer i : arr) {
            System.out.println(i);
        }
    }

}

编译后测试代码

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

public class ArrayForEachTest {
    public ArrayForEachTest() {
    }

    public static void main(String[] args) {
        int[] arr = new int[]{1, 2, 3, 4, 5};
        int[] var2 = arr;
        int var3 = arr.length;

        for(int var4 = 0; var4 < var3; ++var4) {
            Integer i = var2[var4];
            System.out.println(i);
        }

    }
}

Iterable接口适配

适配foreach所需的Iterable接口,从而实现多种foreach遍历顺序

import java.util.*;

public class ReversibleArrayList<T> extends ArrayList<T> {

    public ReversibleArrayList(Collection<T> c) {
        super(c);
    }

    public Iterable<T> reversed() {

        return new Iterable<T>() {
            @Override
            public Iterator<T> iterator() {
                return new Iterator<T>() {
                    int current = size() - 1;

                    @Override
                    public boolean hasNext() {
                        return current > -1;
                    }

                    @Override
                    public T next() {
                        return get(current--);
                    }
                };
            }
        };

    }

    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6);
        ReversibleArrayList<Integer> reversibleArrayList = new ReversibleArrayList<>(list);
        for (Integer i : reversibleArrayList) {
            System.out.print(i + " ");
        }
        System.out.println();
        for (Integer i : reversibleArrayList.reversed()) {
            System.out.print(i + " ");
        }
        System.out.println();
    }

}

总结

Java提供了大量持有对象的方式:

  1. 数组将数字与对象联系起来。它保存类型明确的对象,查询对象时,不需要对结果做类型转换。它可以是多维的,可以保存基本类型型的数据。但是,数组一旦生成,其容量就不能改变。
  2. Collection保存单一的元素,而Map保存相关联的键值对。有了java的泛型,你就可以知道容器存放的对象类型,因此你就不会将错误类型的对象放置到容器中,并且在容器中获取元素时,不必进行类型转换。各种类型的Collection和各种Map都可以在你向其中田间更多的元素时,自动调整其尺寸。容器不能持有基本类型,但是自动包装机制会仔细地执行基本类型到容器中所持有的包装器类型之间双向转换。
  3. 像数组一样,List也建立数字索引与对象的关联,因此,数组和List都是排好序的容器,List能够自动扩容。
  4. 如果要进行大量的随机访问,就使用ArrayList;如果要经常从表中间插入或删除元素,则应该使用LinkedList.
  5. 各种Queue和Stack的行为,由LinkedList支持。
  6. Map是一种将对象(而非数字)与对象相关联的设计。Hashmap设计用来快速访问;而TreeMap保持"键"始终处于排序状态,所以没有HashMap快。LinkedHash保持元素的插入顺序,但也通过散列提供了快速访问的能力。
  7. Set不接受重复元素。HashSet提供最快的查询速度,而TreeSet保持元素处于排序状态。LinedHashSet以插入顺序保存元素。
  8. 新程序不应该使用过时的Vector、Hashtable、Stack.
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容

  • 集合 集合类是一种工具类,存储数量不等的对象,可以实现栈,队列等数据结构。可以分为:Set:无序,不可重复的集合;...
    长远勿见阅读 628评论 0 0
  • 集合类的由来: JAVA是面向对象的,对象用来封装特有数据,对象多了就需要储存起来,当对象的个数不确定的时候,...
    哦00阅读 541评论 0 0
  • 第十一章 持有对象 Java实用类库还提供了一套相当完整的容器类来解决这个问题,其中基本的类型是List、Set、...
    Lisy_阅读 795评论 0 1
  • 四、集合框架 1:String类:字符串(重点) (1)多个字符组成的一个序列,叫字符串。生活中很多数据的描述都采...
    佘大将军阅读 734评论 0 2
  • 第 11 章 持有对象 如果一个程序只包含固定数量的且生命周期都是已知的对象,那么这是一个非常简单的程序。 通常,...
    智勇双全的小六阅读 366评论 0 0