单链表HashMap
- 新增结点
在p
结点后面增加一个s
结点,实现代码如下:
s.next = p.next;
p.next = s;
注意操作步骤,首先是将插入结点
s
的next
指针s.next
指向p
结点的下一个结点p.next
,再将p
结点的next
指针p.next
指向s
结点;如果按照我们正常的思维方式,是将p
结点的next
指针p.next
指向s
结点,再将s
结点的next
指针s.next
指向p
结点的下一个结点p.next
(最后这一句话中的p.next
是s
结点,结果就是s
结点的next
指针s.next
指向s
自己,显然是不正确的)
- 删除结点
删除p
结点后面那个结点,实现代码如下:
p.next = p.next.next;
将
p
结点的next
指针p.next
指向p
结点的下下个结点p.next.next
- 更改结点的值
p.data = new Object();
将
p
结点的数据域data
重新赋值
- 查询结点
while(p.next != l){
p = p.next;
}
从头结点开始遍历,遍历每一个结点失败,则结点后移,直至遍历出我们的
l
结点为止.
双链表LinkedList
- 新增结点
在p
结点后面增加一个s
结点,实现代码如下:
s.next = p.next;
p.next.prev = s;
s.prev = p;
p.next = s;
这里的操作步骤同单链表的新增结点,只是要注意每个结点增加了一个
prev
指针,用于指向前一个结点;所以在处理s
结点与p
结点下一个结点关系的同时也要处理prev
指针,处理p
结点与s
结点的关系的同时也要处理prev
指针
LinkedList在中间新增一个结点的源码:
void linkBefore(E e, Node<E> succ) {
//得到插入位置的前一个结点
final Node<E> pred = succ.prev;
//构建待插入结点的对象,里面包含了插入结点prev指针指向的结点,和next指针指向的结点
final Node<E> newNode = new Node<>(pred, e,
succ);
//将后面一个结点的prev指针指向待插入的结点
succ.prev = newNode;
//插入到表头,则插入的结点就是头结点;不是表头,则将前一个结点的next指针指向待插入的结点
if (pred == null)
first = newNode;
else
pred.next = newNode;
//链表长度加1
size++;
//更改次数加1
modCount++;
}
- 删除结点
删除p
结点,实现代码如下:
p.next.prev = p.prev;
p.prev.next = p.next;
这两行代码执行顺序不影响
LinkedList在删除一个结点的源码,首先调用node(int index)
方法从链表的头部/尾部开始遍历,找到我们要删除的结点,然后执行删除操作:
E unlink(Node<E> x) {
//找到删除结点中的数据域,最后会将其作为方法的返回值
final E element = x.item;
//找到删除结点的后一个结点
final Node<E> next = x.next;
//找到删除结点的前一个结点
final Node<E> prev = x.prev;
//判断是不是删除的链表中第一个结点
if (prev == null) {
//是则将当前结点赋值给fisrt
first = next;
} else {
//不是则将前一个结点的next指针指向删除结点下一个结点
prev.next = next;
//将删除结点中指向前一个结点的指针置空
x.prev = null;
}
//判断是不是删除的链表中最后一个结点
if (next == null) {
//是则将当前结点赋值给last
last = prev;
} else {
//不是则将下一个结点的prev指针指向删除结点的上一个结点
next.prev = prev;
//将删除结点中指向后一个结点的指针置空
x.next = null;
}
//结点的指针域数据置空
x.item = null;
//链表长度减1
size--;
//操作次数加1
modCount++;
//返回删除结点的指针域赋值出来的对象;(上面都将x.item = null,那么element是不是也为空了,难道是因为element前面的final修饰符起到了作用???测试了element非空,应该是element是重新创建的一个对象吧)
return element;
}
补充:
ArrayList
、LinkedList
、Vector
和Stack
是List
的4个实现类
面试问题
- ArrayList和LinkedList的区别?
ArrayList
存储空间是连续的,而LinkedList
存储空间是不连续的;ArrayList
允许随机的访问,尾部插入和删除方便,插入和删除效率低,长度固定;而LinkedList
允许随机的进行增删改,插入和删除效率高,长度可以随意的修改;
- 接口之间可以继承吗?接口和抽象类的区别?
- 可以的,比如
List
接口就继承自Collection
接口,而Collection
接口继承自Iterable
接口; - 接口是特殊的抽象类,接口中所有的方法都是抽象的(
public abstract
隐式声明),都需要重写,并且接口成员变量必须用public static final
修饰,接口不能有静态代码块和静态方法.
面试题:HashMap和HashTable区别,HashMap和HashSet的区别?
- HashMap和HashTable区别
HashTable
几乎等价于HashMap
只不过HashTable
中的方法是synchronized
修饰的,所以HashTable
是线程安全的,而HashMap
不是线程安全的,从效率上比较HashMap
更快.HashMap
中存储数据时key
和value
都可以为null
,而HashTable不能存储key
和value
为null
的值.
- 实现HashMap线程安全的两种方式
- 使用
ConcurrentHashMap
; - 使用
Collections.synchronizeMap(hashMap)
得到的HashMap
对象是线程安全的;
- HashMap和HashSet区别
HashMap
存储key
和value
键值对,而HashSet
存储对象.HashSet
中不能存储重复的对象.- 扩展:
HashSet
继承自HashMap
,所以底层还是用到了map
存储;但是HashSet
存储的集合元素是无序的哦.
代码举例:
//比较HashMap、HashTable和HashSet中存储是否可以为null,是否可以重复
HashMap<String, String> hashMap = new HashMap<>();
hashMap.put(null, null);
hashMap.put(null, null);
hashMap.put("name", "汤坤");
hashMap.put("name", "汤坤");
hashMap.put("name", "罗丹");
Hashtable<String, String> hashtable = new Hashtable<>();
hashtable.put("name", "王强");
hashtable.put("name", "檀庭兵");
hashtable.put("name", "朱海建");
hashtable.put("name", "汤坤");
HashSet<String> hashSet = new HashSet<>();
hashSet.add("王强");
hashSet.add("汤坤");
hashSet.add("罗丹");
hashSet.add("汤坤");
hashSet.add(null);
hashSet.add("檀庭兵");
System.out.println("hashMap是否可以存储重复,key和value都为null的值:" + hashMap.toString() + "\n" +
"hashtable是否可以存储重复,key和value都为null的值:" + hashtable.toString() + "\n" +
"hashSet是否可以存储重复,对象为null的值:" + hashSet.toString()+ "\n");
打印结果:
hashMap是否可以存储重复,key和value都为null的值:{null=null, name=罗丹}
hashtable是否可以存储重复,key和value都为null的值:{name=汤坤}
hashSet是否可以存储重复,对象为null的值:[null, 王强, 檀庭兵, 汤坤, 罗丹]
扩展知识:Java中>>和>>>的区别?
>>
是是带符号右移,转换成2进制右移时候,正数左边补0,负数左边补1>>>
是无符号右移,转换成2进制右移时,左边补0