java中集合的数据结构总结,以及使用场景

一、队列

队列接口指出,可以在头部删除,在尾部插入。收集对象的时候按照先进先出的规则检索对象。

队列有两种实现方式,一种是数组,一种是链表。

如果需要一个循环数组队列,就可以使用ArrayDeQueue类。如果需要链表对列,就直接使用LinkedList类,这个类实现了Queue接口。

循环数组是一个有界集合,即容量有限。如果程序中要收集的对象数量没有上限,就最好使用链表来实现。

二、双端队列

双端队列可以让人们有效地在头部和尾部同时添加或删除元素。不支持在队列中间添加元素。在JavaSE6中引入了Deque接口,并由ArrayDeque和LinkedList类实现。这两个类都提供了双端队列,而且在必要时可以增加队列长度。

三、优先级队列

优先级队列中的元素可以按照任意的顺序插入,却总是按照排序的顺序进行检索。也就是说,无论何时调用remove方法,总会获得当前优先级队列中最小的元素。然后优先级队列并没有对所有元素排序。而是使用了被称为堆的数据结构,堆是一个可以自我调整的二叉树,对树执行添加和删除操作,可以让最小的元素移动到根,而不必花费时间对元素进行排序。

与TreeSet一样,优先级队列即可以保存实现了Comparable接口的类对象,也可以保存在构造器中提供的Comparator对象

	public static void main(String[] args) {
		
		PriorityQueue<LocalDate> pq = new PriorityQueue<>();
		pq.add(LocalDate.of(1996, 12, 9));
		pq.add(LocalDate.of(1815, 12, 10));
		pq.add(LocalDate.of(1903, 12, 3));
		pq.add(LocalDate.of(1910, 6, 20));
		System.out.println("Iterating over elements");
		for (LocalDate localDate : pq) {
			System.out.println(localDate);
		}
	
		
		System.out.println("Removing elements");
		while(!pq.isEmpty())
			System.out.println(pq.remove());
			
	}
Iterating over elements
1815-12-10
1910-06-20
1903-12-03
1996-12-09
Removing elements
1815-12-10
1903-12-03
1910-06-20
1996-12-09

四、迭代器

迭代器初始位置在第一个元素之前,调用next()方法之后,迭代器的位置到了第一个元素和第二个元素之间,此时使用remove()会删除迭代器刚刚越过的那个元素,也就是第一个。注意remove()必须在next()之后使用,并且不能连续使用两次remove().

编译器简单地将"for each"循环翻译为带有迭代器的循环。

“for each”循环可以与任何实现了Iterable接口的对象一起工作。

一般来说 底层非数组结构的集合遍历用迭代器效率要优于for循环,当然for each 和 迭代器是一样的。

原因:Iterator 主要性能开销在next方法体,其一:对记录集进行检测,如果在迭代的过程中,记录集有被修改,会抛出异常;其二:next方法体内有try...catch方法体,这也会影响性能,JVM不能对try...catch块内的代码进行优化。
而for因为不管数据被中途修改,也不进行异常处理。

五、数组和链表对比

数组一旦声明大小固定,且如果在中间位置删除或添加一个元素,其后面的元素位置都会变动,效率很低。但是数组根据指定索引位查询数据很快,因为数组有下标。


链表的每一个节点存着自己的数据的同时还有这下一个节点的引用,双向链表还会存着上一个节点的引用。所以从链表中添加或删除元素很方便,只需要更新被删除或添加元素附近的链接。但链表遍历数据非常慢,假如数据很多的话,你需要找的元素位置比较靠中间,你调用get方法(),它会从头开始给你找。当然get(i)方法做了一定的优化,当索引位大于size()/2的时候会从后向前找。


六、散列集

就是hash开头的那些集合。

散列表会为每一个对象计算一个整数,称为散列码。散列码是由对象的实例域产生的一个整数。更准确的说,具有不同数据域的对象将产生不同的散列码。

如果是自定义类,就要负责实现这个类的hashCode方法。自己实现的hashCode方法应该与equals方法兼容,即如果a.equals(b)为true,a与b必须有相同的散列码。但反过来不一定成立。

散列表是数组和链表的结合。每一个数组元素被称为桶,每一个桶又存放着一个链表。我们通过对象的散列码与桶的总数求余数,余数代表放在第几个桶里面。如果桶里没有其他元素,就直接放进去。如果有其他元素(散列冲突)则需要与其他元素比较看是否有相同的元素,这桶里的其他元素就是以链表的形式存储的。


很明显,如果一个桶里的元素太多,会大大降低效率,所以我们可以指定一个初始桶数,通常将桶数设置为预计元素个数的75%-150%。最好将桶设置为一个素数,以防键的凝聚。标准类库使用的桶数是2的幂,默认值为16(为表提供的任何职都被将自动转换为2的下一个幂)。

有时候我们并无法预估将会有多少元素被装填,有可能最初估值过低。如果散列表太满,就需要再散列。再散列就需要一个更多桶的表,并将所有元素插入到这个新表中,然后丢弃原来的表。装填因子决定何时对三聊表进行再散列。例如装填因子为0.75(默认值),而表中超过75%的位置已经填入元素,这个表就会用双倍的桶数自动第进行再散列。对于大多数应用程序来说,装填因子为0.75是比较合理的。

综上所述可以看出,散列表特点为 无序、去重、查询快。若提高效率,需根据项目对初始桶数以及装填因子进行合理的设置

七、树集

树集是一个有序的集合,分为自然排序实现Comparable接口,和定制排序new集合对象的时候穿一个Comparator。

同时自定义类放入集合时需要重写hashcode和equals方法。

将一个元素添加到树中要比添加到散列表中慢。但是,与检查数组或链表中的重复元素相比还是快很多。

八、链接散列(LinkedHashSet、LinkedHashMap)

我们刚说过,散列是无序的,但是链接散列是在无序的散列表基础上实现有序,那么它是如何做到的呢?

当我们插入 第一个元素的时候,会根据元素的哈希值%桶总数 的余数 来判断放进那个桶,也就是桶会指向这个节点。然后当插入第二个元素的时候,假设放进了另一个桶,然后另一个桶指向这第二个节点。 到此为止是散列表。 重点:此时,会在第一个节也会存放第二个节点的引用,同时第二个节点也存放第一个节点的引用,即一个桶内的节点之间是单向链表。但插入的时候,会按照插入的顺序把相邻插入的两个节点之间用双向链表连接。



总结:

一般Linked开头的都是链表。Array开头的都是数组。Tree开头的就是二叉树。Hash开头的就是散列表。

经常增添、删除,且还需要检索时为插入顺序用Linked 和 linkedhash

经常增添、删除用hash

如果要自动排序 且 去重 用 Tree    

经常需要遍历查数据,用Array,遍历ArrayList用for循环效率比迭代器高。




  • 5
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值