[Java代码] JAVA 持有对象——容器初探(持续补充)

引言

如果一个程序只包含固定数量的且其生命周期都是已知对象,那么这是一个非常简单的程序——《think in java》

了解容器前,先提出一个问题,ArrayList和LinkedList谁的处理速度更快呢?

一 持有对象的方式

在Java中,我们可以使用数组来保存一组对象。但是,数组是固定大小的,在一般情况下,我们写程序时并不知道将需要多少个对象,因此数组固定大小对于编程有些受限。

java类库中提供了一套相当完整的容器类来解决这个问题,其中基本类型有List,Queue,Set,Map,这些对象类型被称为集合类。但是,Java类库中使用了Collection来指代集合类中的子集{List,Queue,Set},所以集合类也被称为容器。容器提供了完善的方法来保存对象。

二 类型安全的容器

java采用泛型保证我们不会向容器中插入不正确的类型,但是java的泛型只存在于程序源码中,在经过编译器编译就会将类型擦除。举一个例子:

  1. java//经过编译前
  2. List<String> list = new ArrayList<>();
  3. list.add("ok");
  4. System.out.println(list.get(0));
  5. //经过编译后
  6. List list = new ArrayList();
  7. list.add("ok");
  8. System.out.println((String)list.get(0));
  9. http://www.nvzi91.cn/fujianyan/29933.html
复制代码

这样做的好处是:在编写程序的时候,不会将其他非导出类型的对象添加到容器中。

三 List

数组存储多个对象的原因是它提前声明了能存储多少对象。那容器又是如何实现存储不定多对象的呢?

  1. java//ArrayList部分源码
  2. private static final int DEFAULT_CAPACITY = 10;
  3. private static final Object[] EMPTY_ELEMENTDATA = {};
  4. private transient Object[] elementData;
  5. private int size;
  6. http://www.nvzi91.cn/zigongnamoyan/29934.html
  7. public ArrayList(int initialCapacity) {
  8. super();
  9. if (initialCapacity < 0)
  10. throw new IllegalArgumentException("Illegal Capacity: "+
  11. initialCapacity);
  12. this.elementData = new Object[initialCapacity];
  13. }
  14. public ArrayList() {
  15. super();
  16. this.elementData = EMPTY_ELEMENTDATA;
  17. }
  18. public boolean add(E e) {
  19. ensureCapacityInternal(size + 1); // Increments modCount!!
  20. elementData[size++] = e;
  21. return true;
  22. }
  23. http://www.nvzi91.cn/chunvmoxiufu/29935.html
  24. private void ensureCapacityInternal(int minCapacity) {
  25. if (elementData == EMPTY_ELEMENTDATA) {
  26. minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
  27. }
  28. ensureExplicitCapacity(minCapacity);
  29. }
  30. private void ensureExplicitCapacity(int minCapacity) {
  31. modCount++;
  32. // overflow-conscious code
  33. if (minCapacity - elementData.length > 0)
  34. grow(minCapacity);
  35. }
  36. http://www.nvzi91.cn/penqiangyan/29936.html
  37. private void grow(int minCapacity) {
  38. // overflow-conscious code
  39. int oldCapacity = elementData.length;
  40. int newCapacity = oldCapacity + (oldCapacity >> 1);
  41. if (newCapacity - minCapacity < 0)
  42. newCapacity = minCapacity;
  43. if (newCapacity - MAX_ARRAY_SIZE > 0)
  44. newCapacity = hugeCapacity(minCapacity);
  45. // minCapacity is usually close to size, so this is a win:
  46. elementData = Arrays.copyOf(elementData, newCapacity);
  47. }
复制代码

