程序员罕用的IDEA插件:https://github.com/silently9527/ToolsetIdeaPlugin微信公众号:贝塔学Java
前言
JAVA中的Map次要就是将一个键和一个值分割起来。尽管JAVA中曾经提供了很多Map的实现,为了学习并把握罕用的数据结构,从本篇开始我将本人实现Map的性能,本篇次要是通过数组和链表两种形式实现,之后提供二叉树,红黑树,散列表的版本实现。通过本人手写各个版本的Map实现,把握每种数据结构的优缺点,能够在理论的工作中依据须要抉择适宜的Map。
Map API的定义
在开始之前,咱们须要先定义出Map的接口定义,后续的版本都会基于此接口实现
public interface Map<K, V> { void put(K key, V value); V get(K key); void delete(K key); int size(); Iterable<K> keys(); default boolean contains(K key) { return get(key) != null; } default boolean isEmpty() { return size() == 0; }}
这个接口是最简略的一个Map定义,置信这些办法对于java程序员来说不会生疏;
基于链表实现Map
- 基于链表实现首先咱们须要定义一个Node节点,示意咱们须要存储的key、vlaue
class Node { K key; V value; Node next; public Node(K key, V value, Node next) { this.key = key; this.value = value; this.next = next; }}
- get办法的实现思路是遍历链表,而后比拟每个Node中的key是否相等,如果相等就返回value,否则返回null
@Overridepublic V get(K key) { return searchNode(key).map(node -> node.value).orElse(null);}public Optional<Node> searchNode(K key) { for (Node node = root; node != null; node = node.next) { if (node.key.equals(key)) { return Optional.of(node); } } return Optional.empty();}
- put办法的实现思路也是遍历链表,而后比拟每个Node的key值是否相等,如果相等那么笼罩掉value,如果未查找到有key相等的node,那么就新建一个Node放到链表的结尾
@Overridepublic void put(K key, V value) { Optional<Node> optionalNode = searchNode(key); if (optionalNode.isPresent()) { optionalNode.get().value = value; return; } this.root = new Node(key, value, root);}
- delete办法实现同样也须要遍历链表,因为咱们的是单向链表,删除某个节点有两种思路,第一种,在遍历链表的时候记录下以后节点的上一个节点,把上一个节点的next指向以后节点next;第二种,当遍历到须要删除的节点时,把须要删除节点的next的key、value齐全复制到须要删除的节点,把next指针指向next.next,比方:first - > A -> B -> C -> D -> E -> F -> G -> NULL,要删除 C 节点,就把D节点齐全复制到c中,而后C -> E,变相删除了C
@Overridepublic void delete(K key) {// 第一种实现:// for (Node node = first, preNode = null; node != null; preNode = node, node = node.next) {// if (node.key.equals(key)) {// if (Objects.isNull(preNode)) {// first = first.next;// } else {// preNode.next = node.next;// }// }// }// 第二中实现: for (Node node = first; node != null; node = node.next) { if (node.key.equals(key)) { Node next = node.next; node.key = next.key; node.value =next.value; node.next = next.next; } }}
剖析下面基于链表实现的map,每次的put、get、delete都须要遍历整个链表,十分的低效,无奈解决大量的数据,工夫复杂度为O(N)
基于数组实现Map
基于链表的实现十分低效,因为每次操作都须要遍历链表,如果咱们的数据是有序的,那么查找的时候咱们能够应用二分查找法,那么get办法会放慢很多
为了体现出咱们的Map是有序的,咱们须要从新定义一个有序的Map
public interface SortedMap<K extends Comparable<K>, V> extends Map<K, V> { int rank(K key);}
该定义要求key必须实现接口Comparable
,rank办法如果key值存在就返回对应在数组中的下标,如果不存在就返回小于key键的数量
- 在基于数组的实现中,咱们会定义两个数组变量分部寄存keys、values;
- rank办法的实现:因为咱们整个数组都是有序的,咱们能够二分查找法(能够查看《老哥是时候来温习下数据结构与算法了》),如果存在就返回所在数组的下表,如果不存在就返回0
@Overridepublic int rank(K key) { int lo = 0, hi = size - 1; while (lo <= hi) { int mid = (hi - lo) / 2 + lo; int compare = key.compareTo(keys[mid]); if (compare > 0) { lo = mid + 1; } else if (compare < 0) { hi = mid - 1; } else { return mid; } } return lo;}
- get办法实现:基于rank办法,判断返回的keys[index]与key进行比拟,如果相等返回values[index],不相等就返回null
@Overridepublic V get(K key) { int index = this.rank(key); if (index < size && key.compareTo(keys[index]) == 0) { return values[index]; } return null;}
- put办法实现:基于rank办法,判断返回的keys[index]与key进行比拟,如果相等间接批改values[index]的值,如果不相等示意不存在该key,须要插入并且挪动数组
@Overridepublic void put(K key, V value) { int index = this.rank(key); if (index < size && key.compareTo(keys[index]) == 0) { values[index] = value; return; } for (int j = size; j > index; j--) { this.keys[j] = this.keys[j--]; this.values[j] = this.values[j--]; } keys[index] = key; values[index] = value; size++;}
- delete办法实现:通过rank办法判断该key是否存在,如果不存在就间接返回,如果存在须要挪动数组
@Overridepublic void delete(K key) { int index = this.rank(key); if (Objects.isNull(keys[index]) || key.compareTo(keys[index]) != 0) { return; } for (int j = index; j < size - 1; j++) { keys[j] = keys[j + 1]; values[j] = values[j + 1]; } keys[size - 1] = null; values[size - 1] = null; size--;}
基于数组实现的Map,尽管get办法采纳的二分查找法,很快O(logN),然而在解决大量数据的状况下效率仍然很低,因为put办法还是太慢;下篇咱们将基于二叉树来实现Map,持续改良晋升效率
文中所有源码已放入到了github仓库https://github.com/silently9527/JavaCore
最初(点关注,不迷路)
文中或者会存在或多或少的有余、谬误之处,有倡议或者意见也十分欢送大家在评论交换。
最初,写作不易,请不要白嫖我哟,心愿敌人们能够点赞评论关注三连,因为这些就是我分享的全副能源起源????