Java集合

Collection常用接口、实现类
1-1
Map接口树
1-2

List 接口

ArrayList 源码分析

jdk 1.7:
	ArrayList list = new ArrayList();	//底层长度为10的Object[]数组elementData
	list.add(123);	//实际为 elementData[0] = new Integer(123);
	...
	list.add(11);	//如果此处添加导致elementData数组容量不足,则扩容。
	//默认情况下,扩容为原来容量的1.5倍,同时需要将原有数组中的数据复制到新的数组中。

	//结论:为了避免频繁扩容造成的性能下降,建议使用带参的构造器:ArrayLsit list = new ArrayList(int capacity)

jdk 1.8:
	ArrayList list = new ArrayList();	//底层Object[] elementData初始化为{},并没有创建长度为10的数组
	list.add(123);	//第一次调用add()时,底层才创建了长度为10的数组,并将数据123添加到 elementData[0]中
	...
	//之后的扩容与jdk 1.7相同

小结:jdk 1.7中的ArrayList的对象的创建类似于单例的饿汉模式,
	 而jdk 1.8中的ArrayList对象的创建类似于懒汉模式,延迟了数组的创建,节省了内存

LinkedList 源码分析

	LinkedList list = new LinkedList();	//内部声明了Node类型的prev和next属性,默认值为null
	list.add(123);	//将123封装到Node中,创建了Node对象
	
	其中Node的定义,体现了LinkedList的双向链表说法
	private static class Node<E>{
		E item;
		Node<E> next;
		Node<E> prev;
		
		Node(Node<E> prev, E element, Node<E> next){
			this.item = element;
			this.next = prev;
			this.last = next;
		}
	}

链表概略图
1-3

Vector 底层

jdk 1.7 和 jdk 1.8 中通过Vector()构造对象时,底层都创建了长度为10的数组,
在扩容方面,默认扩容为原来数组的2

问题

1、集合 Collection 中存储自定义类的对象时,需要自定义类重写那些方法?

List:重写equals方法。
	//原因:equals方法默认比较两个对象的地址,如果需要比较两个对象的内容,则需要重写equals方法

Set:
	(HashSet、LinkedHashSet):重写equals()、HashCode()
	(TreeSet):Comparable:CompareTo(Object obj)
			   Comparator:compare(Object obj1,Object obj2)

2、ArrayList、LinkedList、Vector三者的相同点和不同点

相同点:三者都是List接口的实现类,元素有先后次序,并且可以重复
不同点:
	ArrayList 和 Vector 相比,底层都是动态数组维护的;ArrayList扩容是1.5倍,
而Vector扩容是2倍;ArrayList 是线程不安全的,效率比较高。
	ArrayList 和 LinkedList 相比,LinkedList 底层是一个双向链表,适合做插入频繁的删除、插入操作;
若是在尾部插入,ArrayList效率也比较高,按照下标进行查询,ArrrayList的效率比LinkedList高

迭代器的原理

1-4

Set 接口

存储的元素是无序的,不可重复的

以HashSet为例:
1、无序性:不等于随机性。存储的数据在底层中并非照数组索引顺序添加,而是根据数据的哈希值决定的。
2、不可重复性:保证添加的元素照equals()判断时,不能返回true。即相同的元素只能添加一个。

底层添加元素过程

向HashSet中添加元素a,首先调用元素 a 所在类的HashCode()方法,计算元素a的哈希值,
此哈希值接着通过某种算法计算出在HashSet底层数组中存放的位置,即为索引位置判断此位置上是否已经存在元素:
	如果此位置没有其他元素,则元素 a 添加成功。--->情况1
	如果此位置上有其他元素b 或以链表的形式存在多个元素,则比较元素a 与元素 b 的hash值:
		如果hash 值不相同,则元素 a 添加成功。--->情况2
		如果hash 值相同,进而需要调用元素 a 所在类的equals()方法:
			equals()返回true,元素 a 添加失败
			equals()返回false,元素 a 添加成功。--->情况3
		
对于添加成功的情况 2 和情况 3 而言:元素 a 与已经存在指定索引位置上的数据以链表的方式存储。
jdk 1.7:元素 a 放到数组中,指向原来的元素。
jdk 1.8:原来的元素在数组中,指向元素 a 。
总结:七上八下