我们可以看到,在ArrayList类中有一个elementData数组。当使用无参构造函数时数组长度默认为空,当向ArrayList加入对象时,会调用一个方法来判断数组是否能放下这个对象。当数组为空时设置数组长度为10并申请相应大小空间,当数组已满时,最少重新申请原数组大小1.5倍的空间(除非达到int类型最大值-8)。而在LinkedList中却没有采用这种方式,而是采用链表方式。

  1. java//LinkedList add方法
  2. void linkLast(E e) {
  3. final Node<E> l = last;
  4. final Node<E> newNode = new Node<>(l, e, null);
  5. last = newNode;
  6. if (l == null)
  7. first = newNode;
  8. else
  9. l.next = newNode;
  10. size++;
  11. modCount++;
  12. }
  13. http://www.nvzi91.cn/yindaoyan/29937.html
复制代码

在LinkedList中,他的add方法调用了linkLast方法,直接在链表后边加入一个新的节点。

四 Set

Set类型不保存重复的元素。判断对象元素是否相等采用的是equals方法,所以在存入自定义的对象时,如果重写equals方法依赖于可变属性,将会导致一些问题。

五 Map

Map类型是能够将对象映射到其他对象的一种容器,有区别于List的get方法。HashSet类中包含了一个HashMap对象,HashSet的实现依靠HashMap。

HashMap的实现采用了数组链表的方式,即数组的每一个位置都存放的是链表头。查找会先通过key的hash找到对应数组下标,再在该数组下标所对应的链表中找到是否有对应对象,查找方式为equals方法。
HashMap如果存储的键值对大于设定值,会自动进行扩容并且对已经存入的键值对进行重排序,一次扩容的大小是当前数组长度的两倍。

  1. java//HashMap扩容
  2. public V put(K key, V value) {
  3. //other...
  4. addEntry(hash, key, value, i);
  5. return null;
  6. }http://www.kmrlyy.com/lcnz/33448.html
  7. void addEntry(int hash, K key, V value, int bucketIndex) {
  8. if ((size >= threshold) && (null != table[bucketIndex])) {
  9. resize(2 * table.length);
  10. hash = (null != key) ? hash(key) : 0;
  11. bucketIndex = indexFor(hash, table.length);
  12. }
  13. //other...
  14. }
复制代码
六 Queue

队列是一种典型的先进先出的容器,LinkedList实现了Queue接口。PriorityQueue实现了优先级队列。ArrayDeque是一个用数组实现双端队列的类,我们来看一下ArrayDeque类中的一些方法。

  1. java//ArrayDeque构造方法
  2. public ArrayDeque() {
  3. elements = (E[]) new Object[16];
  4. }
  5. public ArrayDeque(int numElements) {
  6. allocateElements(numElements);
  7. }http://www.kmrlyy.com/penqiangyan/33450.html
  8. private void allocateElements(int numElements) {
  9. int initialCapacity = MIN_INITIAL_CAPACITY;
  10. if (numElements >= initialCapacity) {
  11. initialCapacity = numElements;
  12. initialCapacity |= (initialCapacity >>> 1);
  13. initialCapacity |= (initialCapacity >>> 2);
  14. initialCapacity |= (initialCapacity >>> 4);
  15. initialCapacity |= (initialCapacity >>> 8);
  16. initialCapacity |= (initialCapacity >>> 16);
  17. initialCapacity++;
  18. if (initialCapacity < 0) // Too many elements, must back off
  19. initialCapacity >>>= 1;// Good luck allocating 2 ^ 30 elements
  20. }
  21. elements = (E[]) new Object[initialCapacity];
  22. }
  23. http://www.kmrlyy.com/zgjl/33451.html
复制代码

上边的代码是ArrayDeque的构造方法,可以看到,当没有定义大小时,ArrayDeque默认数组大小为16,而定义大小后,会调用allocateElements方法,这个方法的作用是:当给定长度小于最小长度8时,使用最小长度。若大于等于最小长度,则找到比给定长度大的最小的2的幂数。为什么要是2的幂数呢?原因有以下两点:

操作系统分配内存的方法使用伙伴系统的话,每一块的大小都是2的幂数,如果分配的内存大小为2的幂数,可以减少内存分配的时间。
伙伴系统在百度百科中的解释:http://baike.baidu.com/view/4935190.htm

在ArrayDeque的addFirst方法中不固定将头放在数组的第一位,而是循环移位。使用2的幂数能够有效判断头部所在的地址。

