Java中集合接口和类的框架(部分常用的)
可变数组
vector数组源码分析(最古老的数组集合,线程安全)
1 addAll可能引起的扩容的分析
(1)数组长度扩为2倍或者 数组长度 = 数组长度 + 自定义扩容大大小;
(2)扩容后仍然放不下的情况
(3)扩容后超过数组最大长度的情况
ArrayList数组源码分析
1 在jdk1.7及其之后调用构造函数(无参的)时只创建了一个长度为0的数组,这样更加节省空间,在1.6以前是默认创建了一个长度为10的数组。
2 扩容的大小
默认是扩大为原来的1.5倍
3 调用替换方法时,会返回被替换调的元素。
可变数组的实现(仿照ArrayList)
问题
1 为什么不使用E[]声明数组?
使用E声明数组时,不能初始化数组,只能使用Object。
2 如何扩容?
all = Arrays.copyOf(all, all.length + (all.length >> 1));
一定要赋值,否则相当于没有操作。
3 插入位置的合理范围?
[0,total]
4 如何移动元素?
System.arraycopy(all, index + 1, all, index, total - index - 1);
5 添加、删除元素后一定要修改total数量。
6 想要支持foreach循环?
必须实现iterable接口,需要实现它的方法Iterator,这个方法返回迭代器,我们需要编写一个内部类Itr,来实现这个迭代器Iterator,实现它的方法hasnext和next。
这样做有什么好处?
(1)高内聚
(2)Itr时类的内部类才能访问这个类的私有成员
7 比较元素
(1)“==”和“equals”的区别?
==比较的是两个元素的值和地址,equals是由元素的运行时类型的类规定的,更符合要求。
(2)需要注意的是?
.equals的点前边的对象不能为空,否则会报空指针异常。因此需要将数据分为两种情况来比较。
8 修改和删除的合理范围?
[0,total-1]
9 删除元素怎么做?这样做有什么好处?
all[--total] = null;
不写的话,这个位置的地址指向元素,这个元素就迟迟得不到回收,就是说哪怕再数组中删除该元素了,只要不再添加新元素覆盖它,那么就会有地址一直指向它。
10 数组中存储的null是什么?
null表示没有任何对象,null默认是0.java执行程序判断all[i] = null;就不再到堆中去寻找对象。
package com.dyy.myarraylist;
import java.util.Arrays;
import java.util.Iterator;
public class MyArrayList<E> implements Iterable<E> {
/*初始化时默认的数组*/
private Object[] all = new Object[INIT_SIZE];
/*可变数组中存储元素的个数*/
private int total;
/*初始化时数组默认的长度(自定义的)*/
private static int INIT_SIZE = 5;
/*判断是否需要扩容,如果需要扩容再进行扩容*/
public void expandCapacity() {
if (total + 1 > all.length) {
all = Arrays.copyOf(all, all.length + (all.length >> 1));
}
}
/**
* @param value
*/
public void add(E value) {
/*判断是否扩容,如果需要扩容,尝试进行扩容*/
expandCapacity();
/*在最后一个元素的下标的后一个位置填入add的元素,元素个数total加一*/
all[total++] = value;
}
/**
* @param index 下标
* @return 返回下标存储地址指向的元素
*/
public E get(int index) {
/*判断下标index是否在合理范围内,符合要求,返回元素*/
if (index >= 0 && index < total) {
return (E) all[index];
} else {
/*不合理,抛出异常*/
throw new ArrayIndexOutOfBoundsException("数组下标不在合理范围");
}
}
/**
* @param value 要寻找的元素
* @return 返回找到第一个元素的下标
*/
public int IndexOf(E value) {
/*判断这个元素是不是空值,
如果是null就不能使用.equals方法了
要另外进行处理,此处不适用==,因为==比较的是地址值,
对于一些对象来说,按照它的运行时类型中重写的equals方法更合适*/
if (value == null) {
for (int i = 0; i < total; i++) {
if (all[i] == null) {
return i;
}
}
/*元素非null时*/
} else {
for (int i = 0; i < total; i++) {
/*找到了就返回它的下标*/
if (all[i].equals(value)) {
return i;
}
}
}
/*如果遍历结束都没有找到这个元素,那么*/
return -1;
}
/**
* @return 返回数组中的元素个数
*/
public int size() {
return total;
}
/**
* @param index 删掉下标为index的元素
*/
public void remove(int index) {
/*删除末尾元素,不必移动元素,直接最后一个元素置为空就可以,数组长度减一*/
if (index == total - 1) {
all[--total] = null;
/*如果在合理范围内,index之后的元素整体向前移动一位,
*我们使用的是System.arraycopy(all, index + 1, all, index, total - index - 1);
* 它的意思是从index+1开始长度为total-index-1的长度的元素保持相对顺序移动到index上
* 此时最后一位元素需要抛弃,我们将这个元素置为空*/
} else if (index >= 0 && index < total - 1) {
System.arraycopy(all, index + 1, all, index, total - index - 1);
all[--total] = null;
} else {
throw new ArrayIndexOutOfBoundsException("数组下标不在合理范围");
}
}
/**
* @param data 删掉下标从小到大第一个数据值为data的一项
*/
public void remove(E data) {
/*判断元素是否在数组中*/
if (IndexOf(data) != -1) {
/*data是特殊值null的情况下*/
if (data == null) {
for (int i = 0; i < total; i++) {
if (all[i] == null) {
if (i != total - 1) {
System.arraycopy(all, i + 1, all, i, total - i - 1);
}
all[--total] = null;
}
}
/*非null*/
} else {
for (int i = 0; i < total; i++) {
if (data.equals(all[i])) {
if (i != total - 1) {
System.arraycopy(all, i + 1, all, i, total - i - 1);
}
all[--total] = null;
}
}
}
}
}
/**
* @param loc 替换位置的下标
* @param value 替换的新值
*/
public void set(int loc, E value) {
if (loc >= 0 && loc < total) {
all[loc] = value;
} else {
throw new ArrayIndexOutOfBoundsException("数组下标越界");
}
}
public void set(E old, E NewValue) {
int loc = IndexOf(old);
if (loc != -1) {
all[loc] = NewValue;
}
}
@Override
public Iterator<E> iterator() {
return new Itr();
}
private class Itr implements Iterator {
int cur = 0;
@Override
public boolean hasNext() {
return (cur < total);
}
@Override
public Object next() {
return (E) all[cur++];
}
}
}
可变数组的测试
package com.dyy.myarraylist;
public class MyArrayListTest {
public static void main(String[] args) {
MyArrayList<String> my = new MyArrayList();
my.add("hello");
my.add("world");
my.add("java");
my.add("morning");
my.add("evening");
my.add("afternoon");
my.add(null);
my.add(null);
System.out.println("添加元素");
for (String s : my) {
System.out.print(s + ",");
}
System.out.println("\n----------------------------");
System.out.println("数组长度:");
System.out.println(my.size());
System.out.println("------------------------------");
my.remove(0);
System.out.println("删除下标为0的元素");
for (String s : my) {
System.out.print(s + ",");
}
System.out.println("\n------------------------------");
my.remove("world");
System.out.println("删除内容为world的元素");
for (String s : my) {
System.out.print(s + ",");
}
System.out.println("\n------------------------------");
my.remove(null);
System.out.println("删除null的元素");
for (String s : my) {
System.out.print(s + ",");
}
System.out.println("\n------------------------------");
System.out.println("morning的下标:" + my.IndexOf("morning"));
System.out.println("--------------------------------");
my.set("java", "JAVA");
System.out.println("替换java为JAVA");
for (String s : my) {
System.out.print(s + ",");
}
}
}
/*控制台输出结果
添加元素
hello,world,java,morning,evening,afternoon,null,null,
----------------------------
数组长度:
8
------------------------------
删除下标为0的元素
world,java,morning,evening,afternoon,null,null,
------------------------------
删除内容为world的元素
java,morning,evening,afternoon,null,
------------------------------
删除null的元素
java,morning,evening,
------------------------------
morning的下标:1
--------------------------------
替换java为JAVA
JAVA,morning,evening,*/
链式结构
单链表的实现
问题
1 为什么使用内部类表示结点?
单链表的结点类型,只符合该类的要求,而在别的地方用不到(高内聚思想)
2 每次发生删除和插入的时候,操作成功一定要更新total
3 使用内部类时有什么注意的?
如果内部类加了static,那么外部类的<T><E>等就不能直接使用,必须要重新声明
package com.dyy.mylinkedlist;
import java.util.Iterator;
public class MyLinkedList<E> implements Iterable<E> {
/*单链表头结点*/
private Node head;
/*单链表中结点的个数*/
private int total;
/**
* 表示链表的结点
* @param <E>
*/
private class Node<E> {
E data;
Node next;
public Node(E data, Node next) {
this.data = data;
this.next = next;
}
}
/**
* @param value 添加的元素的值,默认添加在链表末尾
*/
public void add(E value) {
Node node = head;
Node newNode = new Node(value, null);
/*链表为空时,将新结点作为头结点*/
if (head == null) {
head = newNode;
} else {
/*找到最后一个元素,设置它的后继指向新结点*/
while (node.next != null) {
node = node.next;
}
node.next = newNode;
}
/*总个数加一*/
total++;
}
/**
* @return 返回链表中元素的个数
*/
public int size() {
return total;
}
/**
* @param value 要寻找的结点的值
* @return 返回这个结点在链表中的虚拟地址(下标从0开始),如果找不到返回-1
*/
public int IndexOf(E value) {
Node node = head;
int cur = 0;
if (node != null) {
/*如果要找的值恰好为null*/
if (value == null) {
while (node != null) {
/*找到这个元素就退出,因为cur每循环一次就+1,所以就是这个元素的下标*/
if (node.data == null) {
return cur;
}
node = node.next;
cur++;
}
/*如果是普通值*/
} else {
while (node != null) {
if (value.equals(node.data)) {
return cur;
}
node = node.next;
cur++;
}
}
}
/*找不到的话,我们使它返回-1*/
return -1;
}
/**
* @param value 要删除的结点的值,只会删除第一个遍历到符合要求的结点,没有的话就不进行任何操作
*/
public void remove(E value) {
/*遍历结点*/
Node node = head;
/*待删除结点的前一个结点*/
Node prev = head;
/*待删除结点*/
Node del = null;
/*判断这个结点在不在链表中*/
if (IndexOf(value) != -1) {
/*遍历查找删除结点和它的前一个结点*/
while (node != null) {
if (node.data == value) {
del = node;
break;
}
prev = node;
node = node.next;
}
/*如果是头结点的话*/
if (node == head) {
head = head.next;
}
/*如果找到了被删除的结点*/
if (node != null) {
prev.next = del.next;
/*将被删除的结点所有成员变量置为空,使虚拟机回收资源*/
del.data = null;
del.next = null;
}
total--;
}
}
/**
* @param old 要被替换掉的结点的值
* @param NewValue 即将置入的新值
* 找到第一个data==old的结点,将它的值置为NewValue,如果找不到就不进行操作。
*/
public void set(E old, E NewValue) {
/*判断是否存在*/
if (IndexOf(old) != -1) {
Node node = head;
/*这个被替换结点数据项是null时*/
if(old==null){
while (node != null) {
if (node.data == null) {
node.data = NewValue;
return;
}
node = node.next;
}
/*这个被替换结点数据项非空时*/
}else{
while (node != null) {
if (old.equals(node.data)) {
node.data = NewValue;
return;
}
node = node.next;
}
}
}
}
/**
* @param loc 被删除的结点在链表中的位置(从0开始)
*/
public void remove(int loc) {
/*判断位置是否合理*/
if (loc < total && loc >= 0) {
/*删除的是头结点*/
if (loc == 0) {
head = head.next;
total--;
return;
}
Node node = head;
Node prev = null;
/*遍历,找目标结点和它的前一个结点*/
while (loc > 0) {
prev = node;
node = node.next;
loc--;
}
/*进行删除*/
prev.next = node.next;
node.data = null;
node.next = null;
total--;
/*不合理抛出异常*/
} else {
throw new ArrayIndexOutOfBoundsException("下标越界,超过合理范围");
}
}
/**
* 实现Iterable接口中的iterator接口,使这个链表能够使用foreach循环
*
* @return
*/
@Override
public Iterator iterator() {
return new Itr();
}
/**
* 实现Iterator,供iterator方法调用
*/
private class Itr implements Iterator {
Node node = head;
@Override
public boolean hasNext() {
return node != null;
}
@Override
public Object next() {
E data = (E) node.data;
node = node.next;
return data;
}
}
}
单链表的测试
package com.dyy.mylinkedlist;
public class MyLinkedTest {
public static void main(String[] args) {
MyLinkedList<String> my = new MyLinkedList<>();
my.add("hello");
my.add("world");
my.add("2021");
my.add("7/19");
my.add("18:52");
my.add("java");
System.out.println("添加完成!");
System.out.println("遍历输出链表中的数据:");
for (String s : my) {
System.out.print(s + ",");
}
System.out.println();
System.out.println("----------------------------------------------");
System.out.println("java这个字符串的下标:" + my.IndexOf("java"));
System.out.println("----------------------------------------------");
System.out.println("链表的长度:" + my.size());
System.out.println("----------------------------------------------");
my.set("7/19", "七月十九号");
my.set("java", "JAVA");
System.out.println("替换7/19为七月十九号");
System.out.println("替换java为JAVA");
System.out.println("替换后的结果是:");
for (String s : my) {
System.out.print(s + ",");
}
System.out.println("\n----------------------------------------------");
System.out.println("删除2021");
my.remove("2021");
System.out.println("删除hello");
my.remove("hello");
System.out.println("删除结果展示:");
for (String s : my) {
System.out.print(s + ",");
}
System.out.println("\n----------------------------------------------");
System.out.println("删除某个位置上的结点");
System.out.println("当前链表的长度" + my.size() + "\n");
System.out.println("删除位置0上的元素:");
my.remove(0);
System.out.println("删除结点后的链表" + "长度" + my.size());
for (String s : my) {
System.out.print(s + ",");
}
System.out.println("\n\n删除位置1上的元素:");
my.remove(1);
System.out.println("删除结点后的链表" + "长度" + my.size());
for (String s : my) {
System.out.print(s + ",");
}
// System.out.println("\n删除位置7上的元素:");
// my.remove(7);抛出下标越界异常
System.out.println("删除结点后的链表");
for (String s : my) {
System.out.print(s + ",");
}
System.out.println("\n---------------------------------------------");
System.out.println("添加元素null");
my.add(null);
for (String s : my) {
System.out.print(s + ",");
}
System.out.println("\n-----------------------------------------------");
System.out.println("删除null结点");
my.remove(null);
System.out.println("展示");
for (String s : my) {
System.out.print(s + ",");
}
System.out.println("\n-----------------------------------------------");
System.out.println("添加元素null");
my.add(null);
for (String s : my) {
System.out.print(s + ",");
}
System.out.println("\n----------------------------------------------");
my.set(null,"NULL");
System.out.println("将null替换为字符串");
System.out.println("展示");
for (String s : my) {
System.out.print(s + ",");
}
}
}
/* 控制台输出结果
添加完成!
遍历输出链表中的数据:
hello,world,2021,7/19,18:52,java,
----------------------------------------------
java这个字符串的下标:5
----------------------------------------------
链表的长度:6
----------------------------------------------
替换7/19为七月十九号
替换java为JAVA
替换后的结果是:
hello,world,2021,七月十九号,18:52,JAVA,
----------------------------------------------
删除2021
删除hello
删除结果展示:
world,七月十九号,18:52,JAVA,
----------------------------------------------
删除某个位置上的结点
当前链表的长度4
删除位置0上的元素:
删除结点后的链表长度3
七月十九号,18:52,JAVA,
删除位置1上的元素:
删除结点后的链表长度2
七月十九号,JAVA,删除结点后的链表
七月十九号,JAVA,
---------------------------------------------
添加元素null
七月十九号,JAVA,null,
-----------------------------------------------
删除null结点
展示
七月十九号,JAVA,
-----------------------------------------------
添加元素null
七月十九号,JAVA,null,
----------------------------------------------
将null替换为字符串
展示
七月十九号,JAVA,NULL,
Process finished with exit code 0
*/
双向链表的实现
问题:
删除的结点是头结点或尾结点时,应该如何处理?如何判断删除的结点是头/尾结点?
判断结点的前驱,前驱是空的话,那么这个结点是头结点。如果是头结点,那么就不再设置前驱的后继,然后将这个结点的下一个结点设置为头节点。
判断结点的后继,后继是空的话,那么这个结点是尾结点。如果是尾结点,那么就不再设置后继的前驱,然后将这个结点的上一个结点设置为尾节点。
package com.dyy.doublelinkedlist;
import java.util.Iterator;
public class DoubleLinkedList<E> implements Iterable<E> {
/*双向链表头结点*/
private Node first;
/*双向链表尾结点*/
private Node last;
/*链表中元素的个数*/
private int total;
/*双向链表的结点内部类*/
private class Node {
E data;
Node next;
Node prev;
public Node(E data, Node next, Node prev) {
this.data = data;
this.next = next;
this.prev = prev;
}
}
/*支持iterator*/
@Override
public Iterator iterator() {
return new Itr();
}
private class Itr implements Iterator {
Node node = first;
@Override
public boolean hasNext() {
return node != null;
}
@Override
public Object next() {
E value = node.data;
node = node.next;
return value;
}
}
/**
* @param value 目标结点的data值
* @return 返回第一个值是value的虚拟下标(默认从0开始)
*/
public int IndexOf(E value) {
int cur = 0;
Node node = first;
/*如果查找的值是null特殊值*/
if (value == null) {
while (node != null) {
if (node.data == null) {
return cur;
}
node = node.next;
cur++;
}
/*正常值调用equals比较*/
} else {
while (node != null) {
if (value.equals(node.data)) {
return cur;
}
node = node.next;
cur++;
}
}
return -1;
}
/**
* @param value 添加一个结点,这个结点的值是value,默认添加在末端
*/
public void add(E value) {
Node node = new Node(value, null, null);
/*判断链表是否为空,如果为空,将这个新结点作为头尾结点*/
if (first == null) {
first = node;
last = node;
} else {
last.next = node;
node.prev = last;
last = node;
}
total++;
}
/**
* @return 返回数组长度
*/
public int size() {
return total;
}
/**
* @param value 被删除结点的值
* 从头到尾进行遍历,遇到第一个值为value的结点就删掉,如果没有找到,那就不进行任何操作
*/
public void remove(E value) {
int loc = IndexOf(value);
/*判断是否存在,如果存在那么find一定不为null(一定能找到这个元素),不用再进行判断*/
if (loc != -1) {
Node find = null;
/*如果目标结点在前半段,我们从头结点开始遍历,提高效率*/
if (loc <= (size() >> 1)) {
Node node = first;
/*值是null,使用==null比较*/
if (value != null) {
while (node != null) {
if (value.equals(node.data)) {
find = node;
break;
}
node = node.next;
}
/*值非null,使用equals*/
} else {
while (node != null) {
if (node.data == null) {
find = node;
break;
}
node = node.next;
}
}
/*如果在后半段,我们使用尾结点进行遍历*/
} else {
Node node = last;
/*值非空*/
if (value != null) {
while (node != null) {
if (value.equals(node.data)) {
find = node;
break;
}
node = node.prev;
}
/*值为空*/
} else {
while (node != null) {
if (node.data == null) {
find = node;
break;
}
node = node.prev;
}
}
}
Node p = find.prev;
Node n = find.next;
/*如果目标结点是头结点*/
if (p == null) {
first = n;
/*非头结点*/
} else {
p.next = n;
}
/*是尾结点*/
if (n == null) {
last = p;
/*非尾结点*/
} else {
n.prev = p;
}
/*将删除的结点成员变量置为空,使JVM进行回收*/
find.data = null;
find.prev = null;
find.next = null;
/*删除结束,元素个数-1*/
total--;
}
}
/**
* @param loc 双向链表的虚拟下标,从0开始
* loc不在合理范围就抛出异常,在正常范围就进行删除
*/
public void remove(int loc) {
Node node = first;
/*是否合理范围*/
if (loc >= 0 && loc < total) {
/*如果是头结点*/
if (loc == 0) {
first = first.next;
first.prev = null;
return;
/*如果是尾结点*/
} else if (loc == total - 1) {
last = last.prev;
last.next = null;
/*啥也不是*/
} else {
while (loc > 0) {
node = node.next;
loc--;
}
Node p = node.prev;
Node n = node.next;
p.next = n;
n.prev = p;
node.prev = null;
node.next = null;
node.data = null;
}
/*删除后长度-1*/
total--;
} else {
throw new ArrayIndexOutOfBoundsException("下标越界异常");
}
}
/**
* @param old 被替换的元素原来的值
* @param NewValue 被替换的元素的新的值
*/
public void set(E old, E NewValue) {
Node node = first;
/*目标值为null*/
if (old == null) {
while (node != null) {
if (node.data == null) {
node.data = NewValue;
}
node = node.next;
}
/*非null*/
} else {
while (node != null) {
if (old.equals(node.data)) {
node.data = NewValue;
}
node = node.next;
}
}
}
}
双向链表的测试
package com.dyy.doublelinkedlist;
public class DLLTest {
public static void main(String[] args) {
DoubleLinkedList<String> dll = new DoubleLinkedList<>();
dll.add("hello");
dll.add("world");
dll.add("test");
dll.add(null);
dll.add("method");
dll.add("unit");
dll.add("one");
dll.add("DDL");
dll.add(null);
dll.add(null);
for (String s : dll) {
System.out.print(s + ",");
}
System.out.println("\n------------------------------------------------");
System.out.println("此时双向链表长度为"+dll.size());
System.out.println("------------------------------------------------");
System.out.println("<<根据元素内容删除>>");
System.out.println("删除元素world,它的下标为:"+dll.IndexOf("world"));
dll.remove("world");
for (String s : dll) {
System.out.print(s + ",");
}
dll.remove(null);
System.out.println("\n删除元素null");
for (String s : dll) {
System.out.print(s + ",");
}
System.out.println("\n-------------------------------------------------");
System.out.println("<<根据下标删除>>");
dll.remove(0);
System.out.println("删除头结点");
for (String s : dll) {
System.out.print(s + ",");
}
System.out.println();
dll.remove(dll.size()-1);
System.out.println("删除尾结点");
for (String s : dll) {
System.out.print(s + ",");
}
System.out.println();
dll.remove(2);
System.out.println("删除中间结点下标为2的元素");
for (String s : dll) {
System.out.print(s + ",");
}
System.out.println("\n-------------------------------------------------");
dll.set(null,"空");
System.out.println("<<替换>>null为空");
for (String s : dll) {
System.out.print(s + ",");
}
}
}
/*控制台输出结果
hello,world,test,null,method,unit,one,DDL,null,null,
------------------------------------------------
此时双向链表长度为10
------------------------------------------------
<<根据元素内容删除>>
删除元素world,它的下标为:1
hello,test,null,method,unit,one,DDL,null,null,
删除元素null
hello,test,method,unit,one,DDL,null,null,
-------------------------------------------------
<<根据下标删除>>
删除头结点
test,method,unit,one,DDL,null,null,
删除尾结点
test,method,unit,one,DDL,null,
删除中间结点下标为2的元素
test,method,one,DDL,null,
-------------------------------------------------
<<替换>>null为空
test,method,one,DDL,空,
*/
二叉树
在满树的情况下,每层的结点个数为2的(n-1)次方;从根结点到第n层的总结点数是2的n次方-1
哈希表
hashtable---最古老的哈希表,是线程安全的,但是效率比较低
hashmap---最常用的哈希表
jdk 1.7 是数组+链表结构
jdk 1.8 是数组+链表/红黑树结构
1 hashmap是如何解决哈希碰撞的?
(1)对象的hashcode % 数组长度
(2)对象的hashcode & (2的n次方-1)
hashmap底层是使用的第二种,他是位运算,效率比较高,由于如果hashmap的表是无规则长度时,比如10,我们使用与运算和7进行运算时结果种类变少,使碰撞概率大大增加,因此为了能够取到刚好的一个特殊值,hashmap的逻辑会使它的长度强制等于相近大小的2的幂次方(如果不指定的话,它的长度默认是16)。
在jdk1.7以前是默认转为最近的2的幂次方。
在jdk1.8中调用了Integer.highestOneBit(),向上取2的n次方。
2 一个位置上有多个元素存储时,hashmap是使用链表进行存储的,当链表长度达到8且满足一定条件时,会转化为红黑树。
这是为了提高查找的效率,但是因为红黑树要始终保持平衡,所以导致了它添加或者删除的效率很低。
3 那么是不是链表长度一达到8就立刻转化为红黑树?
不是的。
hashmap的数组有初始化长度,默认是16,数组越短,hash冲突的概率越高,那么HashMap的数组长度未达到临界值(64)时,一旦链表长度达到8,那么他是先进行扩容解决的,扩容后[index]中的(key,value)就要重新计算地址了,那么此时这8个结点可能也就分散了。
4 介绍一下这个临界值。
这个值在HashMap源码中声明为
MIN_TREEIFY_CAPACITY = 64;
也就是说hashmap长度小于64之前,某个地址上的链表长度超过8是优先进行扩容的,然后对这些元素的地址重新计算,(这个时候这些元素可能分散),直到长度为64时,它才会使用红黑树。
5 remove删除后的红黑树?
当map中(key,value)移除一些后,原来[index]下面的结点可能会变少,在结点减少至6个时,会考虑是否转换为链表。
6 什么时候红黑树转换为链表?
并不会在remove的时候,发现少于6个立刻进行转换。它的转换场景有:
(1)下次在添加时,发现某个红黑树结点很少(大概四个左右),才转为链表
(2)remove到红黑树的结点降至3个左右时,转为链表
满足上面两个条件之一,会进行转换。
7 (key,value)中的key的值会决定它的最终位置,单并不是直接使用key&(长度-1)而是要经过再次运算
比如说:(1)null值本来是没有hashcode的,但是hashMap把他处理为0,所以key==null的值一定在table[0]中。
(2)key的hashcode分布很广,但table.length是比较局限的,大多数情况是:计算后都会聚集在低位,高位的空间就浪费掉了,所以hashmap使用hashcode的高位二进制和地位二进制进行^运算。(依靠的是离散数学的知识)
8 添加到Map中的key值是不能再进行修改的。特别是跟index计算有关的都不能修改。
因为无论在jdk1.7或者是jdk1.8中,map除了存储各个位置上元素的(key,value)还存储key的(处理过的)hashcode,因为我们每次添加新(key,value)时,会和存储的值进行比较,如果key重复,只会覆盖(key,value),所以我们新的key与原来的key做比较的话,会产生两种情况之一:
(1)我们新的(key,value)会先计算出一个它即将存储的table[index],如果table[index]为null,那么直接放进去就可以;
(2)如果table[index]非空,会比较原来的key和新的key,比较它们的hashcode,如果一样,再比较equals方法;如果不一样,就不用equals了。
为什么不直接使用equals?
这是因为数值运算非常快,equals比较慢,所以先使用hashcode比较。
那么存储了hashcode的值,就不用每次比较的时候再获取key 的hashcode值,然后再次处理了,可以提高效率
concurrentHashMap---是线程安全的哈希表,效率比hashtable高
Map.Entry
我们向哈希表中put的(key,value)键值对,它其实就是Map.Entry类型,他是一个接口,是Map的内部接口,所以他是无法被创建对象的。
在JDK1.7时,HashMap中一个内部类Entry实现了Map.Entry
在JDK1.8时,HashMap中一个内部类Node实现了Map.Entry,还有另外一个内部类TreeNode继承了LinkedHashMap的Entry类型,而这个LinkedHashMap又继承了HashMap的Node,相当于内部类TreeNode继承了HashMap的Node。
栈和队列
Stack栈
java.util.Stack<E>是Vector<E>集合的子类。
比Vector多了几个方法
- (1)peek:查看栈顶元素,不弹出
- (2)pop:弹出栈
- (3)push:压入栈 即添加到链表的头
Queue和Deque
Queue 方法 | 等效 Deque 方法 |
|---|---|
| add(e) | addLast(e) |
| offer(e) | offerLast(e) |
| remove() | removeFirst() |
| poll() | pollFirst() |
| element() | getFirst() |
| peek() | peekFirst() |
双端队列也可用作 LIFO(后进先出)堆栈。应优先使用此接口而不是遗留stack类。
| 堆栈方法 | 等效 Deque 方法 |
|---|---|
| push(e) | addFirst(e) |
| pop() | removeFirst() |
| peek() | peekFirst() |
结论:Deque接口的实现类既可以用作FILO堆栈使用,又可以用作FIFO队列使用。
Deque接口的实现类有ArrayDeque和LinkedList,它们一个底层是使用数组实现,一个使用双向链表实现。