java集合源码分析

.Java中的集合位于java.util包下,主要的结构如下:

java集合源码分析
2.集合中存放的都是对象的引用而不是对象本身。集合中不能放入原生数据类型,要放入原生数据类型,可以将其转化成包装类型后再放入,不过JDK1.5出现了自动的装箱和拆箱,原生数据类型可以直接放入,java自动会将原生数据类型包装成包装类型后放入。
3.ArrayList底层代码如下:
java集合源码分析
由上面代码可以看到ArrayList的带参数的构造方法就是new了一个传入长度的Object数组,而不带参数的构造方法就是默认new一个长度为10的Object数组。因此ArrayList的底层就是一个Object数组。

java集合源码分析
以上的add方法,先判断size 1是否大于了ArrayList底层数组的最大容量,如果大于,那么将新new一个长度是原来长度的3/21长度的数组,然后再将原数组copy到新数组中去。将新数组保存到底层数组中

5.ArrayList中还提供了可以转成数组的方法,如下:

java集合源码分析
6.ArrayList中提供了删除某一位置的元素的方法如下

java集合源码分析
7.ArrayList类中提供的获取某一位置的元素的方法比较简单如下:

java集合源码分析
8.由上面的源码可以看出来ArrayList就是利用数组实现的一个轻量级的集合,该集合是线程不安全的,如果在多线程的环境中使用,需要自行考虑线程安全问题。

9.Vector(向量)和ArrayList差不多,都是继承自AbstractList,底层都是用数组实现的, 但是与ArrayList相比Vector明显显得笨重了些,因为,Vector底层考虑了线程同步的问题,在方法定义时都加上了synchronized关键字,所以是线程安全的,还有一个区别是,当Vector中容量装满后,如果还往里面加元素那么,就会在底层生成一个原数组两倍容 量的数组,再将原数组拷贝到新数组中去,而在ArrayList中这个新数组长度是原数组的3/2倍1的长度。很明显在非多线程的环境下轻量级的ArrayList性能比Vector快。具体细节可以看底层源码。

10.Stack(栈):栈是一种先进后出的集合,直接继承自Vector,所有也是线程安全的。先进先出就是一个只有一头开口的盒子,放进一个东西就放在最里面了,要拿出来只能 是上面的都拿完了才能拿出来。需要注意的是Stack类中的几个方法:

  1)压栈的方法(就是放入数据的方法):push方法的源码如下

  java集合源码分析
    java集合源码分析

    由上可以看出数据放入Stack中,其实就是直接将数据赋给了栈底层(Vector 底层)维护的数组中,这个
   时候数据(其实是引用)是直接放入栈中,而不是数据的拷贝放入栈中了,那么这样就会有一个后果就是
   对元数据的任何操作都是会直接影响栈中存放的数据,如下代码(省略一部分代码)

扫描二维码关注公众号,回复: 475629 查看本文章

   java集合源码分析

   运行会打印一个zhangsan一个lisi,其中上面的peek方法是获得最上面的一个元素,也就是最后放进去的一
   个元素。
  有很多的情况是不希望出现这样的结果的,那么使用这个Stack后,就必须注意不要在原数据上修改了。也可
   以自己实现一个自己的栈。

  2)查看栈顶的元素:peek()是查看栈顶的一个元素的方法,栈顶就是最上面的一个元素也就是最后压入的一
   个元素。
 3)移除并返回栈顶的元素:pop()是移除栈顶的一个元素的方法,也就是移除栈中最后压入的一个元素
 4)判断栈中是否为空:empty()是判断栈中是否为空
 5)搜索栈中的元素:search(Object o),返回一个元素的位置,栈顶位置为1.

11.LinkedList(链接列表):链接列表底层是数据结构中的链表结构。

12.单向链表示意图如下

java集合源码分析

如图所示单项链表是一个数据加上后一个数据的引用一对对出现的,一个是本身数据还有一个是指向它的后继元素的引用。用代码表示一个简单的链表如下:

java集合源码分析
java集合源码分析
13.循环链表的示意图如下所示

java集合源码分析
14.双向循环链表示意图如下
java集合源码分析
比单向链表多了一个前面的引用,既有指向前驱的元素的引用又有指向后继元素的引用

java集合源码分析
15.LinkedList的底层主要代码如下:
   1)成员变量和构造方法

    java集合源码分析
    上面代码中的Entry对象就是上面实现双向链表的那个类,由上可见,调用LinkedList的不带参数的构造方
   法生成的是一个长度为一的指向自身的空的循环链表。由上面可以看出LinkedList底层数据直接维护的仅仅
   是链表头(第一个元素)代表的Entry对象,那么后面跟着的对象都是由Entry对象里的前驱和后继引用表示
   的,由构造方法可以看出LinkedList底层是一个双向的循环链表。既然是双向循环链表那么任何一个链表对
   象的头元素的前驱元素必然是该链表的尾部元素。

   2)在链表尾部添加一个元素的方法:add(Ee)源码如下:

    java集合源码分析
java集合源码分析
  3)删除某个索引位置的元素

  java集合源码分析
 java集合源码分析
   java集合源码分析