jdk 1.7 开始 HashSet 的底层:数组 + 链表

实现子类

|---HashSet: 作为Set接口的主要实现类;线程不安全;可以存储null值
	|---LinkedHashSet: 作为HashSet的子类;遍历其内部数据时,可以按照添加的顺序进行遍历;
	在添加数据的同时,每个数据还维护了两个引用,记录次数据前一个数据和后一个数据。对于频繁的遍历操作,LinkedHashSet效率高于HashSet。
|---TreeSet: 可以按照添加对象的指定属性进行排序。

new HashSet 的底层是 new 一个HashMap,add 一个元素是把一个元素作为Key 来 put。
1-5
TreeSet 的底层是红黑树

练习

在 List 内去除重复的数字,要求尽量简单

public class TestListAndSet {

    public static List duplicateList(List list){
        HashSet set = new HashSet();
        set.addAll(list);
        return new ArrayList(set);
    }

    public static void main(String[] args) {

        ArrayList list = new ArrayList();
        list.add(new Integer(1));
        list.add(new Integer(1));
        list.add(new Integer(2));
        list.add(new Integer(2));
        list.add(new Integer(3));

        List list1 = duplicateList(list);
        System.out.println(list1);
        //打印结果:[1, 2, 3]
    }
}

对hashCode()和equals()的理解

 public static void main(String[] args) {

        HashSet set = new HashSet();
        Person p1 = new Person(1001,"a");
        Person p2 = new Person(1001,"b");

        set.add(p1);
        set.add(p2);
        System.out.println(set);
        //[Person{id=1001, name='a'}, Person{id=1001, name='b'}]

        p1.name = "c";
        System.out.println(set);
        boolean flag = set.remove(p1);
        System.out.println(flag);
        // false    底层为map.remove(key),此处把p1.name = "a" 改为 p1.name = "c"后,相当于key值变了,所以删除失败
        System.out.println(set);
        //[Person{id=1001, name='c'}, Person{id=1001, name='b'}]

        set.add(new Person(1001,"c"));
        System.out.println(set);
        //[Person{id=1001, name='c'}, Person{id=1001, name='b'}, Person{id=1001, name='c'}]

        set.add(new Person(1001,"a"));
        System.out.println(set);
        //[Person{id=1001, name='c'}, Person{id=1001, name='a'}, Person{id=1001, name='b'},
        //    Person{id=1001, name='c'}]
    }

Map 接口

Map接口的主要实现类

|---HashMap:作为map的主要实现类,是线程不安全的,因此效率比较高;能存储null的key和value.
	|---LinkedHashMap:保证在遍历Map元素时,可以按照添加的顺序进行遍历。原因:在原有的HashMap底层结构基础上,添加了一对指针,
	指向前一个元素和后一个元素,对于频繁的遍历操作,此类执行效率高于HashMap。
|---TreeMap:保证按照添加的key-value进行排序,实现排序遍历。此时考虑key的自然排序或定制排序,底层使用红黑树
|---HashTable:作为古老的实现类,线程是安全的,效率低;不能存储null的key和value。
	|---properties:常用来处理配置文件。key 和 value 都是String类型。

HashMap的底层:
	jdk 1.7 及之前: 数组 + 链表
	jdk 1.8 : 数组 + 链表 + 红黑树