同样在第二点中,如果队列满了,数组扩充是将容量capacity值左移一位即可扩充一倍。

  1. javapublic void addFirst(E e) {
  2. if (e == null)
  3. throw new NullPointerException();
  4. elements[head = (head - 1) & (elements.length - 1)] = e;
  5. if (head == tail)
  6. doubleCapacity();
  7. }
  8. private void doubleCapacity() {
  9. assert head == tail;
  10. int p = head;
  11. int n = elements.length;
  12. int r = n - p; // number of elements to the right of p
  13. int newCapacity = n << 1;
  14. if (newCapacity < 0)
  15. throw new IllegalStateException("Sorry, deque too big");
  16. Object[] a = new Object[newCapacity];
  17. System.arraycopy(elements, p, a, 0, r);
  18. System.arraycopy(elements, 0, a, r, p);
  19. elements = (E[])a;
  20. head = 0;
  21. tail = n;
  22. }http://www.kmrlyy.com/gongjingai/33452.html
复制代码
七 List的选择

在文章开头提出了一个问题,数组实现的List快还是链表实现的List快。模拟一下试试:

  1. javapublic static void add()
  2. {
  3. long start = 0;
  4. long end = 0;
  5. List<Integer> alist = new ArrayList<>();
  6. List<Integer> llist = new LinkedList<>();
  7. System.out.println("ArrayList添加1000万数据所需毫秒数");
  8. start = System.currentTimeMillis();
  9. for (int i=0; i<10000000; i++)
  10. {
  11. alist.add(i);
  12. }
  13. end = System.currentTimeMillis();
  14. System.out.println(end-start);
  15. System.out.println("LinkedList添加1000万数据所需毫秒数");
  16. start = System.currentTimeMillis();
  17. for (int i=0; i<10000000; i++)
  18. {
  19. llist.add(i);
  20. }
  21. end = System.currentTimeMillis();
  22. System.out.println(end-start+"\n");
  23. http://www.kmrlyy.com/gongjingfeida/33453.html
  24. System.out.println("ArrayList从1000万数据删除数据所需毫秒数");
  25. start = System.currentTimeMillis();
  26. alist.remove(0);
  27. alist.remove(2000000);
  28. alist.remove(4000000);
  29. alist.remove(6000000);
  30. alist.remove(8000000);
  31. alist.remove(9999994);
  32. end = System.currentTimeMillis();
  33. System.out.println(end - start);
  34. http://m.nvzi91.cn/gongjingyan/29349.html
  35. System.out.println("LinkedList从1000万数据删除数据所需毫秒数");
  36. start = System.currentTimeMillis();
  37. llist.remove(0);
  38. llist.remove(2000000);
  39. llist.remove(4000000);
  40. llist.remove(6000000);
  41. llist.remove(8000000);
  42. llist.remove(9999994);
  43. end = System.currentTimeMillis();
  44. System.out.println(end - start+"\n");
  45. http://m.nvzi91.cn/gongjingmilan/29348.html
  46. System.out.println("ArrayList从1000万数据查找数据所需毫秒数");
  47. start = System.currentTimeMillis();
  48. alist.contains(0);http://m.nvzi91.cn/gongjingxirou/29350.html
  49. alist.contains(2000000);
  50. alist.contains(4000000);
  51. alist.contains(6000000);
  52. alist.contains(8000000);
  53. alist.contains(10000000);
  54. end = System.currentTimeMillis();
  55. System.out.println(end - start);
  56. www.nvzi91.cn     www.kmrlyy.com
  57. System.out.println("LinkedList从1000万数据查找数据所需毫秒数");
  58. start = System.currentTimeMillis();
  59. llist.contains(0);
  60. llist.contains(2000000);
  61. llist.contains(4000000);
  62. llist.contains(6000000);
  63. llist.contains(8000000);
  64. llist.contains(10000000);
  65. end = System.currentTimeMillis();
  66. System.out.println(end - start+"\n");
  67. }m.nvzi91.cn
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值