常用集合类(Set、Map、List)比较

Java中数据存储方式最底层的两种结构:数组和链表,数组的特点:连续空间,寻址迅速,但是在删除或者添加元素的时候需要有较大幅度的移动,所以查询速度快,增删较慢。而链表正好相反,由于空间不连续,寻址困难,增删元素只需修改指针,所以查询慢、增删快。有没有一种数据结构来综合一下数组和链表,以便发挥他们各自的优势?答案是肯定的!

就是:哈希表。哈希表具有较快(常量级)的查询速度,及相对较快的增删速度,所以很适合在海量数据的环境中使用。一般实现哈希表的方法采用“拉链法”。

博客:https://blog.csdn.net/shadow_zed/article/details/78232955

1、HashSet、TreeSet

1、 存储的数据结构不同: HashSet底层用的是HashMap哈希表结构存储,而TreeSet底层用二叉树结构存储。

2、存储时保证数据唯一性依据不同:HashSet是通过复写hashCode()方法和equals()方法来保证的,而TreeSet通过Compareable接口的compareTo()方法来保证的。

3、有序性不一样:HashSet无序,可以放入null,但只能放入一个null,TreeSet有序且不允许放入null值,TreeSet是SortedSet接口的唯一实现类,TreeSet可以确保集合元素处于排序状态。

public static void main(String[] args) {
		HashSet<String> set1 = new HashSet<String>();
		set1.add("2");
		set1.add("1");
		set1.add("3");
		set1.add(null);
		set1.add(null);
		System.out.println(set1);// [null, 3, 2, 1]

		TreeSet<String> set2 = new TreeSet<String>();
		set2.add("2");
		set2.add("1");
		set2.add("3");
		// set2.add(null); 运行报错
		System.out.println(set2);// [1, 2, 3]
	}

存储原理:

    1、HashSet:底层数据结构是哈希表,本质就是对哈希值的存储,通过判断元素的hashCode方法和equals方法来保证元素的唯一性,当hashCode值不相同,就直接存储了,不用在判断equals了,当hashCode值相同时,会在判断一次euqals方法的返回值是否为true,如果为true则视为用一个元素,不用存储,如果为false,这些相同哈希值不同内容的元素都存放一个桶里(当哈希表中有一个桶结构,每一个桶都有一个哈希值)

   2、TreeSet:底层的数据结构是二叉树,可以对Set集合中的元素进行排序,这种结构可以提高排序性能,根据比较方法的返回值确定的,只要返回的是0就代表元素重复。

  插值:当向HashSet结合中存入一个元素时,HashSet会调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据 hashCode值来决定该对象在HashSet中存储位置。

      简单的说,HashSet集合判断两个元素相等的标准是两个对象通过equals方法比较相等,并且两个对象的hashCode()方法返回值相等。

    TreeSet判断两个对象不相等的方式是两个对象通过equals方法返回false,或者通过CompareTo方法比较没有返回0。

     LinkedHashSet集合同样是根据元素的hashCode值来决定元素的存储位置,但是它同时使用链表维护元素的次序。这样使得元素看起 来像是以插入顺 序保存的,也就是说,当遍历该集合时候,LinkedHashSet将会以元素的添加顺序访问集合的元素。
     LinkedHashSet在迭代访问Set中的全部元素时,性能比HashSet好,但是插入时性能稍微逊色于HashSet。

     LinkedHashSet集合同样是根据元素的hashCode值来决定元素的存储位置,但是它同时使用链表维护元素的次序。这样使得元素看起 来像是以插入顺 序保存的,也就是说,当遍历该集合时候,LinkedHashSet将会以元素的添加顺序访问集合的元素。
     LinkedHashSet在迭代访问Set中的全部元素时,性能比HashSet好,但是插入时性能稍微逊色于HashSet。

