集合框架
软件包 java.util
包含 collection 框架、遗留的 collection 类、事件模型、日期和时间设施、国际化和各种实用工具类(字符串标记生成器、随机数生成器和位数组)。
1.集合
探究集合的特点:
- 是否有序: [2,4,5,3] -> [2,4,5,3]
- 是否唯一
- 是否排序:[2,4,5,3] -> [2,3,4,5]
- 效率
- 线程安全问题
- 能否存储null值
1.1集合的特点
- 能够存储任意数据类型
- 集合能够动态扩容,长度可以改变
- 集合只能够存储引用数据类型,但是可以存储基本数据类型对应的包装类类型
- 集合针对元素的操作提供了(增删查改)API
- 集合是一套框架,针对不同的容器提供不同的类
1.2集合容器的底层依赖数据结构
- 常用的和集合相关的数据结构有: 栈, 队列, 链表, 哈希表(hashCode), 数组, 二叉树;
- 效率的决定因素: 数据结构 + 算法 + 硬件
1.3数组和集合的区别
数组 | 集合 | |
---|---|---|
长度 | 长度是固定不变的 | 根据实际需求改变 |
内容 | 存储的是同一种类型的元素 | 存储不同类型的元素 |
数据类型 | 数组可以存储基本数据类型,也可以存储引用数据类型 | 集合只能存储引用类型,但是可以存储基本数据类型的包装类类型 |
2.Collection
单列集合 :List 有序 可重复; Set 无序 唯一
2.1Collection的方法
-
增加
说明 boolean add(Object e) 增加一个Object对象 boolean addAll(Collection<? extends E> c) 增加一个集合 -
删除
说明 boolean remove(Object o) 删除一个Object对象 boolean removeAll(Collection<?> c) 删除一个集合 void clear() 清除集合内所有内容 -
修改(Collection没有修改的方法,因为不知道子类是什么数据结构类型,没办法决定修改的方式)
-
查询【遍历】
说明 Object[] toArray() T[] toArray(T[] a) Iterator iterator() 迭代器 -
判断
说明 boolean contains(Object o) 是否包含一个Object对象 boolean containsAll(Collection<?> c) 是否包含一个集合 boolean isEmpty() 是否为空 -
获取
说明 int size() 集合长度 -
其他
说明 boolean retainAll(Collection<?> c) 【交集】 返回原集合在求交集之后是否发生改变; 没有改变返回false; 发生改变返回 true
2.2Collection的遍历方法
Collection有4种,遍历方式:
- 迭代器
- foreach
- toArrays
- for循环(利用迭代器)
2.2.1ArrayList的toString()源码
public String toString() {
Iterator<E> it = c.iterator();
//判断是否为空,提高效率
if (! it.hasNext())
return "[]";
StringBuilder sb = new StringBuilder();
sb.append('[');
for (;;) {
Object e = it.next();
//this是调用者c,正常情况下,e不等于c的,
//因为e是对象,c是集合
//当在add一个集合时就会出现e == this,
//打印“this Collection”
sb.append(e == this ? "(this Collection)" : e);
if (! it.hasNext())
return sb.append(']').toString();
sb.append(',').append(' ');
}
}
2.2.2为什么需要迭代器这个玩意?
- 因为Collection是无序的,而使用for循环遍历是需要索引(index),除非向下转型为List,利用List有序的特性,才能使用for循环,所以需要迭代器来对Collection集合进行遍历
2.2.3迭代器的异常问题
- java.util.NoSuchElementException:迭代器中没有元素了,如果再取之抛出该异常,如果希望解决,在迭代之前判断
- java.util.ConcurrentModificationException
- 异常名称:并发修改异常
- 产生原因:在使用迭代器对象迭代的同时,使用原集合就该了元素
//AB是同一个集合的元素
while (it.hasNext()) {
Object obj = it.next();
if (obj.equals("A")) {
c.remove("B");
}
}
//系统会报异常:java.util.ConcurrentModificationException
//因为obj.equals("A")是迭代器线程的
//而c.remove("B");则在调用原集合
//会导致同时执行两个不同的集合从而报错
2.2.3.1迭代器的内存原理解析
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uvvdOs3E-1574599617946)(E:\尚学堂\TYPORA_NOTES\14.1 集合框架.assets\迭代器内存解析.png)]
- 迭代器和原集合不是同一个集合,迭代器包括cursor(指针)
- 同时运行两个集合就会出并发修改异常
2.2.3.2并发修改异常的解决方式
-
Object[] toArray()
//AB是同一个集合的元素 for (Object obj : objs) { System.out.println(object); if (obj.equals("A")) { c.remove("B"); } } //因为toArray() 把原集合转化为数组了,所以遍历和移除都在同一个数组上 //所以不存在并发 //缺陷:把集合装换成数组了
2.2.4迭代器的理解易错点
-
iterator.next( )在循环中每一次出现都会迭代一次
Iterator<String> iterator = map.keySet().iterator(); while (iterator.hasNext()) { //key是键,所以用map.put(key)获取value String key = iterator.next(); String value = map.get(key); System.out.println(key + "=" + value); //结果:Tom=John //Tom2=John2 //Tom1=John1 //String value = map.get(iterator.next()); //System.out.println(iterator.next() + "=" + value); //结果:Tom=John //因为代码中出现了三次的iterator.next(),使一次循环多迭代了2次 }
2.2.5 4种遍历方式
-
迭代器
Iterator it = c.iterator(); while (it.hasNext()) { Object obj = it.next(); System.out.println(obj); }
-
foreach:数组和集合的专有遍历的方式
for (Object obj : c) { System.out.println(obj); }
-
toArrays
Object[] objs = c.toArray(); for (Object obj : objs) { //for (Object obj : c.toArray()) System.out.println(obj); }
-
for循环(利用迭代器)
for (Iterator iterator = c.iterator(); iterator.hasNext();) { Object obj = iterator.next(); } //简便写法 for(Iterator iterator = c.iterator(); it.hasNext(); System.out.println(it.next()));
2.3去重复
- 方式一: 针对不确定是否有序的元素 【创建新的集合,地址传递】
- 方式二: 针对有序的元素可以使用选择排序思想去除重复元素
-
方式一: 针对不确定是否有序的元素 【创建新的集合,地址传递】
public class CollectionDemo04 { public static Collection c; public static void main(String[] args) { c = new ArrayList(); c.add("孙悟空"); c.add("孙悟空"); c.add("猪八戒"); c.add("嫦娥"); c.add("嫦娥"); c.add("唐僧"); removeRepeatElement(); System.out.println(c); } private static void removeRepeatElement() { // 1.创建一个新的集合 Collection newC = new ArrayList(); // 2.遍历旧集合 for (Object obj : c) { // 3.判断新集合是否存在该遍历的元素 if (!newC.contains(obj)) { // 4.如果新集合中不存在该元素,就添加到集合中 newC.add(obj); } } // 5.地址传递 c = newC; } }
-
方式二: 针对有序的元素可以使用选择排序思想去除重复元素
// 2.去除重复的学生 for (int i = 0; i < list.size() - 1; i++) { for (int j = i+1; j < list.size(); j++) { if (list.get(i).equals(list.get(j))) { list.remove(j); j--; } } }
2.3.1contains的源码解析
Object的equals默认比较的是地址,需要重写
c2.contains(obj);
Object[] elementData; 1001 1002 1003 1004
1001
public boolean contains(Object o) {
return c2.indexOf(o) >= 0;
}
1001
public int indexOf(Object o) {
//判断是否是空,提高效率
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
//如果o和数组 Object[] elementData内元素相等
//就返回正数,contains为true,则表示重复
//不相等就返回-1,contains为false,则表示不重复
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
3.List
特点
有序
可重复
3.1List的方法
List 是Colletion的子集,所以继承Collection的所有方法,只需要探究List特有方法
-
添加
说明 void add(int index, E element) 根据索引添加一个对象 boolean addAll(int index, Collection<? extends E> c) 根据索引添加一个集合 -
删除
说明 E remove(int index) 根据索引移除一个对象 -
修改
说明 E set(int index, E element) 根据索引修改一个对象 -
查询【遍历】
说明 E get(int index) 根据索引获取一个对象 ListIterator listIterator() 迭代器 ListIterator listIterator(int index) List的迭代器 -
判断
-
获取
说明 E get(int index) 根据索引获取一个对象 int indexOf(Object o) 从左到右,根据对象,获取相应的索引 int lastIndexOf(Object o) 从右到左,根据对象,获取相应的索引 List subList(int fromIndex, int toIndex) 截取,不影响原集合,左开右闭原则 //indexof获取索引的定位思维 System.out.println(list.get(list.size() - 1)); System.out.println(list.indexOf("A"));
3.2List的遍历
List有5种遍历方式
普通for
ListIterator listIterator() / ListIterator listIterator(int index)
toArray
迭代器
foreach
3.2.1List的遍历方式
-
普通for
for (int i = 0; i < list.size(); i++) { Object obj = list.get(i); System.out.println(obj); }
-
ListIterator
- 想比较与Collection的迭代器,ListIterator可以通过索引,选择遍历开始的地方
- 有逆向遍历的功能
- remove()没有被defalut,throw异常
3.2.2 解决并发修改异常
3.2.2.1 解决思路
迭代迭代器元素的时候同时使用了原集合修改元素
- 不用迭代器,就使用原集合遍历,原集合修改 普通for
- 迭代器遍历,迭代器修改 列表迭代器
3.3ArraryList的源码解析
3.3.1rrayList动态扩容原理 【了解】
- 1.扩容倍数1.5倍
- 2.ArrayList动态扩容底层还是依赖 Arrays.copyOf方法,创建了一个新的数组,然后利用System.arraycopy()方法进行拷贝的
扩容原理:
-
1.计算最小容量:如果内部数组的长度超过了10,则最小容量则最小容量为为数组的实际长度,否则最小长度就是10
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); //10 }
-
2.扩容时机:计算最小容量,如果最小容量超过了内部数组的长度,则进行
if (minCapacity > elementData.length) grow(minCapacity);
-
3.扩容原理: 扩容的实际大小等于内部数组长度的1.5倍
- a.如果扩容后长度还比最小长度小,那么就使用最小长度作为新容量
- b.如果扩容后长度比最小长度大,那么就是用原来长度的1.5倍作为新容量
- c.如果扩容长度比最大长度Integer.MAX_VALUE-8还要大
- (1)如果最小长度超出了Integer的范围导致变成负数溢出,则抛出内存溢出
- (2)如果最小长度比最大数组长度大,但是比Integer范围小,
- 就设置Integer范围的最大值为新的容量,否则设置最大数组长度为新容量
ArrayList<String> list = new ArrayList<>();
list.add("hello");
list.add("world");
class ArrayList {
private int size; //开始集合size为0
transient Object[] elementData; //数组为空数组
private static final int DEFAULT_CAPACITY = 10; //默认容量为10
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
//最大数组容量 = Integer最大值 - 8
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
"hello"
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
//size + 1 = 0 + 1 = 1
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); 10
}
//当elementData等于DEFAULTCAPACITY_EMPTY_ELEMENTDATA,
//比较默认容量和最小容量,较大值赋值给minCapacity
ensureExplicitCapacity(minCapacity);
}
//默认容量为10,所以除了自己定义的,一般最小为10
private void ensureExplicitCapacity(int minCapacity) {
modCount++; // 对容器修改了一次
// overflow-conscious code
if (minCapacity > elementData.length)
grow(minCapacity);
}
10
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length; 7
int newCapacity = oldCapacity + (oldCapacity >> 1);//(>> 1)除2的意思
// oldCapacity * 1.5 Integer
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
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();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}//minCapacity > MAX_ARRAY_SIZE.就赋值为 Integer.MAX_VALUE
}
ArrayList的排序
-
ArrayList默认是不能排序的,但是可以通过Collections工具类进行排序
-
使用Collections对Comparator进行重写
Collections.sort(list, new Comparator<Student>() { @Override//比较器重写 public int compare(Student s1, Student s2) { Collator c = Collator.getInstance(Locale.CHINA); // 先按照总分排序 int cmp = s1.getTotalScore().compareTo(s2.getTotalScore()); int cmp2 = (cmp == 0) ? s1.getChinesScore().compareTo(s2.getChinesScore()) : cmp; int cmp3 = (cmp2 == 0) ? s1.getEnglishScore().compareTo(s2.getEnglishScore()) : cmp2; int cmp4 = (cmp3 == 0) ? s1.getMathScore().compareTo(s2.getMathScore()) : cmp3; int cmp5 = (cmp4 == 0) ? s1.getAge().compareTo(s2.getAge()) : cmp4; int cmp6 = (cmp5 == 0) ? c.compare(s1.getName(), s2.getName()) : cmp5; return cmp6; } });
-
4.泛型
泛型的由来: 模仿了数组和方法。数组: 编译时期确定元素类型;方法: 参数化类型
Java提供了泛型是JDK1.5之后引入的新特性,一种将类型提前在编译时期确定的参数化类型
//格式
<T,E>
//这个T必须是引用数据类型,这里可以叫T,E,H,26个字母都可以,这里可以有多个泛型类型
4.1泛型的优点
-
提高了可读性
-
2.提高了维护性
-
3.提高了扩展性
-
4.消除了黄色警告线,提高了安全性
-
5.简化了代码
-
6.去除了类型判断,提高了效率
4.2泛型类
泛型类是一项独立的技术,和集合无关,是因为Object
4.2.1泛型类特点
-
泛型具有传递性
-
泛型可以定义多个
-
泛型将定义类的时候确定元素类型转而交给了调用者调用或者new的时候确定类型
class GenericClass<E,T> {//可以定义多个
private E e;
private T t;
public E getE() {
return e;
}
public void setE(E e) {
this.e = e;
}
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
}
4.3泛型接口
4.3.1泛型接口的特点
-
实现类时候确定泛型
-
在创建接口的子类对象的时候确定泛型(new)
-
匿名内部类确定泛型
IGenericInterface<String, Boolean> gi = new IGenericInterface<String, Boolean>() { @Override public void show(String e) { System.out.println(e); } @Override public Boolean get(Boolean t) { return t; } };
4.3.2compareTo的重写
Student s1 = new Student("阿三", 30, 98);
Student s2 = new Student("钱八", 30, 98);
int cmp = s1.compareTo(s2);
System.out.println((cmp > 0) ? "张三大" : (cmp < 0 ? "李四大": "一样大"));
class Student implements Comparable<Student>{
private String name;
private int age;
private double score;
//省略构造方法、set、get
//compareTo的重写
@Override
public int compareTo(Student s) {
Collator c = Collator.getInstance(Locale.CHINA);
int cmp = this.getAge() - s.getAge();
int cmp2 = (cmp == 0) ? c.compare(this.getName(), s.getName()): cmp;
double cmp3 = (cmp2 == 0) ? this.getScore() - s.getScore() : cmp2;
int cmp4 = cmp3 > 0 ? 1 : (cmp3 < 0 ? -1 : 0);
return cmp4;
}
}
4.4泛型方法
4.4.1泛型方法的特点
-
泛型方法也称为局部泛型
-
泛型方法在方法调用的并且传参的时候确定类型
//在调用方法的时候,且把实参传给形参时确定数据类型
gm.method("good", 'a');//string,char
gm.method(2.5, false); //double,boolean
class GenericMethod<E, T> {
public <K, V> void method(K k, V v) {
System.out.println(k);
System.out.println(k.getClass().getName());
System.out.println(v);
System.out.println(v.getClass().getName());
}
}
4.4.2 T[] toArray(T[] a) 的作用
有了 Object[] toArray() 方法为什么还需要 T[] toArray(T[] a) 方法?
//可以在遍历前就确定数据类型,避免了向下转型所带来的弊端
//向下转型的弊端,需要不不断添加else if(多态的弊端改善的缺陷)
Collection<Integer> c2 = new ArrayList<Integer>();
c2.add(100);
c2.add(200);
c2.add(300);
Integer[] iis = c2.toArray(new Integer[] {});
for (Integer integer : iis) {
System.out.println(integer);
}
5.HashSet类
-
为什么是无序的?
- 因为哈希值的计算和对象本身的hashCode有关,而hashCode在默认不重写的情况下返回的是地址的转换值
-
为什么可以去重复? --> 底层依赖的是hashCode和equals方法
- 首先比较hashCode值
- 如果hashCode值一样,比较equals
-
为什么从第二次开始每次都是一样的?
- 因为hashCode和对象本身有关,所以同一个对象的hashCode方法返回的值一样,所以哈希值一样
5.HashSet的源码解析
HashSet<String> hs = new HashSet<>();
hs.add("ab");
hs.add("ac");
class HashSet {
private transient HashMap<E,Object> map;
private static final Object PRESENT = new Object(); // 0x0001
public HashSet() {
map = new HashMap<>();
}
"ab"
public boolean add(E e) {
"ab"
return map.put(e, 0x0001)==null;
}
}
1.HashSet底层实现是 HashMap,HashMap底层数据结构是 哈希表
2.数据存储到哈希表结构当中是通过哈希值来存储
3.哈希值和对象本身有关,并且和对象的hashCode方法返回的值有关,并且是随机的,但是每次运行又是一样的
4. if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
哈希表去除重复元素,依赖的是哈希值和equals方法
逻辑: 如果哈希值【对象本身的hashCode】不同,肯定可以保证元素不同
如果哈希值相同,不能完全保证元素相同,还需要比较equals
可以直接比较两个对象equals方法,但是每次比较两个对象的equals方法效率很低
先比较对象的hashCode,hashCode是int类型,int类型的比较肯定非常快
class HashMap {
final float loadFactor;
static final float DEFAULT_LOAD_FACTOR = 0.75f;
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
"ab" 0x0001
public V put(K key, V value) {
6 "ab" 0x0001
return putVal(hash(key), key, value, false, true);
}
"ab"
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
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);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
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;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
}
6.comparable & comparator
6.1comparable & comparator 的区别
comparable | comparator | |
---|---|---|
类型 | 接口 | 接口 |
方法 | 只有compareTo一个方法 | 比较器Comparator是其中一个 |
参数列表 | 一个,compareTo(T o) | 两个,compare(T o1, T o2) |
重写 | 常用类数据类型都实现重写 | 作为后期扩展,实现类少,需要自行实现 |
6.2TreeSet底层源码解析
//访问TreeSet的有参构造方法,重写Comparator的compare方法
// TreeSet<Integer> ts = new TreeSet<>();
TreeSet<Integer> ts = new TreeSet<>(new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
int cmp = s1.getAge() - s2.getAge();
int cmp2 = (cmp == 0) ? s1.getName().compareTo(s2.getName()) : cmp;
return cmp2;
}
});
ts.add(40);
ts.add(43);
//TreeSet部分
class TreeSet{
private transient NavigableMap<E,Object> m; //4. m = TreeMap,所以访问TreeMap
private static final Object PRESENT = new Object(); // 0x0001
public TreeSet() {//1.无参构造
this(new TreeMap<E,Object>());//2.访问带参构造
}
Comparator<? super E> comparator = new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
int cmp = s1.getAge() - s2.getAge();
int cmp2 = (cmp == 0) ? s1.getName().compareTo(s2.getName()) : cmp;
return cmp2;
}
}
public TreeSet(Comparator<? super E> comparator) {
this(new TreeMap<>(comparator));
}
// 3.NavigableMap<E,Object> m = new TreeMap<E,Object>();
TreeSet(NavigableMap<E,Object> m) {//NavigableMap
this.m = m;
}
40 43
public boolean add(E e) {//6. 调用add添加40
40 43 0x0001
return m.put(e, PRESENT)==null;//7. m = TreeMap,所以put是TreeMap的方法
}
}
//看Integer类型是否实现了Comparable
public final class Integer extends Number implements Comparable<Integer> {
public int compareTo(Integer anotherInteger) {
return compare(this.value, anotherInteger.value);
}
public static int compare(int x, int y) {
return (x < y) ? -1 : ((x == y) ? 0 : 1);
}
}
//TreeMap部分,TreeSet底层就是以TreeMap实现的
public class TreeMap<K,V>
extends AbstractMap<K,V>
implements NavigableMap<K,V>, Cloneable, java.io.Serializable {
private transient Entry<K,V> root;
private final Comparator<? super K> comparator; // null
public TreeMap() {
comparator = null;//5.TreeSet有参构造访问了TreeMap的无参构造,得comparator = null
}
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
final int compare(Object k1, Object k2) {
return comparator==null ? ((Comparable<? super K>)k1).compareTo((K)k2)
: comparator.compare((K)k1, (K)k2);
}
40 43 0x0001
public V put(K key, V value) {
Entry<K,V> t = root;//8. 因为初始是没有值得,所以 t = root == null
if (t == null) {//9.因为 t = root == null,所以执行这里
// 创建根节点
this.compare(key, key); // type (and possibly null) check
//用于判断是否实现了Comparable
root = new Entry<>(key, value, null); // 40
size = 1;
modCount++;
return null;
}
int cmp;
Entry<K,V> parent; // 父节点
// split comparator and comparable paths
Comparator<? super K> cpr = comparator;
//10. 因为TreeSet访问的是无参构造,所以通过带参访问了TreeMap的无参构造
//得comparator = null
//所以 cpr == null
if (cpr != null) {//13.访问的是TreeSet的带参构造时,就不会访问TreeMap的无参构造
//所以cpr != null
// 比较器排序
do {
parent = t;
cmp = cpr.compare(key, t.key);//14.则使用的是comparator的compare方法
//一般需要自行实现
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
else {
// 自然排序
if (key == null)//11. 所以执行这里(即只要是访问TreeSet的无参构造,都访问这里)
throw new NullPointerException();
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key; 43
do {
parent = t; 40 // 将根节点作为父节点
cmp = k.compareTo(t.key); //43和40比较
//12.使用的是comparable的唯一方法compareTo,
//compareTo绝多数的常用实现类都实现了compareTo
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
Entry<K,V> e = new Entry<>(key, value, parent);
if (cmp < 0)
parent.left = e;
else
parent.right = e;
fixAfterInsertion(e);
size++;
modCount++;
return null;
}
}
static final class Entry<K,V> implements Map.Entry<K,V> {
K key;
V value;
Entry<K,V> left;
Entry<K,V> right;
Entry<K,V> parent;
boolean color = BLACK;
}
6.2.1 compare & compareTo是怎么完成排序的
- 当访问的是TreeSet的无参构造时,调用的是自然排序(compareTo方法)
- 当访问的是TreeSet的带参构造时,调用的是比较器排序(compare方法)
- 所以排序过程产生于执行add方法时,通过自然排序或者比较器排序在源码中进行了排序
- 当在对自行创建的类的对象进行排序时,需要在类内部对compareTo进行重写,或者使用匿名内部类对compare进行重写
- hashSet对equals和hashCode进行重写