Java集合

集合

基本概念

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

  • Collection:一个独立元素的序列,这些元素都服从一条或多条规则。List必须按照插入的顺序保存元素,而Set不能有重复元素。Queen按照排队规则来确定对象的产生顺序(通畅与他们插入的顺序相同)。
  • Map:一组成对的键值对对象,允许你使用键来查找值。ArrayList允许你使用数字来查找值,某种意义上它将数字与对象关联在了一起。映射表允许我们使用另一个对象来查找某个对象,它也被称为“关联数组”,或者被称为“字典”。
    你将在大部分情况下与这些接口打交道,并且唯一需要指定所使用的精确类型的地方就是在创建的时候。就像List apples = new ArrayList();
    这种方法并非总能奏效,因为某些类具有额外的功能,例如:Linkedlist具有在List接口中未包含的额外方法,如果你需要使用这些方法,就不能把它转为更通用的接口
    在java.util包中的Arrays和Collections有很多实用的方法,可以在Collection中添加一组元素。Arrays.asList()方法接受一个数组或是一个用逗号分割的元素列表,并将其转化为一个List对象。Collections.addAll()方法接受一个Collection对象,以及一个数组或是一个用逗号分割的列表,将元素添加到Collection中
public class CollectionsTest {
   public static void main(String[] args) {
       Collection<Integer> collection = new ArrayList<>(Arrays.asList(1, 2, 3, 5, 4));
       Integer[] moreInts = {6, 7, 9, 8, 10};
       Collections.addAll(collection, 11, 12, 14, 13, 15);
       Collections.addAll(collection, moreInts);
       System.out.println(collection);
       List<Integer> list = Arrays.asList(16, 17, 19, 18, 20);
       list.set(1, 99);
//        list.add(44);   //这句话会报 java.lang.UnsupportedOperationException错误
       System.out.println(list);
   }
}

Collection的构造器支持传入另一个Collection来进行初始化,因此你可以用Arrays.asList()来为这个构造器输入。但是,Collection.addAll()方法运行起来要快的多,而且构建一个不包含元素的Collection,然后再调用Collections.addAll()
Collection.addAll()成员方法只能接受另一个Collection对象作为参数,因此它不如能接受可变参数列表的Arrays.asList()或Collections.addAll()灵活
对于add方法报错那里,可以直接使用Arrays.asList()的输出作为List,但是在这种情况下,其底层表示的是数组,因此不能调整容量大小。如果使用add()或delete(),就会引发去改变数组尺寸的尝试,就会在运行时获得UnsupportedOperationException(不支持的操作错误)。
java容器类库中的两种主要类型,区别主要在于容器中的每个“槽”中保存的元素个数。Collection在每个容器的“槽”中只能保存一个元素。此类包括:List:以特定顺序存储;Set:元素不能重复;Queen:受限的线性表。而Map在每个槽内保存了两个对象,即键与之相关联的值
ArrayList和LinkedList都是List类型,它们都按照插入的顺序保存元素,两者的不同基本在于执行不同操作时的性能
HashSet,TreeSet和LinkedHashSet都是Set类型,它们每个相同的项只保存一次,但是不同的Set在存储元素的方面也有不同。HashSet是最快的查找元素的方式,因此,存储的顺序看起来并无实际意义。如果存储顺序很重要,那么TreeSet很适合,或者使用LinkedHashSet它按照被添加的顺序存储对象。
Map也分为HashMap,TreeMap和LinkedHashMap,它使得你可以用键来查找对象,就像一个简单的数据库。对于每一个键,Map只存储一次

迭代器(也是一种设计模式)

迭代器是一个对象,它的工作是遍历并选择序列中的对象,而客户端程序员不必关心该序列底层的结构,此外,迭代器通常被称为轻量级对象 :创建它的代价小。因此,经常可以见到对迭代器有些奇怪的限制:例如:java的Iterator只能单向移动,这个Iterator只能用来:

  • 使用方法Iterator()要求返回一个Iterator。Iterator将准备好返回序列的第一个元素
  • 使用next()获得下一个元素。
  • 使用hasNext()检查序列中是否还有元素。
  • 使用remove()将迭代器新近返回的元素删除。
   	List<Integer> list1 = new ArrayList<>();
       list1.addAll(Arrays.asList(moreInts));
       Iterator<Integer> iterator = list1.iterator();
       while (iterator.hasNext()) {
           Integer num = iterator.next();
           System.out.print(num + " ");
           iterator.remove();
       }
       System.out.println(list1);