public static void main(String[] args)
    {
        /**
         * LinkedHashSet
         * 底层是链表实现的,是set集合中唯一一个能保证怎么存就怎么取的集合对象
         * 因为是HashSet的子类,所以也是保证元素唯一的,与HashSet的原理一样.
         * HashSet的子类,LinkedHashSet也是根据元素的hashCode值来决定
         * 元素的存储位置,但它同时使用链表维护元素的次序,这样使得元素看起来是
         * 以插入的顺序保存的。
         */
        LinkedHashSet<String> lhs = new LinkedHashSet<String>();
        lhs.add("a");
        lhs.add("b");
        lhs.add("a");
        lhs.add("c");
        lhs.add("a");
        lhs.add("d");
        
        System.out.println(lhs);//[a, b, c, d]      
    }

 

2、HashMap、TreeMap

   大多数情况下,只要不涉及线程安全问题,Map基本都可以使用HashMap,不过HashMap的顺序并不是HashMap放置的顺序,也就是无序。HashMap的这一缺点往往会带来困扰。

 HashMap是基于哈希表实现的,每一个元素是一个key-value对,其内部通过单链表解决冲突问题,容量不足(超过了阀值)时,同样会自动增长。HashMap最多只允许一条记录的键为Null,允许多条记录的值为 NullTreeMap没有调优选项,因为该树总处于平衡状态。

      HashMap是非线程安全的,只是用于单线程环境下,多线程环境下可以采用concurrent并发包下的concurrentHashMap

HashMap:基于哈希表实现。使用HashMap要求添加的键类明确定义了hashCode()equals()(可以重写hashCode()equals()),为了优化HashMap空间的使用,您可以调优初始容量和负载因子。

比较:

(1)实现 :  TreeMap:SortMap接口,基于红黑树       HashMap:基于哈希散列表实现 

(2)存储 :  TreeMap:默认按键的升序排序                 HashMap:随机存储

(3)遍历 :  TreeMap:Iterator遍历是排序的                HashMap:Iterator遍历是随机的 

(4)性能损耗 :TreeMap:插入、删除                          HashMap:基本无 

(5)键值对 : TreeMap:键、值都不能为null                HashMap:只允许键、值均为null 

(6)安全 : TreeMap:非并发安全Map                         HashMap:非并发安全Map 

(7)效率 : TreeMap:低                                              HashMap:高

HashMap通常比TreeMap快一点(树和哈希表的数据结构使然)

HashMap通过hashcode对其内容进行快速查找,而 TreeMap中所有的元素都保持着某种固定的顺序,如果你需要得到一个有序的结果你就应该使用TreeMapHashMap中元素的排列顺序是不固定的)。

  HashMap:适用于在Map中插入、删除和定位元素。

  Treemap:适用于按自然顺序或自定义顺序遍历键(key)

优秀博客: 

https://blog.csdn.net/fujiakai/article/details/51585767

https://www.cnblogs.com/beatIteWeNerverGiveUp/p/5709841.html

http://www.importnew.com/24822.html

HashMap、HashTable区别:

HashMap是非线程安全的,HashTable是线程安全的, 内部的方法基本都是synchronized。 
2、HashMap的键和值都允许有null值存在,而HashTable则不行。 
3、因为线程安全的问题,HashMap效率比HashTable的要高。

HashTable、ConcurrentHashMap区别:

ConcurrentHashMap是线程安全的HashMap的实现。

同样是线程安全的类,它与HashTable在同步方面有什么不同呢? 

synchronized关键字加锁的原理,其实是对对象加锁,不论你是在方法前加synchronized还是语句块前加,锁住的都是对象整体,但是ConcurrentHashMap的同步机制和这个不同,它不是加synchronized关键字,而是基于lock操作的,这样的目的是保证同步的时候,锁住的不是整个对象。事实上,ConcurrentHashMap可以满足concurrentLevel个线程并发无阻塞的操作集合对象。

HashMap实现原理:

HashMap即是采用了链地址法,也就是数组+链表的方式。

HashMap的主干是一个Entry数组。Entry是HashMap的基本组成单元,每一个Entry包含一个key-value键值对。

实现原理:

https://www.cnblogs.com/chengxiao/p/6059914.html

