集合框架
一、框架
在程序中,将一些功能进行封装,提供给其他人使用的功能叫做框架。其作用是用来简化开发过程以及代码。
二、集合
实现了类似于数组的功能,将多个对象进行操作的方法进行了定义和封装,在开发时只需要调用即可,简化了数组的使用过程。
与数组的区别:
- 数组长度固定,集合长度不固定。
- 数组可以存储基本数据类型和引用数据类型,而集合只能存储引用数据类型。
注意:如果在集合中存储基本数据类型,会自动转换成包装类。
三、集合框架的体系
集合框架常用的主要有两个体系:Collection和Map
3.1 Collection体系
Collection作为整个体系的父接口,能够存储多种类型的对象,特点是无序、无下标、可以迭代(循环)每个元素。其有两个常用的子接口。
List,该接口增加下标功能,特点是有序,有下标,且元素可以重复。常用实现类如下:
- ArrayList,底层使用数组实现
- LinkedList,底层使用链表实现
Set,该接口特点是无序,无下标,且元素不能重复。
- HashSet,特点是使用HashMap的key来实现。
- TreeSet,特点是使用tree实现,所以可以排序。
3.2 collection体系常用方法
add:添加对象
addAll:将另一个集合中的所有对象添加到当前集合中
clear:清空集合中所有的对象
contains:判断集合中是否包含某个对象
isEmpty:判断集合是否为空
remove:删除一个对象
size:得到集合中元素的个数
toArray:将集合转换成数组
四、List接口相关
4.1 基本特点
List接口继承自Collection接口,特点是:有序、有下标、元素可以重复,相对于Collection接口,添加一些与下标相关的方法。
add(index, obj):向指定下标添加元素。
get(index):获得指定下标的元素。
4.2 有序、无序、排序
有序是指会按照元素添加时的顺序存放。
无序是指添加元素的顺序与最终存放的顺序无关,顺序与用户的理解也无关。
排序是指可以根据程序员要求的特点来进行排序。
4.3 常用实现类
ArrayList(Vector)、LinkedList
Vector与ArrayList具备有几乎相同的结构以及完全相同的用法,区别在于ArrayList不安全,Vector是线程安全的,但是性能非常低下,几乎不会被使用,后面会有其他的性能相对较高的安全的集合来代替。
4.4 数组和链表
- 数组的特点:连续空间、长度固定。增删慢,遍历速度快。
链表的特点:随机空间、长度不固定。增删快,遍历较慢。
- 链表如果操作首尾,比操作中间更快
- 链表有单向和双向之分,单向表示只能向一个方向遍历,双向可以向两边遍历。
4.5 ArrayList的用法
public class TestMain1 {
// ArrayList用法
// 使用数组原理实现的集合
public static void main(String[] args) {
// 创建一个集合
ArrayList list = new ArrayList();
// 添加元素
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("e");
// 通过下标获取元素
System.out.println(list.get(2));
// 通过下标遍历
// size方法获得集合中元素的个数
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
// 通过foreach遍历
for (Object object : list) {
String temp = (String)object;
System.out.println(temp);
}
// 使用迭代器循环
Iterator it = list.iterator(); // 得到集合中的迭代器
while(it.hasNext()) { // 如果有下一个
String str = (String)it.next(); // 获取下一个
System.out.println(str);
}
// 删除
list.remove(1);// 通过下标删除
list.remove("e"); // 直接删除元素
Object [] arr = list.toArray(); // 将集合转换成数组
}
}
注意:Vector与ArrayList用法完全相同。
4.6 LinkedList的基本用法
public class TestMain2 {
// LinkedList用法
// 使用数组原理实现的集合
public static void main(String[] args) {
// 创建一个集合
LinkedList list = new LinkedList();
// 添加元素
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("e");
// 通过下标获取元素
System.out.println(list.get(2));
// 通过下标遍历
// size方法获得集合中元素的个数
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
// 通过foreach遍历
for (Object object : list) {
String temp = (String)object;
System.out.println(temp);
}
// 使用迭代器循环
Iterator it = list.iterator(); // 得到集合中的迭代器
while(it.hasNext()) { // 如果有下一个
String str = (String)it.next(); // 获取下一个
System.out.println(str);
}
// 删除
list.remove(1);// 通过下标删除
list.remove("e"); // 直接删除元素
Object [] arr = list.toArray(); // 将集合转换成数组
// 除了上面的通用用法以外,还有一些链表特有的用法
list.addFirst("1"); // 在链表最前面添加
list.addLast("2"); // 在链表最后面添加
list.removeFirst(); // 删除最前面的元素
list.removeLast(); // 删除最后面的元素
}
}
4.7 ArrayList底层实现
1、当创建一个无参的对象时,默认数据为一个空数组。长度为0
2、当创建的无参对象(空数组)第一次添加元素时,会将长度指定为10,添加元素时,会进行一次扩容。
3、如果元素的个数没有超过数组的空间时,不会扩容。如果添加的下一个元素时超过了数组空间大小时,会进行扩容。注:说明扩容的
临界点
是当数组元素已满时进行。4、当进行扩容时,会扩容为原来大小的1.5倍。如果扩容1.5倍时超出了范围,扩容1个空间,保障至少能添加元素。
5、当新的空间大小超出MAX_ARRAY_SIZE时,指定为Integer.MAX_VALUE,如果超过了Integer.MAX_VALUE时,则报错。
// 默认大小
private static final int DEFAULT_CAPACITY = 10;
// 存储数据的
Object[] elementData;
// 当前数组中元素的数量
private int size;
// 无参构造方法
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
// 通过指定长度来创建集合
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
// 通过指定长度来创建数组
this.elementData = new Object[initialCapacity];
// 如果长度等于0,就直接赋值为一个空数组
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
// 如果是负数就报错
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
// 添加元素
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
//将新添加的元素赋值到数组中,并size加1
elementData[size++] = e;
return true;
}
// 判断是否空数组,根据数组情况来设置最小需要的长度
private void ensureCapacityInternal(int minCapacity) {
// 当通过无参构造方法创建对象时,默认为空数组时
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// 将数组大小设置为10
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
// 当数组元素已经达到数组所容纳的元素上限时
if (minCapacity - elementData.length > 0)
// 扩容
grow(minCapacity);
}
// 扩容
private void grow(int minCapacity) {
// overflow-conscious code
// 旧的空间大小,原来数组的长度
int oldCapacity = elementData.length;
// 新的空间大小,原来数组大小的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 当新的空间-需要的最小空间(size+1)小于0时,新空间直接赋值为最小空间(size+1)
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 当新的空间如果大于2147483639时
if (newCapacity - MAX_ARRAY_SIZE > 0)
// 指定新的空间为巨大的空间
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
// 创建一个新的大小的数组,并将原来的数据复制到新数组中
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
// 保障的最小空间都超出了范围,此时直接报错
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
// 如果需要保障的最小空间超过2147483639,直接指定大小为最大值(2147483647),否则指定为最大数组大小
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
4.8LinkedList的原理
1、LinkedList是一个双向链表。
2、内置一个Node节点类,来进行链接。
3、会保存链表的首尾节点。
4、当添加元素时,会将元素保存为一个节点(Node),并设置节点相应的前后节点,以及根据情况修改LinkedList首尾节点。
// LinkedList的内部类,用来保存一个节点
private static class Node<E> {
E item; // 元素
Node<E> next; // 下一个节点
Node<E> prev; // 上一个节点
// 有参构造方法,每次创建对象时必须指定上一个节点,当前元素,下一个节点
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
transient Node<E> first; // 第一个节点
transient Node<E> last; // 最后一个节点
// 无参构造方法
public LinkedList() {
}
// 添加,默认在尾部添加
public boolean add(E e) {
linkLast(e);
return true;
}
// 在尾部添加
public void addLast(E e) {
linkLast(e);
}
//
void linkLast(E e) {
// 记住最后一个节点
final Node<E> l = last;
// 创建一个新的节点,将上一个节点指定为原来的最后一个节点,下一个节点指定为null
final Node<E> newNode = new Node<>(l, e, null);
// 将自己指定为当前LinkedList的尾部(最后一个节点)
last = newNode;
// 如果上一个节点为空,则将当前节点也设为第一个节点(首部)
if (l == null)
first = newNode;
else
// 如果上一个节点不为空,则将原来的最后一个节点的下一个节点指定为当前节点
l.next = newNode;
size++;
modCount++;
}
五、泛型以及集合
5.1 泛型
泛型是对类型的一种约束。作用是在使用时会自动转成相应的类型,还会对不是该类型的地方进行检查,会有相应的编译错误提示。
5.2 泛型集合
在集合创建时使用泛型(推荐使用),会在向集合中添加元素时对元素进行检查, 如果类型不一致会出现编译错误的提示,在访问时(获取)会自动转型,避免强制转换的麻烦。
5.3 泛型的语法
泛型分为类泛型和方法泛型。
类泛型
- 在类定义时使用泛型(可以使用多个),类中的任意位置都可以使用该定义的泛型
// T是一个可变化的类型,用户在使用时传入的是什么类型,就会变化成什么类型
// 如果用户没有传入,则就是Object类型
// 在当前类中,只能当为Object使用,因为不确定用户会传入什么类型
// T也可以使用任意字母代替
// 多个类型之间可以使用逗号隔开
public class MyClass <T, P>{
public T test(T obj) {
T t;
return null;
}
public T test1(T obj1, P obj2) {
return null;
}
}
public class TestMain1 {
public static void main(String[] args) {
MyClass<String, Integer> c = new MyClass<String, Integer>();
String str = c.test("2");
String str1 = c.test1("2", 3);
}
}
方法泛型
- 是指在类上面不定义泛型,但是在方法中定义并使用泛型
public class MyClass1 {
// 在方法上定义并使用泛型
public <T> T test(T obj) {
T t;
return null;
}
// 在方法中定义的泛型,在其他方法中不能使用
// public T test1() {
//
// }
}
public class TestMain1 {
public static void main(String[] args) {
MyClass1 c1 = new MyClass1();
// 由于在方法中定义了泛型,且要求传入参数与返回值类型一致,所以也可以不需要强制转换,会自动检查类型
Date str2 = c1.test(new Date());
}
}
为什么说Java中的泛型是伪(假)泛型?
1、Java中的泛型仅对传入的参数进行编译检查,不会真的对参数进行处理。
2、Java中的泛型仅对对象使用时进行强制转换,不会真的对对象进行转型处理。
3、在对象真正使用的过程中,仍旧是Object类型。
5.4 泛型中的super和extends
当作为参数时,传入的参数根据extends和super的不同,可以传入更宽泛的类型。
? extends B 表示可以传入B类或者B类的子类 ,称为上界限定符
? super B 表示可以传入B类或者B类的父类,称为下界限定符
在使用时,上界不存,下界不取
使用上界时,该集合不能存储数据,可以取值
使用下界时,集合不能取值,但是可以存储数据
public class TestMain2 {
public static void main(String[] args) {
ArrayList<B> list = new ArrayList<B>();
addAll(list);
}
// ? extends B 表示B类或者B类的子类 上界限定符
// ? super B 表示B类或者B类的父类 下界限定符
// 相对于直接写B类,类型会宽泛一些
// 上界不存,下界不取
// 使用上界时,集合不能存储数据,可以取值
// 使用下界时,集合不能取值,但是可以存储数据
public static void addAll(ArrayList<? super B> c) {
// 添加元素(存)
// c.add(new B());
// 循环(取)
// for (B b : c) {
// System.out.println(b);
// }
}
}
六、Collections工具类
Collections针对于集合的帮助类,相当于Arrays是针对于数组的帮助类。
主要是对集合的排序、打乱、截取等操作。
public class TestMain3 {
public static void main(String[] args) {
// sort
ArrayList<Integer> list = new ArrayList<>();
list.add(23);
list.add(35);
list.add(12);
list.add(8);
list.add(10);
list.add(45);
list.add(14);
Collections.sort(list); // 对集合进行排序
System.out.println(list);
Collections.reverse(list); // 对集合进行反转
System.out.println(list);
Collections.shuffle(list);// 随机打乱一个集合
System.out.println(list);
// 如果已知元素,可以使用asList方法将其转换成集合,但是不能强转成ArrayList,因为它是List接口
// 一般用作循环使用
List<Integer> list1 = Arrays.asList(23, 35, 12, 8, 10, 45, 14);
System.out.println(list1);
Collections.sort(list1); // 对集合进行排序
System.out.println(list1);
Collections.reverse(list1); // 对集合进行反转
System.out.println(list1);
Collections.shuffle(list1);// 随机打乱一个集合
System.out.println(list1);
}
}
Collection与Collections的区别:
Collection是List和Set的父接口,无序,无下标。
Collections是一个集合的帮助类。提供一系列的静态方法对集合进行操作。
七、Set集合
无序、无下标,元素不能重复。
7.1 HashSet
通过hashCode实现元素不可重复。
当存入的元素hashCode相同时,会使用equals方法来判断是否相同元素,如果不相同,则可以存入。
HashSet判断不同对象的步骤是:
1、先判断hashCode是否相同,如果不同,直接认定是不同对象,如果相同,进行第二步。
2、判断equals是否相同,如果不同,才认定是不同对象,如果相同,认定为相同对象,会直接放弃添加。
所以,在使用Hashset时,如果要存入实体类的对象,一定要对该实体类进行重写equals和hashCode方法。
注意:HashSet底层使用的HashMap,创建HashSet时会创建一个HashMap,每次添加元素时,就是向HashMap的key上面进行添加。
public class TestMain {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
// 六个都会存入
list.add("c");
list.add("b");
list.add("a");
list.add("a");
list.add("b");
list.add("c");
System.out.println(list);
HashSet<String> set = new HashSet<String>();
// 只存入了A、B、C
set.add("B");
set.add("C");
set.add("A");
set.add("B");
set.add("C");
set.add("A");
System.out.println(set);
}
}
public class Student {
private String name;
public Student(String name) {
super();
this.name = name;
}
@Override
public String toString() {
return "Student [name=" + name + "]";
}
// 如果hashCode和equals都相同,无论是否同一个对象,都会认定为同一个,不会存入
@Override
public int hashCode() {
return 1;
}
@Override
public boolean equals(Object obj) {
return true;
}
}
public class TestMain1 {
public static void main(String[] args) {
HashSet<Student> set = new HashSet<Student>();
// 当hashCode和equals都认定相同时,只会存入张三
set.add(new Student("张三"));
set.add(new Student("李四"));
System.out.println(set);
}
}
7.2 LinkedHashSet
LinkedHashSet继承自HashSet。
HashSet一般情况下使用HashMap作为底层,而LinkedHashSet使用的LinkedHashMap作为底层,在HashMap的基础上添加了链表的结构,来保存了添加的顺序。
public class TestMain2 {
public static void main(String[] args) {
LinkedHashSet<String> set = new LinkedHashSet<>();
set.add("B");
set.add("C");
set.add("A");
// 输出结果为:B-->C-->A
for (String string : set) {
System.out.println(string);
}
}
}
7.3 TreeSet
实现了SortedSet接口,自动排序,根据自定的方式(基于Comparable接口)排序。
注意:使用TreeSet来存储元素时一定要指定排序的方式,否则会报错。
二叉树:
一个节点,最多只能有两个直接的子节点。
TreeSet进行排序的规则要求实现Comparable接口,如果是自定义的类,要将对象放入到TreeSet中,必须实现该接口,否则会出现ClassCastException。
public class Student implements Comparable<Student>{
private String name;
private int age;
public Student(String name) {
super();
this.name = name;
}
public Student(String name, int age) {
super();
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + "]";
}
// 如果hashCode和equals都相同,无论是否同一个对象,都会认定为同一个,不会存入
@Override
public int hashCode() {
return 1;
}
@Override
public boolean equals(Object obj) {
return true;
}
@Override
public int compareTo(Student o) {
// if(this.age - o.age < 0) {
// return -1;
// }else if(this.age - o.age > 0) {
// return 1;
// }
// return 0;
return this.age - o.age;
}
}
public class TestMain3 {
public static void main(String[] args) {
TreeSet<Integer> set = new TreeSet<Integer>();
set.add(20);
set.add(15);
set.add(30);
set.add(18);
set.add(12);
set.add(14);
set.add(10);
set.add(30); // 与前面添加的30相等,放弃添加,保证元素不重复
System.out.println(set);
// 要使用TreeSet来添加自定义类的对象,需要实现比较接口
TreeSet<Student> set1 = new TreeSet<Student>();
set1.add(new Student("张三", 20));
set1.add(new Student("李四", 18));
set1.add(new Student("王五", 19));
set1.add(new Student("赵六", 22));
System.out.println(set1);
}
}
上面使用的Collections.sort方法进行排序时,如果是自定义对象,也需要实现Comparable接口。
public class TestMain3 {
public static void main(String[] args) {
ArrayList<Student> list = new ArrayList<Student>();
list.add(new Student("张三", 20));
list.add(new Student("李四", 18));
list.add(new Student("王五", 19));
list.add(new Student("赵六", 22));
Collections.sort(list);
System.out.println(list);
}
}
注意:Comparable接口中的compareTo方法,返回负数会放到树的左边,正数放到树的右边,返回0表示元素相等,会放弃添加。
八、Map体系
8.1 Map结构
Map是一个顶层接口,常用的实现类有HashMap、TreeMap等。
Map结构特点是:使用键值(key,value)对来存放数据。key键要求不能重复,value值可以重复。
// 常用方法:
put(Object key, Object value); // 添加
Object get(Object key); // 获取元素
Set keySet(); // 返回所有的键集
Collection values(); // 返回所有的值
注意:map存取元素时,需要使用key来访问,key可以是任意类型,但是推荐使用字符串。当使用相同的key存放时,会覆盖原来的内容。
8.2 HashMap基本使用
public class TestMain {
// HashMap的用法
public static void main(String[] args) {
// 创建对象
HashMap<String, Student> map = new HashMap<String, Student>();
// 添加元素
map.put("a", new Student("1", "张三", 20));
map.put("b", new Student("2", "李四", 21));
map.put("c", new Student("3", "王五", 22));
// 获取元素
Student stu = map.get("b");
System.out.println(stu);
// 遍历元素
// 1、借助遍历key来遍历值
// 得到keys键集
Set<String> set = map.keySet();
for (String key : set) {
System.out.println("key为:" + key + ",对应的值为:" + map.get(key));
}
// 2、直接获取值集合
Collection<Student> colls = map.values();
for (Student student : colls) {
System.out.println("值为:" + student);
}
// 3、在遍历时获得键值
Set<Map.Entry<String, Student>> entrySet = map.entrySet();
for (Map.Entry<String, Student> entry : entrySet) {
System.out.println("key为:" + entry.getKey() + ",对应的值为:" + entry.getValue());
}
// 删除元素
map.remove("a");
int size = map.size(); // 得到元素个数
System.out.println(size);
}
}
HashMap与Hashtable的区别:(相同点:使用方法基本一样)
1、HashMap性能较高,但是线程不安全,Hashtable线程安全,性能极低,基本不使用。
2、HashMap键和值都可以为null,但是键不能重复,会覆盖。Hashtable键和值都不能为null。
8.3 HashMap的底层原理
既有比较快的查找性能,又有较好的增删性能。
采用数组+链表+红黑树(JDK1.8后)的结构。
1、创建HashMap时,指定了扩容因子为0.75
2、在第一次添加元素时,才会创建数组,大小为16,设置临界点12
3、添加元素时,如果key相同,会覆盖值,会根据key的hash计算,并与数组的长度求模,得到该元素存放的下标,如果该下标上有元素,就直接放到链表最后一个元素,如果此时链表数量大于等于7,变化为红黑树。
注意:红黑树的变化条件有两个:1、元素总数量大于64,2、链表上的元素大于等于7
4、当元素添加到临界点时,再添加元素时,会进行扩容,扩容为原来的2倍,临界点重新设置为数组大小*0.75
5、当扩容时,数组大小大于2^30,则指定int的最大值。
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 初始大小
static final int MAXIMUM_CAPACITY = 1 << 30; // 最大空间2^30
static final float DEFAULT_LOAD_FACTOR = 0.75f; // 扩容因子
static final int TREEIFY_THRESHOLD = 8; // 变成树的临界值
static final int UNTREEIFY_THRESHOLD = 6; // 变成链表的临界值
static final int MIN_TREEIFY_CAPACITY = 64; // 变成的最小大小
// map中的链表结构(单向链表)
static class Node<K,V> implements Map.Entry<K,V> {
final int hash; // 哈希值
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + "=" + value; }
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}
transient Node<K,V>[] table; // map中存放数据的数组,每个元素都是一个链表
// hash计算
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
// 无参构造
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
// 如果是第一次添加元素(table为null),或者table中的元素数量为0
if ((tab = table) == null || (n = tab.length) == 0)
// 设置n为16
n = (tab = resize()).length;
// hash值求模运算,如果原来的数组的首元素为空,直接赋值
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
// 如果首元素不为空,则判断key的hash与equals是否相同
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
// 相同则覆盖元素
e = p;
// 如果不同,并且为树形结构
else if (p instanceof TreeNode)
// 向树型结构中添加元素
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
// 向链表中添加元素
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
// 如果当前桶上元素数量大于等于7,转成红黑树
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
// key相同则停止循环
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
//
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
// 如果size大于临界点
if (++size > threshold)
resize(); // 扩容
afterNodeInsertion(evict);
return null;
}
final Node<K,V>[] resize() {
// 记住原来的数组
Node<K,V>[] oldTab = table;
// 如果原来的数组为空,长度即为0,否则为原来的长度
int oldCap = (oldTab == null) ? 0 : oldTab.length;
// 记住原来的临界点空间大小
int oldThr = threshold;
// 新的大小暂为0
int newCap, newThr = 0;
// 如果原来的大小大于0
if (oldCap > 0) {
// 如果原来的大小大于最大大小,直接赋值为最大值
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
// 如果原来的大小扩容1倍后小于最大大小并且原来的大小大于等于16
// 大小扩容1倍
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
// 临界点扩容1倍
newThr = oldThr << 1; // double threshold
}
// 原来的临界点大于0
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr; // 新的空间等于老的临界点
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY; // 新的空间大小设置为16
// 新的临界点为12
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
// 处理极限值
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr; // 将计算的临界值保存属性中
@SuppressWarnings({"rawtypes","unchecked"})
// 创建新的数组大小
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab; // 保存新的数组到属性中
// 如果原来的数组元素不为空,则重新计算位置
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve order
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
8.4 Properties
Properties继承自Hashtable,但是添加了setProperty和getProperty方法,只能够操作字符串。
作用是用来加载系统的配置文件。所以里面还添加了load方法,专门用来加载配置。
public class TestMain2 {
// Properties的用法,用来加载资源文件
public static void main(String[] args) {
Properties prop = new Properties();
// 设置值
prop.setProperty("username", "root");
prop.setProperty("password", "root");
prop.setProperty("url", "数据库的url");
// 获取值
System.out.println(prop.getProperty("username"));
}
}
8.5 TreeMap
TreeMap使用树的方式来存放数据,需要对Key对应的类实现Comparable接口。当添加后,会自动排序。
public class Student implements Comparable<Student>{
private String id;
private String name;
private int age;
public Student(String id, String name, int age) {
super();
this.id = id;
this.name = name;
this.age = age;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Student other = (Student) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
return true;
}
@Override
public String toString() {
return "Student [id=" + id + ", name=" + name + ", age=" + age + "]";
}
@Override
public int compareTo(Student o) {
return this.id.hashCode() - o.id.hashCode();
}
}
public class TestMain3 {
// TreeMap的用法
public static void main(String[] args) {
TreeMap<Student, String> map = new TreeMap<Student, String>();
map.put(new Student("2", "张三2", 21), "aaa");
map.put(new Student("1", "张三1", 22), "bbb");
map.put(new Student("4", "张三4", 23), "ccc");
map.put(new Student("3", "张三3", 24), "ddd");
System.out.println(map);
}
}