`
huoyj
  • 浏览: 89390 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

读JDK源码-集合部分

    博客分类:
  • J2SE
 
阅读更多
以前读过一遍JDK源码的集合部分,读完了一段时间后忘了,直到有一次面试简历上还写着读过JDK集合部分的源码,但面试官让我说说,感觉记得不是很清楚了,回答的也模模糊糊的,哎,老了记性越来越差了,所以再回头来读一遍,并且在这里做个笔记,省的又忘了,java.util里的集合类的源代码本身不是很难,就一个一个的记录吧:
(1).ArrayList:
此类底层数据结构是数组:
private transient Object[] elementData;

另外还有一个属性是用来记录size的,感觉JDK源码实现的确实很优雅,他里面的属性不多也不少,都用到很精妙。
三个构造函数:
public ArrayList(int initialCapacity) {
	super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
	this.elementData = new Object[initialCapacity];
    }
public ArrayList() {        //[b]由这个无参构造可以看得出默认分配的capacity是10个[/b]
	this(10);
    }
public ArrayList(Collection<? extends E> c) {
	elementData = c.toArray();
	size = elementData.length;
	// c.toArray might (incorrectly) not return Object[] (see 6260652)
	if (elementData.getClass() != Object[].class)
	    elementData = Arrays.copyOf(elementData, size, Object[].class);
    }

添加一个元素:
    //默认添加到数组最末尾
    public boolean add(E e) {
	ensureCapacity(size + 1);  // Increments modCount!!
	elementData[size++] = e;
	return true;
    }
//ensureCapacity方法确认一下数组长度,当长度不够minCapacity时就重新分配数组空间,在add方法中调用了此方法,传递给形参minCapacity的值为size+1、即有当前一个元素的空间就可以了,由此可以看出ArrayList的空间不是预先加载的,int newCapacity = (oldCapacity * 3)/2 + 1就表示新添加到空间的大小是原来长度的一半。
    public void ensureCapacity(int minCapacity) {
	modCount++;
	int oldCapacity = elementData.length;
	if (minCapacity > oldCapacity) {
	    Object oldData[] = elementData;
	   [b] int newCapacity = (oldCapacity * 3)/2 + 1[/b];
    	    if (newCapacity < minCapacity)
		newCapacity = minCapacity;
            // minCapacity is usually close to size, so this is a win:
            elementData = Arrays.copyOf(elementData, newCapacity);
	}
    }
//此方法是在指定位置添加一个元素,将此位置后的元素向后移动一个空间。
    public void add(int index, E element) {
	if (index > size || index < 0)
	    throw new IndexOutOfBoundsException(
		"Index: "+index+", Size: "+size);

	ensureCapacity(size+1);  // Increments modCount!!
	System.arraycopy(elementData, index, elementData, index + 1,
			 size - index);
	elementData[index] = element;
	size++;
    }

添加一个集合进来:
  
 
//追加一个集合到list中,先确认了数组空间是否能容纳的下要添加到元素,然后利用了System.arraycopy方法将所有元素复制进数组中,实现起来很容易。
   public boolean addAll(Collection<? extends E> c) {
	Object[] a = c.toArray();
        int numNew = a.length;
	ensureCapacity(size + numNew);  // Increments modCount
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
	return numNew != 0;
    }
    public boolean addAll(int index, Collection<? extends E> c) {
	if (index > size || index < 0)
	    throw new IndexOutOfBoundsException(
		"Index: " + index + ", Size: " + size);

	Object[] a = c.toArray();
	int numNew = a.length;
	ensureCapacity(size + numNew);  // Increments modCount

	int numMoved = size - index;
	if (numMoved > 0)
	    System.arraycopy(elementData, index, elementData, index + numNew,
			     numMoved);

        System.arraycopy(a, 0, elementData, index, numNew);
	size += numNew;
	return numNew != 0;
    }

remove(int index),get和set方法都是直接对数组指定位置的元素进行操作。
remove(Object obj),indexOf(Object obj)方法这样一些方法都是对数组的遍历,ArrayList中运行存放null,ArrayList是比较简单的。
(2).HashMap:
HashMap就是用hash方式映射key-value,底层实现是数组+链表的方式,通过hash码定位索引,如果有重复的索引存在就以链表的方式存放索引相同的元素,HashMap中会预分配空间,初始默认分配16个空间存放元素。
以数组+链表的方式存放元素:
transient Entry[] table;
Entry是一个静态内部类,他实现的是[b]Map接口中的一个内部接口[/b],接口也可以有嵌套定义的,长见识了。:

   static final int DEFAULT_INITIAL_CAPACITY = 16;//默认的初始分配空间大小
   static final int MAXIMUM_CAPACITY = 1 << 30; //最大能分配的空间,写JDK的人真能装的,好端端的写个常数就行了,1左移一次相当于乘2,那就是2^30=1073741824(我也是计算器算的)
   static final float DEFAULT_LOAD_FACTOR = 0.75f; //默认的加载因子。

  static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;
        V value;
        Entry<K,V> next;       //典型的单向列表,后面的LinkedList还用到了双向列表。
        final int hash;
      .......
   }

Map中的Entry就不在此列出了。。。
再看看他的变态的hash方法,我也没看明白,就先放这把,哪位要是看懂了给我回复一下:
 
 static int hash(int h) {
        // This function ensures that hashCodes that differ only by
        // constant multiples at each bit position have a bounded
        // number of collisions (approximately 8 at default load factor).
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

接下来我们就来看看HashMap是怎么样进行增删改查的
增加元素:
 
   //可以添加<null,null>进去哎 
     public V put(K key, V value) {
        if (key == null)   
            return putForNullKey(value);
        int hash = hash(key.hashCode());
        int i = indexFor(hash, table.length);
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {  //遍历链表,如果元素已经存在就更新,否则就在链表中添加一个。
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }
//根据hash值计算索引位置的
   static int indexFor(int h, int length) {
        return h & (length-1);
    }
//添加到元素不存在,在链表头添加元素
   void addEntry(int hash, K key, V value, int bucketIndex) {
	Entry<K,V> e = table[bucketIndex];
        table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
        if (size++ >= threshold)
            resize(2 * table.length);
    }
//既然看到resize方法就说一下吧
在addEntry方法里看到一个threshold的属性,该属性的值为capacity * loadFactor,当当前元素的个数超过threshold时就扩展数组大小,包括链表上面的元素。
    void resize(int newCapacity) {
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }

        Entry[] newTable = new Entry[newCapacity];
        transfer(newTable);
        table = newTable;
        threshold = (int)(newCapacity * loadFactor);
    }

移除一个元素,因为这里涉及到数组和链表两种数据结构,所以移除时要分清楚:
 
final Entry<K,V> removeEntryForKey(Object key) {
        int hash = (key == null) ? 0 : hash(key.hashCode());
        int i = indexFor(hash, table.length);
        Entry<K,V> prev = table[i];
        Entry<K,V> e = prev;     //prev和e都初始为链表第一个元素

        while (e != null) {    //检查链表
            Entry<K,V> next = e.next;
            Object k;
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k)))) {
                modCount++;
                size--;
                if (prev == e) //移除的就是链表第一个元素
                    table[i] = next;
                else        //要移除的就是e,将prev的next指向e的next。
                    prev.next = next;
                e.recordRemoval(this);//看了一下这个方法里没有代码。
                return e;     //移除后的元素就交给GC了。
            }
            prev = e;
            e = next;
        }

        return e;
    }

查找就不多说了,看代码也比较简单。
(3).HashSet
看完了HashMap再看HashSet比较简单了,因为HashSet底层是HashMap实现的,要添加的元素作为HashMap的key,private static final Object PRESENT = new Object();作为value,由此可见HashSet是不允许有重复元素的。
    public boolean add(E e) {
	return map.put(e, PRESENT)==null;   //put方法返回与key关联的旧值,如果没有旧值就返回null,而在HashSet中PRESENT是个常量,所以第一次add时返回true,以后add时返回false,表示重复添加了,但实际上也添加进去了,只是key都一样,value都是PRESENT.
    }
    public boolean remove(Object o) {
	return map.remove(o)==PRESENT;
    }
    public void clear() {
	map.clear();
    }
    public Iterator<E> iterator() {
	return map.keySet().iterator();
    }

(4).LinkedList
现在该LinkedList出场了,这个是我们数据结构里所学的双向链表,如果数据结构双向链表学到不错的话这个里面的代码也挺好理解的,首先看看他的field和constructor:
private transient Entry<E> header = new Entry<E>(null, null, null);

 public LinkedList() {
        header.next = header.previous = header; //一开始表头元素的前驱和后继都指向了自己
    }
//静态内部类
    private static class Entry<E> {
	E element;
	Entry<E> next;
	Entry<E> previous;

	Entry(E element, Entry<E> next, Entry<E> previous) {
	    this.element = element;
	    this.next = next;
	    this.previous = previous;
	}
    }

以下几个增删查到方法都比较简单。
   
public E getFirst() {
	if (size==0)
	    throw new NoSuchElementException();

	return header.next.element;
    }
    public E getLast()  {
	if (size==0)
	    throw new NoSuchElementException();

	return header.previous.element;
    }

    public E removeFirst() {
	return remove(header.next);
    }

    public E removeLast() {
	return remove(header.previous);
    }

    public void addFirst(E e) {
	addBefore(e, header.next);
    }

    public void addLast(E e) {
	addBefore(e, header);
    }
    public void push(E e) {
        addFirst(e);
    }
    public E pop() {
        return removeFirst();
    }

// 更新:
    public E set(int index, E element) {
        Entry<E> e = entry(index);
        E oldVal = e.element;
        e.element = element;
        return oldVal;
    }
    private Entry<E> entry(int index) {
        if (index < 0 || index >= size)
            throw new IndexOutOfBoundsException("Index: "+index+
                                                ", Size: "+size);
        Entry<E> e = header;
        if (index < (size >> 1)) {  //entry方法在此处采用了二分法查找
            for (int i = 0; i <= index; i++)
                e = e.next;
        } else {
            for (int i = size; i > index; i--)
                e = e.previous;
        }
        return e;
    }

【补充】迭代器:待续
分享到:
评论

相关推荐

    jdk1.8.0-8u333-linux-arm64.tar.gz

    Stream API允许对集合进行声明式处理,提高了数据处理的效率;日期与时间API的改进则提供了更好的日期和时间操作功能。 "arm64"表示这个JDK是针对ARM架构的64位版本。ARM架构是一种广泛应用于移动设备和嵌入式系统...

    jdk-7u80-windows-x64.exe

    【标签】"源码软件" 暗示JDK包含的不仅有二进制文件,还可能包括Java语言的源代码,这对于学习和理解Java的内部工作原理非常有帮助。"windows" 表明这是为Windows操作系统设计的版本。"jdk-7" 指的是Java SE(标准版...

    openjdk-17 GA源码(jdk17-jdk-17-ga.tar.gz)

    2. `jdk`:这部分包含了Java公共API,如基础库、IO、网络、集合框架等。 3. `langtools`:提供了Java编译器Javac和其他语言工具,如Javadoc和jar工具。 4. `test`:测试套件,用于验证OpenJDK实现的正确性和性能。...

    jdk源码包jdk-11.0.1

    【描述】"jdk源码包"意味着这个压缩文件包含了Java开发工具集(JDK)的所有源代码。通过分析这些源码,开发者可以学习到Java语言的内部工作原理,包括类库、虚拟机(JVM)以及各种工具的实现细节。 【标签】"jdk"和...

    jdk源码 jdk源码

    Java Development Kit (JDK) 源码是学习和理解Java平台核心机制的关键资源。...通过深入阅读JDK源码,开发者不仅可以增强对Java语言特性的理解,还能提高解决实际问题的能力,这对于成为一名优秀的Java开发者至关重要。

    JDK8源码 jdk-1.8.0_221 jdk-8u221 src源码

    在Java开发领域,深入理解JDK源码是提升技术水平的重要途径。本篇文章将围绕标题“JDK8源码 jdk-1.8.0_221 jdk-8u221 src源码”展开,通过分析描述中的“jdk1.8.0_221-src.7z”压缩包内容,带你走进JDK8的世界,了解...

    jdk源码(完整版)

    这里提到的"jdk源码(完整版)"提供了JDK的源代码,包括了最新的OpenJDK 1.8版本。OpenJDK是JDK的一个开源实现,由全球开发者社区共同维护,其源码公开使得开发者能够深入理解Java平台的工作原理。 1. **Java核心库...

    jdk-15_windows-x64_binexe.zip

    描述部分同样简洁地提到了"jdk-15_windows-x64_binexe.zip",没有提供额外的信息,但我们可以理解这是下载或分发JDK的一种常见格式。 **标签解析** "jdk"标签代表了Java Development Kit,它是Java编程语言和平台...

    jdk源码的另一部分

    在深入探讨JDK源码之前,我们先理解一下它的核心概念。JDK(Java Development Kit)是Oracle公司提供的用于开发和运行Java应用程序的工具集合,其中包含了Java编译器、Java运行时环境(JRE)、Java类库以及各种实用...

    javajdk源码-jdk8-base-source-code:jdk8中的java.base的源码学习

    本资源"javajdk源码-jdk8-base-source-code"聚焦于JDK8中的"java.base"模块,这是Java平台标准版(Java SE)的基础部分,包含了大量核心类库。下面将详细解析这个模块中的关键知识点。 首先,"java.base"模块包含了...

    javajdk源码-JDK8-Source-Code:JavaDevelopmentKit8源代码

    Java JDK8源代码是Java开发工具包的第八个主要版本的源码,它包含了实现Java平台标准版(Java SE)的全部源代码。这个源码库对于深入理解Java语言、虚拟机(JVM)、类库以及Java编程的核心概念至关重要。通过研究JDK...

    jdk8u-jdk-master

    3. jdk:这个模块包含了Java标准库的所有实现,包括基本类型、集合框架、IO流、网络、并发等。 4. corba:用于实现CORBA(Common Object Request Broker Architecture)的代码。 5. jaxp:Java API for XML ...

    openjdk-18 GA源码(jdk18-jdk-18-ga.tar.gz)

    - **Stream API**:Java的流API持续扩展和完善,增加了新的方法来增强其功能,例如在集合操作中的更多便利方法。 - **日期和时间API**:Java 8引入的日期和时间API在OpenJDK 18中得到了进一步的增强,提供了更丰富...

    jdk8源码(jdk-687fd7c7986d)

    在Java编程领域,深入理解JDK源码是提升技术能力的重要途径。JDK8是Java发展过程中的一个重要里程碑,引入了许多创新特性,如Lambda表达式、Stream API以及日期时间API的改进等。本次我们将主要探讨解压后的`jdk-687...

    jdk1.8-src

    《深入解析JDK1.8源码》 JDK1.8是Java开发的一个重要版本,它引入了许多新的特性和优化,对开发者来说,理解其源码有助于提升编程技巧和解决问题的能力。本篇将深入探讨JDK1.8源码中的关键知识点。 一、Lambda...

    JDK1.7.0-80总源码,包含各种来源的

    源码的结构通常分为几个主要部分,包括`javax`、`com`、`org`、`jdk`、`java`、`sun`和`sunw`等。这些包名代表着不同的模块和功能: 1. `javax`包:主要用于Java扩展功能,如JavaBeans、JavaServer Faces (JSF) 和...

    Java安装:jdk-8u331-windows-x64.exe

    3. **Stream API**:这是一个处理集合的新API,提供了一种声明式处理数据的方式,便于进行过滤、映射和归约操作。 4. **Date和Time API的改进**:Java 8引入了新的`java.time`包,替代了过时的`java.util.Date`和`...

    jdk 源码 完整版

    《深入解析JDK源码:探索Java编程的核心奥秘》 在软件开发领域,JDK(Java Development Kit)作为Java编程的基础工具,其源码是开发者深入理解Java语言、提升编程技能的重要资源。这份"jdk源码 完整版"包含了javax...

    jdk的部分源码(jdk)

    这里提供的"jdk的部分源码"压缩包很可能是Oracle JDK或者OpenJDK的一部分,它们都是开源的,允许开发者深入研究和定制。 1. **Java虚拟机(JVM)**: JDK的源码中包含了Java虚拟机的实现,JVM是Java程序的执行环境。...

Global site tag (gtag.js) - Google Analytics