HashMap 与 LinkedHashMap 的比较

	public static void main(String[] args) {
		Map map = new HashMap();
        map.put("3", "value1");
        map.put("2", "value2");
        map.put("5", "value3");

        Set entrySet = map.entrySet();
        Iterator iterator = entrySet.iterator();
        while(iterator.hasNext()){
            Map.Entry<String,String> entry = (Map.Entry<String, String>) iterator.next();
            String key = entry.getKey();
            String value = entry.getValue();
            System.out.println("key = " + key + ", value = " + value);
        }
        /**
         * 打印结果:
         *              key = 2, value = value2
         *              key = 3, value = value1
         *              key = 5, value = value3
         * 输出的顺序与放入的顺序不一致,取出遍历时,改变了顺序
         */

        System.out.println("-----------------------------------------");

        Map map1 = new LinkedHashMap();
        map1.put("3", "value1");
        map1.put("2", "value2");
        map1.put("5", "value3");
        Set entrySet1 = map1.entrySet();
        Iterator iterator1 = entrySet1.iterator();
        while(iterator1.hasNext()){
            Map.Entry<String, String> entry = (Map.Entry<String, String>) iterator1.next();
            String key = entry.getKey();
            String value = entry.getValue();
            System.out.println("key = " + key + ", value = " + value);
        }
        /**
         * 打印结果:
         *              key = 3, value = value1
         *              key = 2, value = value2
         *              key = 5, value = value3
         * 输出的结果完全与放入的结果一致
         */
    }

linkedHashMap的底层源码

1-9

Map的结构

1-6

Map结构的理解:
	Map中的key:无序的、不可重复的,使用set存储所有的key --->key所在的类要重写equals()hashCode() (以HashMap为例)
	Map中的value:无序的,可重复的,使用Collection存储所有的value;
	一个键值对(key-value)构成了一个Entry对象。
	Map中的Entry:无序的,不可重复的,使用set存储所有的Entry。

HashMap 的底层原理实现

HashMap的底层实现原理(jdk 1.7)
	HashMap map = new HashMap()
	在实例化以后,底层创建了长度为16的一维数组Entry[] table。
	...可能已经执行了很多次put...
	map.put(key1, value1):
	首先调用key1所在类的hashCode()计算key1哈希值,次哈希值经过某种算法计算以后,得到在Entry数组中的存放位置。
	如果此位置上的数据为空,此时的key1-value1添加成功。---情况1
	如果此位置上的数据不为空,意味着此位上存在一个或多个数据(以链表形式存在)),比较key1和已经存在一个或多个数据的哈希值:
		如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1添加成功。---情况2
		如果key1的哈希值和已经存在的某一个数据(key2-value2)的哈希值相同,继续比较:调用key1所在类的equals(key2)
			如果equals()返回false:此时key1-value1添加成功。---情况3
			如果equals()返回true:使用value1替换value2。
	
补充:关于情况2和情况3: 此时key1-value1和原来的数据以链表的方式存储。
在不断的添加过程中,会涉及到扩容问题,默认的扩容方式:扩容为原来的2倍,并将原来的数据复制过来。

jdk 1.8 相较于jdk 1.7 在底层方面的不同:
1new HashMap():底层没有创建一个长度为16的数组
2、jdk 1.8 底层数组是:Node[],而非Entry[]
3、首次调用put()方法时,底层创建长度为16的数组
4、jdk 1.7 底层结构只有:数组+链表。jdk 1.8 底层结构:数组+链表+红黑树
当数组的某一个索引位置上的元素以链表形式存在的数据个数>8 且 当数据长度>64时,此时此索引位置上的所有数据改为使用红黑树。

添加元素源码

1-7
扩容源码 jdk 1.7

与ArrayList达到10开始扩容相比,HashMap的容量为16,但达到12且key位置下的链表非空就开始扩容

1-8

Map的遍历方式

//一:获取key,通过keySet是由一个个key组成的来进行相应的遍历
        Set set = map.keySet();
        Iterator iterator = set.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }
        System.out.println("--------------------------------");

        //二:获取value,通过values
        Collection values = map.values();
        Iterator iterator1 = values.iterator();
        while(iterator1.hasNext()) {
            System.out.println(iterator1.next());
        }
        //通过foreach遍历
        for(Object value : values){
            System.out.println(value);
        }

        System.out.println("--------------------------------");
        //三、通过获取entrySet的对象来获取entry对象,通过entry对象来
        Set set1 = map.entrySet();
        Iterator iterator2 = set1.iterator();
        while(iterator2.hasNext()){
            //entrySet是由一个个entry组成的Set集合
            Object obj = iterator2.next();
            //entry是由key和value组成的,因此可以通过entry的对象获取到key和value
            Map.Entry entry = (Map.Entry) obj;
            System.out.println(entry.getKey()+"--->"+entry.getValue());
        }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值