https://blog.csdn.net/lyt_7cs1dn9/article/details/54925837

LinkedHashSet:

是基于HashSet实现的,在HashSet的基础上,增加了时间和空间上的开销维持了一个双向链表的关系, 保证了元素迭代的顺序。

   

     

LinkedHashMap是否允许空

Key和Value都允许空

LinkedHashMap是否允许重复数据

Key重复会覆盖、Value允许重复

LinkedHashMap是否有序

有序

LinkedHashMap是否线程安全

非线程安全

集合异同比较:https://blog.csdn.net/learningcoding/article/details/79983248

Collection接口继承树https://www.cnblogs.com/nayitian/p/3266090.html

import java.util.HashMap;
import java.util.Hashtable;
import java.util.LinkedHashMap;
import java.util.TreeMap;
import java.util.WeakHashMap;

public class Map
{
    public static void main(String[] args)
    {
        HashMap<Integer, Integer> map1 = new HashMap<Integer, Integer>();
        map1.put(3, 3);
        map1.put(1, 1);
        map1.put(6, 6);
        map1.put(6, 6);
        map1.put(2, 2);
        map1.put(5, 5);
        System.out.println(map1);// {1=1, 2=2, 3=3, 5=5, 6=6}

        TreeMap<Integer, Integer> map2 = new TreeMap<Integer, Integer>();
        map2.put(3, 3);
        map2.put(1, 1);
        map2.put(6, 6);
        map2.put(6, 6);
        map2.put(2, 2);
        map2.put(5, 5);
        System.out.println(map2);// {1=1, 2=2, 3=3, 5=5, 6=6}

        LinkedHashMap<Integer, Integer> map3 = new LinkedHashMap<Integer, Integer>();
        map3.put(3, 3);
        map3.put(1, 1);
        map3.put(6, 6);
        map3.put(6, 6);
        map3.put(2, 2);
        map3.put(5, 5);
        System.out.println(map3);// {3=3, 1=1, 6=6, 2=2, 5=5}

        Hashtable<Integer, Integer> map4 = new Hashtable<Integer, Integer>();
        map4.put(3, 3);
        map4.put(1, 1);
        map4.put(6, 6);
        map4.put(6, 6);
        map4.put(2, 2);
        map4.put(5, 5);
        System.out.println(map4);// {6=6, 5=5, 3=3, 2=2, 1=1}

        WeakHashMap<Integer, Integer> map5 = new WeakHashMap<Integer, Integer>();
        map5.put(3, 3);
        map5.put(1, 1);
        map5.put(6, 6);
        map5.put(6, 6);
        map5.put(2, 2);
        map5.put(5, 5);
        System.out.println(map5);// {6=6, 5=5, 3=3, 2=2, 1=1}
    }

}

 

3、ArrayList、LinkedList、Vector

ArrayList和LinkedList是顺序存储结构(基于动态数组的数据结构)和链式存储结构(双向链表的数据结构)的表在java语言中的实现 。

ArrayList提供了一种可增长数组的实现, ArrayList内部使用数组实现,优点是对于get和set操作调用花费常数时间,缺点是插入元素和删除元素会付出昂贵的代价。因为这个操作会导致后面的元素都要发生变动,除非操作发生在集合的末端。

LinkedList是一个双链表的结构,在设计这个这个表结构时,我们需要考虑提供3个类:1、MyLinkedList类本身,2、Node类,该类作为静态的内部类出现,包含一个节点上的数据,以及到前一个节点和后一个节点的链。3、LinkedListIterator类,也是一个私有类,实现Iterator接口,提供next().hannext().remove()方法。

LinkedList比ArrayList更占内存,因为LinkedList的节点除了存储数据,还存储了两个引用,一个指向前一个元素,一个指向后一个元素。

ArrayList 插入,删除数据慢
LinkedList, 插入,删除数据快
ArrayList是顺序结构,所以定位很快,指哪找哪。 就像电影院位置一样,有了电影票,一下就找到位置了。
LinkedList 是链表结构,就像手里的一串佛珠,要找出第99个佛珠,必须得一个一个的数过去,所以定位慢。