public class IteratableClass implements Iterable<String> {
    public String[] words = ("And that is how " + "we know the Earth to be banana-shaped.").split(" ");
    @Override
    public Iterator<String> iterator() {
        return new Iterator<String>() {
            private int index = 0;
            @Override
            public boolean hasNext() {
                return index < words.length;
            }

            @Override
            public String next() {
                return words[index++];
            }
        };
    }

    public static void main(String[] args) {
        for (String s : new IteratableClass()) {
            System.out.print(s + " ");
        }
    }
}

iterator()返回的是实现了Iterator < String>的匿名内部类的实例。
ListIterator是一个更加强大的Iterator的子类型,它只能用于各种List类的访问。ListIterator可以双向移动。它还可以产生相对于迭代器在列表中 指向的当前位置的前一个和后一个元素的索引(如果你了解过数据结构中的链表,那么这种双向链表的特性你就会很清晰)。并且可以使用set()方法替换它访问过的最近的一个元素,你可以通过调用listIterator()方法产生一个指向List开始处的ListIterator,并且还可以通过调用listIterator(n)方法创建一个一开始就指向列表索引为n的元素处的ListIterator。

集合

LinkedList也像ArrayList实现了基本的list接口, 但是它执行某些操作更高效,同样的,它在随机访问操作方面要逊色。它还添加了可以使其用作栈,队列的方法。这些方法有些只是名字有些差异,或只存在些许差异,以使得这些名字在特定用法的上下文更适用。例如:getFirst()和element()完全一样,它们都返回列表的头,而并不移除它,如果list为空,则抛出异常,peek()与这两个方式只是有些许差异,它在列表为空时返回null。
Stack:栈通常是指“后进先出”的容器(操作受限的线性表)。LinkedList具有能够直接实现栈的所有功能的方法,因此可以直接将LinkedList作为栈使用。
Set:Set不保存重复的元素,Set中最常用的是测试归属性,你可以很容易地判断某个对象是否在 某个Set中。因此,查找成为了Set中最重要的操作,通常使用HashSet。Set具有和Collection完全一样的接口,不像前面两个List。实际上Set就是Collection,只是行为不同(这是继承与多态思想的典型应用:表现不同的行为)。Set是基于对象的值来确定归属性的

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

HashSet使用了散列,它维护的顺序与TreeSet和LinkedHashSet都不同,因为他们的实现具有不同的元素存储方式。TreeSet将元素存储在红黑树数据结构中,而HashSet使用的是散列函数,LinkedHashSet也使用了散列但是它使用了链表来维护元素的插入顺序。
Map:将对象映射到其他对象的能力是一种解决编程问题的杀手锏。例如:你要编写一个程序来检验Java的Random类的随机性。理想状态下,Random可以产生理想的数字分析,但要想测试它,则需要生成大量的随机数,并对落入各种不同范围的数字进行统计。Map可以很容易解决,它的键用来存储随机产生的数,而值是该数字出现的次数:

 		Random rand = new Random(47);
        Map<Integer, Integer> map = new HashMap<>();
        for (int i = 0; i < 10000; i++) {
            int r = rand.nextInt(20);
            Integer freq = map.get(r);
            map.put(r, freq == null ? 1 : freq + 1);
        }
        System.out.println(map);

在main方法中,自动包装机制将随机生成的int转换为HashMap可以使用的Integer引用。如果键不在容器内,get()将返回null,这表示该数字第一次被找到;否则get()将产生与该键相关联的Integer值,然后值递增(这个过程也包括对Integer的包装与拆包)。
Map与数组和其他的Collection一样,可以很容易的扩展到多维,而我们只需要将其设置为Map(这些Map的可以是其他容器,甚至是其他Map!),因此我们能很容易的将容器组合起来生成更强大的数据结构,例如:你正在跟踪拥有多个宠物的人,你所需的只是一个:

Map<Person,List<Pet>>;
public class MapDemo {
    public static Map<String, List<?>> map = new HashMap<>();
    static {
        map.put("Dawn", Arrays.asList("Molly", "Spot"));
        map.put("Shackleton", Arrays.asList("Elsie May", "Margrett"));
    }

