java之单链表
单链表是一种物理存储单元上非连续、非顺序的存储结构。
可以看出, Entry 就是数组中的元素,每个 Map.Entry 其实就是一个 key-value 对,它持有一个指向下一个元素的引用,这就构成了链表。
从上面的源代码中可以看出:从HashMap中get元素时,首先计算key的hashCode,找到数组中对应位置的某一元素,然后通过key的equals方法在对应位置的链表中找到需要的元素。
单链表是由那几个部分组成的呢?
是由N个节点组成的
每一个节点分为两部分:
1.数据域
2.指针域
数据域用来存储数据,指针域用来链接各个节点。废话不多说,直接上代码!
节点的代码如下:
public class Node<E> {
private E e;// 数据域
private Node<E> next;// 引用域
public Node() {
}
public Node(E e) {
this.e = e;
}
public E getE() {
return e;
}
public void setE(E e) {
this.e = e;
}
public Node<E> getNext() {
return next;
}
public void setNext(Node<E> next) {
this.next = next;
}
单链表的代码如下:
public class MyLinkedList<E> {
//声明头节点
private Node<E> root;
private int size;//声明单链表中存储的节点数
public MyLinkedList(){
root = new Node<E>();//实例化头节点
}
/**
* 向链表中添加元素的方法
* @param e要添加的元素
*/
public void add(E e){
//根据e实例化了一个新的节点对象
Node<E> node = new Node<E>(e);
//获取root的下一个节点
Node<E> tnode = root.getNext();
root.setNext(node);//将新的节点作为root的下一个节点
node.setNext(tnode);//将root原来的下一个节点作为新增加节点的下一个节点
size++;//记录添加的节点数
}
/**
* 删除指定索引位置的元素
* @param index索引位置
* @return 返回删除的元素
*/
public E remove(int index){
if(index <= 0 || index > size)
return null;
//获取要删除节点的前一个节点
Node<E> node = select(index-1);
//获取要删除的节点
Node<E> dNode = node.getNext();
//获取要删除节点的后一个节点
Node<E> nNode = dNode.getNext();
//先建立删除节点的前一个节点和删除节点的后一个节点的关系
node.setNext(nNode);
//清除dNode的下一个节点
dNode.setNext(null);
size--;//计数器减一
return dNode.getE();//返回删除节点中的数据域
}
/**
* 获取指定索引位置的元素
* @param index索引位置
* @return 返回节点中的数据域
*/
public E get(int index){
if(index <= 0 || index > size)
return null;
//查找指定索引位置的节点对象
Node<E> node = select(index);
//获取节点中的数据域元素并返回
return node.getE();
}
/**
* 获取单链表中存储的元素总数
* @return 返回size属性
*/
public int size(){
return size;
}
/**
* 获取指定索引位置的节点对象
* @param index索引位置
* @return 返回获取到的节点对象
*/
private Node<E> select(int index){
Node<E> node = root.getNext();//将头节点的下一个节点赋给node
if(index==1)//如果index是1表示是头结点的下一个节点
return node;//直接返回node
for(int i=1;i<index;i++){
node = node.getNext();//获取node的下一个节点
}
return node;
}
}
java之HashMap
1 : HashMap概述
HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作,并允许使用null值和null键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。
2 : HashMap的数据结构
在java编程语言中,最基本的结构就是两种,一个是数组,另外一个是模拟指针(引用),所有的数据结构都可以用这两个基本结构来构造的,HashMap也不例外。HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。
从上图中可以看出,HashMap底层就是一个数组结构,数组中的每一项又是一个链表。当新建一个HashMap的时候,就会初始化一个数组。
源码如下:
/**
* The table, resized as necessary. Length MUST Always be a power of two.
*/
transient Entry[] table;
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
final int hash;
……
}
可以看出, Entry 就是数组中的元素,每个 Map.Entry 其实就是一个 key-value 对,它持有一个指向下一个元素的引用,这就构成了链表。
3. HashMap的存取实现:
存储:
public V put(K key, V value) {
// HashMap允许存放null键和null值。
// 当key为null时,调用putForNullKey方法,将value放置在数组第一个位置。
if (key == null)
return putForNullKey(value);
// 根据key的keyCode重新计算hash值。
int hash = hash(key.hashCode());
// 搜索指定hash值在对应table中的索引。
int i = indexFor(hash, table.length);
// 如果 i 索引处的 Entry 不为 null,通过循环不断遍历 e 元素的下一个元素。
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
// 如果i索引处的Entry为null,表明此处还没有Entry。
modCount++;
// 将key、value添加到i索引处。
addEntry(hash, key, value, i);
return null;
}
从上面的源代码中可以看出:当我们往HashMap中put元素的时候,先根据key的hashCode重新计算hash值,根据hash值得到这个元素在数组中的位置(即下标),如果数组该位置上已经存放有其他元素了,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放在链尾。如果数组该位置上没有元素,就直接将该元素放到此数组中的该位置上。
2) 读取:
public V get(Object key) {
if (key == null)
return getForNullKey();
int hash = hash(key.hashCode());
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
return e.value;
}
return null;
}
从上面的源代码中可以看出:从HashMap中get元素时,首先计算key的hashCode,找到数组中对应位置的某一元素,然后通过key的equals方法在对应位置的链表中找到需要的元素。
归纳起来简单地说:HashMap 在底层将 key-value 当成一个整体进行处理,这个整体就是一个 Entry 对象。HashMap 底层采用一个 Entry[] 数组来保存所有的 key-value 对,当需要存储一个 Entry 对象时,会根据hash算法来决定其在数组中的存储位置,在根据equals方法决定其在该数组位置上的链表中的存储位置;当需要取出一个Entry时,也会根据hash算法找到其在数组中的存储位置,再根据equals方法从该位置上的链表中取出该Entry。