Vector 和 ArrayList 一样,都是继承自 AbstractList。它是 Stack 的父类。英文的意思是 “矢量”。

Vector 特点:1、底层由一个可以增长的数组组成。2、Vector 通过 capacity (容量) 和 capacityIncrement (增长数量) 来尽量少的占用空间。3、扩容时默认扩大两倍。4、最好在插入大量元素前增加 vector 容量,那样可以减少重新申请内存的次数。5、通过 iterator 和 lastIterator 获得的迭代器是 fail-fast 的。5、通过 elements 获得的老版迭代器 Enumeration 不是 fail-fast 的。6、同步类,每个方法前都有同步锁 synchronized。

Vector   ArrayList

共同点:

都是基于数组

都支持随机访问

默认容量都是 10

都有扩容机制

区别:

Vector 出生的比较早,JDK 1.0 就出生了,ArrayList JDK 1.2 才出来

Vector 比 ArrayList 多一种迭代器 Enumeration

Vector 是线程安全的,ArrayList 不是

Vector 默认扩容 2 倍,ArrayList 是 1.5

都实现了List接口(List接口继承了Collection接口)

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Stack;

public class ListTest
{
    public static void main(String[] args)
    {
        ArrayList<Integer> list1 = new ArrayList<Integer>();
        list1.add(3);
        list1.add(1);
        list1.add(2);
        System.out.println(list1);// [3, 1, 2]

        LinkedList<Integer> list2 = new LinkedList<Integer>();
        list2.add(3);
        list2.add(1);
        list2.add(2);
        System.out.println(list2);// [3, 1, 2]

        /*
         * Stack继承于Vector,意味着Vector拥有的属性和功能,Stack都拥有
         */
        Stack<Integer> stack = new Stack<Integer>();
        stack.push(2);// push将元素推入栈中
        stack.push(1);
        stack.push(3);
        System.out.println(stack.peek());// 3 peek()取出栈顶元素,不执行删除
        stack.pop();// 取出栈顶元素,并将该元素从栈中删除
        System.out.println(stack.peek());// 1 peek()取出栈顶元素,不执行删除
        System.out.println(stack.isEmpty());// false 是否为空
    }
}

 

HashCode方法

Java中的集合(Collection)有两类,一类是List,再有一类是Set。前者集合内的元素是有序的,元素可以重复;后者元素无序,但元素不可重复。要想保证元素不重复,可两个元素是否重复应该依据什么来判断呢?

     这就是Object.equals方法了。但是,如果每增加一个元素就检查一次,那么当元素很多时,后添加到集合中的元素比较的次数就非常多了。也就是说,如果集合中现在已经有1000个元素,那么第1001个元素加入集合时,它就要调用1000次equals方法。这显然会大大降低效率。   

     于是,Java采用了哈希表的原理。哈希算法也称为散列算法,是将数据依特定算法直接指定到一个地址上,hashCode方法实际上返回的就是对象存储的物理地址(实际可能并不是)。这样一来,当集合要添加新的元素时,先调用这个元素的hashCode方法,就一下子能定位到它应该放置的物理位置上。 如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;如果这个位置上已经有元素了, 就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址。 所以这里存在一个冲突解决的问题。这样一来实际调用equals方法的次数就大大降低了,几乎只需要一两次。

Java对于eqauls方法和hashCode方法是这样规定的:

1、如果两个对象相同,那么它们的hashCode值一定要相同;

2、如果两个对象的hashCode相同,它们并不一定相同。    

上面说的对象相同指的是用eqauls方法比较。

equals方法主要是用来判断从表面上看或者从内容上看,2个对象是不是相等。

hashcode这个方法是用来鉴定2个对象是否相等的。hashcode方法一般用户不会去调用。我们一般在覆盖equals的同时也要 覆盖hashcode,让他们的逻辑一致。

要从物理上判断2个对象是否相等,用==就可以了。

博客:https://www.cnblogs.com/wl0000-03/p/6019627.html

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值