16.由上源码可以分析比较出ArrayList和LinkedList的区别:

  1)ArrayList底层采用数组实现,LinkedList底层采用双向循环链表实现。
 2)当执行插入或者删除操作时,采用LinkedList效率比较高,它只需要改变它前后两个元素的后继和前驱
  引用就行了,也不涉及到扩容问题,而ArrayList是顺序结构的所以需要大量的移动元素,而且当插入元素
   的数量超过当前容量时,在底层还会new一个1.5倍1长度的新数组,然后再将旧数组的所有元素copy到
   新数组中。
 3)当执行搜索操作时,ArrayList效率较高,
 4)当向ArrayList添加一个对象时,实际上是将该对象的引用放置到了ArrayList底层所维护的数组中;而
  向LinkedList中添加一个对象时,实际上LinkedList内部会生成一个Entry对象,该对象的结构是

    class Entry {
     Entry previous;//前驱引用,指向前一个元素的引用
     Object element;//需要添加的元素
     Entry next;//后继引用,指向下一个元素的引用
  }
 最后将这个Entry对象加入到链表当中,在一个链表中直接维护的仅仅是一个代表链表头的Entry对象,链
  表中的其它对象都是由该对象前驱引用或者是后继引用这样一层层往前或往后可以找到的。
17.和List不同Set是一类不包含重复元素的集合,实现该接口的主要几个类是HashSet,LinkedHashSet和TreeSet。这些类在介绍了映射后再看就简单了,这里先不做介绍。

18.集合的遍历:集合的遍历主要有三种方式:
   1)使用简单for循环:

    java集合源码分析
   当然这种for循环还有很多不同的写法,就不多说了。
   2)使用迭代器

   java集合源码分析

  模仿for循环上面的迭代器还存在几个写法不同的变种,其实是一个意思
   java集合源码分析

  上面代码就是将初始化过程放在循环外面了。执行流程完全一样

  java集合源码分析
 上面这个可能很少见到,但是模仿简单for循环的执行流程就很清楚了,上面的初始化仍然可以放在外面

  java集合源码分析
 上面这个更狠,for后面的括号里面就有两个分号了,其它的都放在外面了,由上面的发现for循环的写法
  是非常活的,只要自己习惯怎么写就怎么写,其实上面的几种方式效果是一样的。

  3)增强for循环:

  java集合源码分析
 刚看见上面的代码可能不好理解,现在我们将上面的代码生成的.class文件放在反编译软件XJad中反编译后
  得出如下代码
java集合源码分析
   也就是说增强for循环针对这种集合底层就是使用迭代器,只是简化了我们的操作由上面可以看出增强for循
   环支持泛型,大大降低了代码的简洁性。
  4)对增强for循环可能会有疑问:数组不能使用迭代器迭代,为什么可以使用增强for循环呢?看下面的例子
   就知道了。

   java集合源码分析
   将上面代码产生的class文件进行反编译,如下

   java集合源码分析

  那么可以看出增强for循环针对数组的底层是简单for循环,由上面可以看出针对和数组具有同样的线性结构
   的ArrayList的增强for循环都是使用的迭代器,链式结构就更不用说了,那么我可以大胆猜测增强for循环针
   对不能迭代的数组底层是简单for循环,针对可以迭代的集合底层就是迭代的方式了。这个我没有一一验证,
   如果不确定可以自己试试。

19.关于集合遍历的时候删除满足特定条件的元素的问题

   1)简单for循环遍历删除

   java集合源码分析
   上面的方式完全没问题

   2)迭代器遍历删除

   java集合源码分析
  上面的代码是使用迭代器自己的删除方法删除该元素也是没有问题的。但是往往我们会习惯性的写成

  java集合源码分析
   那么错误也会随之而来如下

   java集合源码分析

 看看上面报的错误,首先我们知道一般编译后内部类的名字都是:类名$内部类名,那么这样我们上上面的错
  误就很好理解了,就是AbstractLIst类里面的Itr内部类的
  checkForComodification方法有问题,源码如下
   java集合源码分析
  由上面代码知道根源在modCount和expectedModCount不相同才产生了异常。modCount表示数据被修改
  的次数,而expectedModCount是迭代器里面使用的变量表示迭代器期望的集合数据被修改的次数,对于迭
  代器来说,为了遍历方便他当然希望这个次数就是他迭代集合之前的modCount,而expectedModCount只
  有在获得集合迭代器的时候或者是由迭代器修改数据(包括删除)的时候expectedModCount才会和
  modCount同步,所以在迭代的时候通过集合本身的方法添加或删除集合的元素都会使这两个参数不同步,如
  上面那个产生异常的原因是调用了集合本身的remove方法时modCount变化了,但是expectedModCount没
   有变化,所以抛出异常。内部类Itr的代码如下
   java集合源码分析
   3)增强for循环遍历删除:针对集合来说,增强for循环底层是使用迭代器遍历的,但是它是将迭代器封装
  在了增强for循环里面,在循环里面没法拿到那个迭代器,因此无法靠一个增强for循环删除集合中的指定元
   素,不过可以使用两个for循环达到目的如下:

 

  java集合源码分析
20.对于各种遍历方式的粗略的性能试验:
java集合源码分析
(进行20次左右测试)运行结果如下
 java集合源码分析
 上面的实验结果从一定程度上说明了,简单for循环在大数据量遍历的时候性能优于迭代器迭代,所以在能够使用遍历索引方式遍历(简单for循环)的线性结构的集合和数组的时候尽量使用简单for循环,不要装那个啥的使用迭代器,虽然在数据量不是很大的时候差距不大,但是迭代器遍历的同时修改数据很容易因为习惯使代码出现异常就像上面那样。当然对于那种链式结构(LinkedList)这样的集合,我们只能使用迭代器或者是增强for循环了。不过在要求不高的场合,还是可以根据习惯选择遍 历方式。


猜你喜欢

转载自blog.csdn.net/j_loveyou/article/details/50836648