摘要:类库中提供了一套相当完整的容器类来解决这个问题,其中基本类型有,,,,这些对象类型被称为集合类。但是,类库中使用了来指代集合类中的子集,,,所以集合类也被称为容器。五类型是能够将对象映射到其他对象的一种容器,有区别于的方法。
引言
如果一个程序只包含固定数量的且其生命周期都是已知对象,那么这是一个非常简单的程序——《think in java》
了解容器前,先提出一个问题,ArrayList和LinkedList谁的处理速度更快呢?
一 持有对象的方式在Java中,我们可以使用数组来保存一组对象。但是,数组是固定大小的,在一般情况下,我们写程序时并不知道将需要多少个对象,因此数组固定大小对于编程有些受限。
java类库中提供了一套相当完整的容器类来解决这个问题,其中基本类型有List,Queue,Set,Map,这些对象类型被称为集合类。但是,Java类库中使用了Collection来指代集合类中的子集{List,Queue,Set},所以集合类也被称为容器。容器提供了完善的方法来保存对象。
二 类型安全的容器java采用泛型保证我们不会向容器中插入不正确的类型,但是java的泛型只存在于程序源码中,在经过编译器编译就会将类型擦除。举一个例子:
//经过编译前 Listlist = new ArrayList<>(); list.add("ok"); System.out.println(list.get(0)); //经过编译后 List list = new ArrayList(); list.add("ok"); System.out.println((String)list.get(0));
这样做的好处是:在编写程序的时候,不会将其他非导出类型的对象添加到容器中。
三 List数组存储多个对象的原因是它提前声明了能存储多少对象。那容器又是如何实现存储不定多对象的呢?
//ArrayList部分源码 private static final int DEFAULT_CAPACITY = 10; private static final Object[] EMPTY_ELEMENTDATA = {}; private transient Object[] elementData; private int size; public ArrayList(int initialCapacity) { super(); if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); this.elementData = new Object[initialCapacity]; } public ArrayList() { super(); this.elementData = EMPTY_ELEMENTDATA; } public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; } private void ensureCapacityInternal(int minCapacity) { if (elementData == EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); } private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); }
我们可以看到,在ArrayList类中有一个elementData数组。当使用无参构造函数时数组长度默认为空,当向ArrayList加入对象时,会调用一个方法来判断数组是否能放下这个对象。当数组为空时设置数组长度为10并申请相应大小空间,当数组已满时,最少重新申请原数组大小1.5倍的空间(除非达到int类型最大值-8)。而在LinkedList中却没有采用这种方式,而是采用链表方式。
//LinkedList add方法 void linkLast(E e) { final Nodel = last; final Node newNode = new Node<>(l, e, null); last = newNode; if (l == null) first = newNode; else l.next = newNode; size++; modCount++; }
在LinkedList中,他的add方法调用了linkLast方法,直接在链表后边加入一个新的节点。
四 SetSet类型不保存重复的元素。判断对象元素是否相等采用的是equals方法,所以在存入自定义的对象时,如果重写equals方法依赖于可变属性,将会导致一些问题。
五 MapMap类型是能够将对象映射到其他对象的一种容器,有区别于List的get方法。HashSet类中包含了一个HashMap对象,HashSet的实现依靠HashMap。
HashMap的实现采用了数组链表的方式,即数组的每一个位置都存放的是链表头。查找会先通过key的hash找到对应数组下标,再在该数组下标所对应的链表中找到是否有对应对象,查找方式为equals方法。
HashMap如果存储的键值对大于设定值,会自动进行扩容并且对已经存入的键值对进行重排序,一次扩容的大小是当前数组长度的两倍。
//HashMap扩容 public V put(K key, V value) { //other... addEntry(hash, key, value, i); return null; } void addEntry(int hash, K key, V value, int bucketIndex) { if ((size >= threshold) && (null != table[bucketIndex])) { resize(2 * table.length); hash = (null != key) ? hash(key) : 0; bucketIndex = indexFor(hash, table.length); } //other... }六 Queue
队列是一种典型的先进先出的容器,LinkedList实现了Queue接口。PriorityQueue实现了优先级队列。ArrayDeque是一个用数组实现双端队列的类,我们来看一下ArrayDeque类中的一些方法。
//ArrayDeque构造方法 public ArrayDeque() { elements = (E[]) new Object[16]; } public ArrayDeque(int numElements) { allocateElements(numElements); } private void allocateElements(int numElements) { int initialCapacity = MIN_INITIAL_CAPACITY; if (numElements >= initialCapacity) { initialCapacity = numElements; initialCapacity |= (initialCapacity >>> 1); initialCapacity |= (initialCapacity >>> 2); initialCapacity |= (initialCapacity >>> 4); initialCapacity |= (initialCapacity >>> 8); initialCapacity |= (initialCapacity >>> 16); initialCapacity++; if (initialCapacity < 0) // Too many elements, must back off initialCapacity >>>= 1;// Good luck allocating 2 ^ 30 elements } elements = (E[]) new Object[initialCapacity]; }
上边的代码是ArrayDeque的构造方法,可以看到,当没有定义大小时,ArrayDeque默认数组大小为16,而定义大小后,会调用allocateElements方法,这个方法的作用是:当给定长度小于最小长度8时,使用最小长度。若大于等于最小长度,则找到比给定长度大的最小的2的幂数。为什么要是2的幂数呢?原因有以下两点:
操作系统分配内存的方法使用伙伴系统的话,每一块的大小都是2的幂数,如果分配的内存大小为2的幂数,可以减少内存分配的时间。
伙伴系统在百度百科中的解释:http://baike.baidu.com/view/4...
在ArrayDeque的addFirst方法中不固定将头放在数组的第一位,而是循环移位。使用2的幂数能够有效判断头部所在的地址。
同样在第二点中,如果队列满了,数组扩充是将容量capacity值左移一位即可扩充一倍。
public void addFirst(E e) { if (e == null) throw new NullPointerException(); elements[head = (head - 1) & (elements.length - 1)] = e; if (head == tail) doubleCapacity(); } private void doubleCapacity() { assert head == tail; int p = head; int n = elements.length; int r = n - p; // number of elements to the right of p int newCapacity = n << 1; if (newCapacity < 0) throw new IllegalStateException("Sorry, deque too big"); Object[] a = new Object[newCapacity]; System.arraycopy(elements, p, a, 0, r); System.arraycopy(elements, 0, a, r, p); elements = (E[])a; head = 0; tail = n; }七 List的选择
在文章开头提出了一个问题,数组实现的List快还是链表实现的List快。模拟一下试试:
public static void add() { long start = 0; long end = 0; Listalist = new ArrayList<>(); List llist = new LinkedList<>(); System.out.println("ArrayList添加1000万数据所需毫秒数"); start = System.currentTimeMillis(); for (int i=0; i<10000000; i++) { alist.add(i); } end = System.currentTimeMillis(); System.out.println(end-start); System.out.println("LinkedList添加1000万数据所需毫秒数"); start = System.currentTimeMillis(); for (int i=0; i<10000000; i++) { llist.add(i); } end = System.currentTimeMillis(); System.out.println(end-start+" "); System.out.println("ArrayList从1000万数据删除数据所需毫秒数"); start = System.currentTimeMillis(); alist.remove(0); alist.remove(2000000); alist.remove(4000000); alist.remove(6000000); alist.remove(8000000); alist.remove(9999994); end = System.currentTimeMillis(); System.out.println(end - start); System.out.println("LinkedList从1000万数据删除数据所需毫秒数"); start = System.currentTimeMillis(); llist.remove(0); llist.remove(2000000); llist.remove(4000000); llist.remove(6000000); llist.remove(8000000); llist.remove(9999994); end = System.currentTimeMillis(); System.out.println(end - start+" "); System.out.println("ArrayList从1000万数据查找数据所需毫秒数"); start = System.currentTimeMillis(); alist.contains(0); alist.contains(2000000); alist.contains(4000000); alist.contains(6000000); alist.contains(8000000); alist.contains(10000000); end = System.currentTimeMillis(); System.out.println(end - start); System.out.println("LinkedList从1000万数据查找数据所需毫秒数"); start = System.currentTimeMillis(); llist.contains(0); llist.contains(2000000); llist.contains(4000000); llist.contains(6000000); llist.contains(8000000); llist.contains(10000000); end = System.currentTimeMillis(); System.out.println(end - start+" "); }
可以看到,无论在何种情况下,数组实现的list都比链表快。当我在ArrayList构造方法中设置数组初始大小1000万时,ArrayLIst添加数据的速度慢了下来,降到5000毫秒左右,所以一般情况下不需要优化。
简单容器类图:
Java的容器分为两类,一类是Collection,一类是Map。collection中包含三种集合类型:Set,List,Queue。
如果想要set中的数据有序,请使用TreeSet。
HashTable和Vector是线程安全的,但是不建议使用,请使用java.util.concurrent包下的容器。
HashMap允许key/value值为null。
更多文章:http://blog.gavinzh.com
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/64423.html
摘要:允许从任一方向来遍历对象,并在遍历迭代过程中进行修改该对象,还能获得迭代器的当前位置。这个构造函数是将返回了一个对象给,这也是的存储实现原理。 一、容器产生的原因 1.数组的缺点:大小一旦给定就无法更改,除非复制到一个新的数组中,开销大;而容器类都可以自动地调整自己的尺寸。 2.容器功能的多样性:容器可以实现各种不同要求,如按不同依据将元素进行排序或者保证容器内无重复元素等等。关...
摘要:诞生之初,是单线程的。当接收到服务端的响应之后,便通过回调函数执行之后的操作。冲锋基于事件驱动。拥有拦截请求消息推送静默更新地理围栏等服务。控制时处于两种状态之一终止以节省内存监听获取和消息事件。支持的所有事件五销毁浏览器决定是否销毁。 这次体验一种新的博客风格,我们长话短说,针针见血。 showImg(https://segmentfault.com/img/remote/14600...
摘要:动态编程使用场景通过配置生成代码,减少重复编码,降低维护成本。动态生成字节码操作字节码的工具有,其中有两个比较流行的,一个是,一个是。 作者简介 传恒,一个喜欢摄影和旅游的软件工程师,先后从事饿了么物流蜂鸟自配送和蜂鸟众包的开发,现在转战 Java,目前负责物流策略组分流相关业务的开发。 什么是动态编程 动态编程是相对于静态编程而言的,平时我们讨论比较多的静态编程语言例如Java, 与动态...
摘要:如果一个程序只包含固定数量且其生命周期都是已知的对象,那么这是一个非常简单的程序。 如果一个程序只包含固定数量且其生命周期都是已知的对象,那么这是一个非常简单的程序。 1.泛型和类型安全的容器 通过使用泛型,可以在编译期防止将错误类型的对象放置到容器中. 2.基本概念 Java容器类库的用途是保存对象,并将其划分为两个不同的概念:Collection,Map. Collection...
阅读 2096·2023-04-26 00:09
阅读 3114·2021-09-26 10:12
阅读 3480·2019-08-30 15:44
阅读 2862·2019-08-30 13:47
阅读 921·2019-08-23 17:56
阅读 3225·2019-08-23 15:31
阅读 474·2019-08-23 13:47
阅读 2508·2019-08-23 11:56