    public static void main(String[] args) {
        System.out.println("String:" + map.keySet());
        System.out.println("?:" + map.values());
        for (String s : map.keySet()) {
            System.out.println(s + " has:");
            for (Object ss : map.get(s)) {
                System.out.println("  " + ss);
            }
        }
    }
}
String:[Dawn, Shackleton]
?:[[Molly, Spot], [Elsie May, Margrett]]
Dawn has:
  Molly
  Spot
Shackleton has:
  Elsie May
  Margrett

Queue:队列是一个典型的先进先出的容器(也是操作受限的线性表)。队列常被当做一种可靠的将对象从程序的某个区域传输到另一个区域的途径。队列在并发编程中非常重要,它可以安全的将对象从一个任务传输给另一个任务。
LinkedList提供了方法以支持队列的行为,并且它实现了Queue接口,因此LinkedList可以用作Queue的一种实现。通过将LinkedList向上转型为Queue,实现Queue相关方法:

public class QueueDemo {
    public static void printQ(Queue queue) {
        //检索但是不移除头部元素,如果没有则返回null
        while (queue.peek() != null) {
            //在队列中删除头部并返回这个头部
            System.out.print(queue.remove() + " ");
        }
        System.out.println();
    }

    public static void main(String[] args) {
        Queue<Integer> queue = new LinkedList<>();
        Random random = new Random(47);
        for (int i = 0; i < 10; i++) {
            //在队列中添加元素
            queue.offer(random.nextInt(i + 10));
        }
        printQ(queue);
        Queue<Character> qc = new LinkedList<>();
        for (char c : "aiucaaui".toCharArray()) {
            qc.offer(c);
        }
        printQ(qc);
    }
}

offer()方法是与Queue相关的方法之一,它在允许的的情况下,将一个元素插入到队尾,或者返回false。peek()和element()都将在不移除的情况下返回队头,但是peek()在队列为空时返回null,而element()会抛出NoSuchElementException异常。poll()和remove()将移除并返回队头,但是poll()在队列为空时返回null,而remove会抛出NoSuchElementException异常。
PriorityQueue:优先级队列声明下一个弹出元素是最需要的元素(具有最高的优先级)。如果构建了一个消息系统,某些消息比其他消息更重要,因而应该更快地得到处理,那么它们何时得到处理就与它们何时到达无关。当你在PriorityQueue上调用offer()插入一个对象时,这个对象会在队列中被排序。默认地排序将使用对象在队列中的自然顺序,但是你可以通过提供自己的Comparator来修改这个顺序。PriorityQueue可以确保你调用peek(),poll(),remove()时,获取的元素时队列中优先级最高的元素

PriorityQueue<Integer> p = new PriorityQueue<>(Arrays.aslist(22,25,23));

总结

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

  1. 数组将数字与对象联系起来。它保存类型明确的对象,查询对象时,不需要对结果做类型转换。它可以是多维的,可以保存基本数据的类型,但是数组一旦生成,其容量就不能改变。
  2. Collection保存单一的元素,而Map保存相关联的键值对。有了java的泛型,你就可以指定容器中存放的数据类型。各种Collection和各种Map都可以在你向其中添加更多元素时自动调整容量。容器不能持有(holding)基本类型,但是自动包装机制会自动进行基本类型与包装类型的转换。
  3. 像数组一样,List也建立数字与对象的联系,因此,数组与list都是排好序的容器。区别在于List可以自动扩容。
  4. 如果要进行大量的随机访问,就使用ArrayList;如果要经常从表中插入或删除元素,应用LinkedList。
  5. 各种Queue以及栈的行为,由LinkedList提供支持。
  6. Map是一种将对象(而非数字)与对象相关联的设计。HashMap设计用来快速访问,而TreeMap保持“键”始终处于排序状态,所以没有HashMap快。LinkedHashMap保持元素插入的顺序,但是也通过散列提供了快速访问能力。
  7. Set不接受重复元素,HashSet提供快速的查询速度,而TreeSet保持元素处于排序状态,LinkedHashSet以插入顺序保存元素。
  8. 新程序中不应使用过时的Vector,Hashtable和Stack。
    其实只有四种容器
    其实只有四种集合:Map ,List,Set和Queue
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值