Java 数据结构与算法

这篇博客也是一个大坑,需要慢慢填。

先说数据结构吧。java常用的数据结构有Array,List,Map,Set等。

除了数组以外,其他集合类都是不必在初始化时候指定大小的,仿佛是一个如意金箍棒,可大可小随心所欲。

数组是公认最快的数据结构。为什么快呢?因为它是一块连续的内存区域,在初始化时记录了这块区域的起始位置,根据元素个数和类型,可以精确每个元素所在的内存区域。

我们知道List的一种实现ArrayList底层就是数组。ArrayList也具备了数组的优点,访问快。至于为什么它可大可小,看了源码的同学就知道了,实际上ArrayList初始化的时候内部维护了一个长度为10的数组,已用空间超过当前容量时,就会创建一个长度是当前1.5倍的新数组(new_size=old_size+old_size>>1),再把老数组的内容复制过来。因此在使用时,如果知道ArrayList的大致容量,最好直接指定容量一次性创建出来,避免不停扩容带来的不必要的系统开销。使用ArrayList最好就不要做删除插入操作了,因为除非操作尾部元素,否则都会涉及到其他位置元素的移动复制,效率就大大下降了。

Map,常用的是hashmap。大家都知道hashmap访问速度快,时间复杂度是O(1),却不知道为什么快。其实它底层也是一个数组。瓦特?hashmap内部维护了一个初始大小为16的数组,数组元素是一个内部类定义的entry(有点眼熟,迭代的时候就是迭代这个玩意儿)entry包含了k,v,next,hash.因为是k-v结构,entry在数组中的位置,是由key的hashcode决定的(使用hash值与容量-1进行按位与运算。2的N次方-1各位都是1。只要hash算法合适,使得hash值均匀分布,最终的映射就是均匀的。)。这样在读取时可以根据hashcode很快找到数组下标。那么如果不巧两个key的hash值一样了呢(专业点说就是hash发生碰撞,先判断key是否碰撞,在判断key的hashcode是否碰撞),这时上面的entry结构就起作用了:entry里面维护了一个next变量,实际上是为了构建链表做准备的。如果两个hash值一样,那么新来的entry会占据数组的下标位置,然后把entry.next指向之前的entry,即插入链表的头部,俗称头插法。这样,相同的hashcode的元素自己构成一个链表,在访问时先定位到头元素,在遍历链表进行查找,一般来说损失的性能可以忽略不计。

Set,常用的是HashSet。Map的底层是数组,那么Set呢?Set就更加过分了。它的底层竟然是一个Map!纳尼?因为Map的键是不能重复的,Set里的元素也是不能重复,这两个特性是相同的,所以Set里面就是一个Map,因为只用到key,不需要value,为了降低value占用不必要的内存,Set中的value统一用一个final new Object()来填充。

这么看下来,就不难得出一个结论,单论查找,还是数组最快。这就是为什么好多算法题里,都把问题最终归结到对数组的排序上:首先从硬件上我们已经找到最快的办法了,剩下的就是软件算法上的优化了。

其实高级语言中各种数据结构,不论有多么复杂,在内存中只有两种形式:或者挨着存放,或者分开存放。挨着存放的就是:数组,分开存放的就是:链表。数组的优势是查找快,删除修改慢;链表则刚好相反。

引申而来的就是为了兼顾两个特性,衍生了一些其他的数据结构,比如二叉树,B树等。



未完待续。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值