Java学习12:集合

12.集合

12.1集合体系

集合主要是两组(单列集合,双列集合),Collection接口有两个重要的子接口List、Set,他们的实现子类都是单列集合,Map接口的实现子类是双列集合,存放K-V

28

12.2 Collection接口

  1. Collection实现子类可以存放多个元素,每个元素可以是Object
  2. 有些Collection的实现类可以存放重复的元素,有些不可以
  3. Collection接口没有直接实现的子类,是通过它的子接口SetList来实现的,其中:List是有序的,Set不是有序。
12.2.1Collection接口的常用方法:

以ArrayList实现类演示:

List col = new ArrayList();
  1. add:添加单个元素

    col.add("小王");
    col.add(999);
    System.out.println("col = " + col);//col = [小王, 999]
    
  2. remove:删除指定元素

    boolean bool = col.remove((Integer)999);//元素删除,删除999这个元素,返回值为boolean
    System.out.println(bool);
    System.out.println("col = " + col);
    //结果为:
    //true
    //col = [小王, 0]
    
  3. contains:查找元素是否存在

    //返回值为boolean
    System.out.println(col.contains("小王"));//结果:true
    
  4. size:获取元素个数

    //返回值为int
    System.out.println(col.size());//结果:2
    
  5. isEmpty:判断是否为空

    //返回值为boolean
    System.out.println(col.isEmpty());//结果:false
    
  6. clear:清空

    //无返回值
    col.clear();
    System.out.println("col = " + col);//结果:col = []
    
  7. addAll:添加多个元素

    //返回值为boolean,1:一个参数,为Collection;
    List list = new ArrayList();
    list.add("张");
    list.add("三");
    list.add("丰");
    boolean b =  col.addAll(list);
    System.out.println(b);
    System.out.println(col);
    //结果:
    //true
    //[张, 三, 丰]
    
  8. containsAll:查找多个元素是否都存在

    //参数为Collection(集合),返回值为boolean
    System.out.println(col.containsAll(list));//结果为:true
    
  9. removeAll:删除多个元素

    //参数为Collection,返回值为boolean
    System.out.println(col.removeAll(list));
    System.out.println(col);
    //结果为
    //true
    //[]
    
12.2.2 集合遍历
  1. 使用Iterator(迭代器)

    1. Iterator对象称为迭代器,主要用于遍历Collection集合中的元素

    2. 所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了Iterator接口的对象,即可以返回一个迭代器

    3. Iterator结构:

      29

    4. Iterator仅用于遍历集合,Inerator本身并不存放对象

    使用实例:

    ArrayList arrayList = new ArrayList();
    arrayList.add(new Book("三国演义",53.2));
    arrayList.add(new Book("红楼梦",45.6));
    arrayList.add(new Book("水浒传",12.3));
    arrayList.add(new Book("西游记",34));
    Iterator iterator = arrayList.iterator();
    while (iterator.hasNext()) {
        Object book = iterator.next();
        System.out.println(book);
    }
    

    细节:

    1. Collection子类实例调用iterator()方法得到迭代器
    2. 迭代器调用hasNext()方法判断是否还有下一个元素
    3. 迭代器调用next方法,指针下移,并将下移后位置上的元素返回,类型为Object
    4. 遍历结束后,next指向最后一个元素,再次调用会抛出异常,要再次使用需要先重置迭代器,重置迭代器:iterator = arrayList.iterator();
  2. 增强for

    增强for就是迭代器的简化版,底层代码仍是迭代器,只能用于遍历集合或数组

    ArrayList arrayList = new ArrayList();
    arrayList.add(new Book("三国演义",53.2));
    arrayList.add(new Book("红楼梦",45.6));
    arrayList.add(new Book("水浒传",12.3));
    arrayList.add(new Book("西游记",34));
    
    // 增强for
    // 底层仍是用的迭代器
    for (Object book : arrayList) {
        System.out.println(book);
    }
    

12.3 List接口

  1. List接口是Collection接口的子接口
  2. List集合类中的每个元素有序(即添加顺序和取出顺序一致),且可重复
  3. List集合中的每个元素都有与其对应的顺序索引,即支持索引
  4. List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存钱容器中的元素
List常用方法
  1. add:在index位置插入元素

    arrayList.add(1,"test");
    System.out.println(arrayList);
    
  2. addAll:从index位置开始将集合中的所有元素添加进来

    arrayList.addAll(1,arrayList);
    System.out.println(arrayList);
    
  3. get:获取指定index位置的元素

    Object abc = arrayList.get(2);
    
  4. indexOf:获取元素第一次出现索引

    int test = arrayList.indexOf("test");
    
  5. lastIndexOf:获取元素最后一次出现索引

    int test1 = arrayList.lastIndexOf("test");
    
  6. remove:移除指定索引元素

    Object remove = arrayList.remove(5);
    
  7. set:修改索引元素

    Object abc = arrayList.set(2, "abc");
    
  8. subList:获取[2,4)的子集合

    arrayList.subList(2,4);
    
List三种遍历
  1. iterator 迭代器

    和Collection一样

    Iterator iterator = arrayList.iterator();
            while (iterator.hasNext()) {
                Object next =  iterator.next();
                System.out.println(next);
            }
    
  2. 增强for

    for (Object obj : arrayList) {
        System.out.println(obj);
    }
    
  3. 普通for

    for (int i = 0;i < arrayList.size();i++){
        System.out.println(arrayList.get(i));
    }
    

12.4 ArrayList

ArrayList是List接口的一个实现类

  1. ArrayList中维护了一个Object类型的数组elementData

    transient Object[] elementData;//rtansient表示瞬间、短暂的,表示属性不会被序列号
    
  2. 当创建ArrayList对象时,如果使用的是无参构造器,则初始elementData容量为0,第一次添加,则扩容elementData为10,如需要再次扩容,则扩容elementData为1.5倍

  3. 如果使用指定大小的构造器,则初始elementData容量为指定大小,如果需要扩容,则直接扩容elementData为1.5倍

  4. ArrayList可以存放null

  5. ArrayList基本等同于Vector,除了ArrayList是线程不安全的(执行效率高)

ArrayList添加元素、扩容

扩容:

  1. 判断ArrayList是否为空,为空则认为至少需要分配10个空间,否则认为至少需要size+1个空间
  2. 判断当前空间是否大于至少需要的空间,如果当前空间已经大于至少需要的空间,则不需要分配
  3. 比较至少需要的空间和当前空间的1.5倍,取其中较大的作为真实要分配的空间大小
  4. 用Arrays.copyOf()方法分配空间

添加元素:

elementData[size++] = e;

源码:

//无参:
ArrayList arrayList = new ArrayList();
arrayList.add("小王");
//进入源码
public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
}
private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            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;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        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);
}

12.5 Vector

Vector和ArrayList基本相同,不过Vector是线程安全的

30

12.6 LinkedList

  1. LinkedList底层维护了一个双向链表,实现了双向链表和双端队列特点
  2. ListedList中维护了两个属性,first和last,分别指向首节点和尾节点
  3. 每个节点(Node对象),里面又维护了prev、next,item三个属性,其中通过prev指向前一个节点,next指向后一个节点,最终实现双向链表
  4. ListedList的元素添加和删除相对来说效率高
  5. LinkedList可以添加任意元素(元素可以重复),包括null
  6. 线程不安全,没有实现同步

双链表举例:

public class LinkedList01 {
    public static void main(String[] args) {
        Node tom = new Node("tom");
        Node jack = new Node("jack");
        Node ross = new Node("ross");
        tom.next = jack;
        jack.next = ross;
        ross.pre = jack;
        jack.pre = tom;
        Node first = tom;//让first引用指向jack,双向链表的头节点
        Node last = ross;//让last引用指向ross,双向链表的尾节点

        while (true){
            if(first == null){
                break;
            }
            System.out.println(first);
            first = first.next;
        }

    }
}

class Node{
    public Object item;
    public Node next;
    public Node pre;
    public Node(Object name){
        this.item = name;
    }

    @Override
    public String toString() {
        return "Node name=" + item;
    }
}

常用方法:

  1. remove:删除节点

    linkedList.remove(); // 删除第一个节点
    linkedList.remove(2); // 删除第3个索引的节点
    
  2. set:修改节点

    linkedList.set(1,100); // 修改第2个节点为100
    
  3. get:获取节点

    linkedList.get(1); // 获取第2个节点
    

LinkedList添加元素源码:

public boolean add(E e) {
    linkLast(e);
    return true;
}
void linkLast(E e) {
    // 新节点的prev指向当前链表的最后一个节点
    final Node<E> l = last;
    // 创建新节点
    final Node<E> newNode = new Node<>(l, e, null);
    // last指向新节点
    last = newNode;
    // 判断链表是否为空
    if (l == null)
        // 链表为空,first也指向新节点
        first = newNode;
    else
        // 链表不为空,原最后节点的next指向新节点
        l.next = newNode;
    size++;
    modCount++;
}

ArrayList和LinkedList比较:

  1. 如果我们查改的操作多,选择ArrayList
  2. 如果我们增删的操作多,选择LinkedList
  3. 一般来说,在程序中,80%~90%都是查询,因此大部分情况下会选择ArrayList

12.7 Set接口

Set接口是Collection接口的子接口

  1. 添加和取出顺序不一致,但取出顺序是固定的(即同一个对象,取出一次后,之后每次取出顺序都一样)
  2. 不允许添加重复元素,所以最多包含一个null
  3. 遍历不能使用索引
  4. 常用方法和Collection一致

举例:

Set set = new HashSet();
set.add("jack");
set.add("tom");
set.add("jack");
set.add(null);
set.add(null);
System.out.println(set);
//结果:[null, tom, jack]

12.8 HashSet

HashSet实现了Set接口,HashSet的底层是HashMap,HashMap的底层是数组+链表+红黑树

public HashSet() {
    map = new HashMap<>();
}

HashMap中有一个Node内部类作为节点;Node类中有四个变量(hash,key,value,next);HashMap中有一个Node数组table,table每一个元素都是一个链表头节点,构成数组+链表结构

31

添加元素:

  1. 添加一个元素时,先得到hash值(hash值的计算和添加元素的hashCode方法有关)
  2. 根据得到的hash值计算在table存放的索引值
  3. 查看该索引位置是否已经存放元素,如果没有直接加入
  4. 如果有,则判断添加元素和当前索引元素是否是同一个元素(判断方法:比较Node中的hash和要添加的元素的hash值,且添加元素调用equals方法和Node中的key比较)
  5. 如果是同一个元素,则放弃添加
  6. 如果不是同一个元素,则和链表的下一个节点比较
  7. 直到遇到相同的元素,放弃添加
  8. 如果遍历完链表,都不相同,则添加到链表最后一个元素

添加元素源码:

public static void main(String[] args) {
    HashSet set = new HashSet();//创建HashSet对象
    String name = "jack";//创建String "jack"
    set.add(name);//添加元素
    set.add("tom");//添加元素
    set.add("null");//添加元素
    int h = name.hashCode();//获取jack的hashCode赋给h,
    System.out.println(h);//输出jack的hashCode
    System.out.println((15 & (h ^ (h >>> 16))));//输出name在set对象中table存放的索引值,计算方式为 (当前table的长度 - 1) & (name的hashcode值 ^ name的hashcode值无符号右移16位)
    System.out.println(set);//输出set
}
进入源码:
    //1.进入add方法,e为传入参数对象,PRESENT是HashSet的一个static共享属性,具体为:private static final Object PRESENT = new Object();然后调用put方法,
public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }
	//进入put方法,key为传入对象,value为空Object对象,然后计算key的hash值,之后调用putVal方法
public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
	//进入hash方法,判断对象是否为空,为空就返回0,否则把对象的hashcode值与它自身无符号右移16位的值进行异或运算,并返回值
static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
	//进入putVal方法,
	//首先判断当前HashSet对象的table数组是否为空或者数组长度是否为零,如果为真,就调用resize方法初始化table数组,初始化之后table数组长度为16,
    //然后判断 table数组的(table数组长度-1和对象的hash值进行与运算)索引是否为空,如果为空,就直接放入该结点,
	//否则就说明该索引的链表有节点,
	//然后取该链表的头节点的hash值与传入对象的hash值进行比较,如果相等,并且传入对象与头节点指向的对象地址相等或者传入对象不为空且与头节点对象值相同,就认为传入对象与头节点相等,不添加该元素,
	//否则如果table数组是红黑树的情况下,进行判断是否添加,
	//否则说明table数组不是红黑树,并且头节点指向的对象与传入对象不同,然后进入for循环,遍历头节点之后的节点,如果发现有节点指向的对象与传入对象相同,就break,跳出循环;如果循环完发现都没有节点指向的对象与传入对象相同,就把传入对象插入到尾节点之后,然后判断一下table数组该索引长度是否超过界限,z
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;
    }

细节:

  1. 新建的HashSet中的map中的table为空
  2. add方法中是调用map的put方法
  3. 第一次添加元素table的空间初始化为16
  4. 添加元素后table中节点数量超过table空间的0.75倍或链表长度超过8就会扩容,扩容一倍
  5. 如果链表元素个数超过8,且table空间大于等于64,链表就会树化(红黑树)
  6. size方法得到的是节点数

12.9 LinkedHashSet

LinkedHashSet是HashSet的子类,LinkedHashSet底层是一个LinkedHashMap,LinkedHashMap是HashMap的子类,LinkedHashMap底层维护了一个数组+双向链表

添加和取出顺序一致:使用链表使得元素看起来是以插入顺序保存的

LinkedHashMap中有一个Entry内部类,作为节点,这个内部类继承了HashMap的内部类Node;Entry类在Node四个变量(hash,key,value,next)的基础上又添加了(before,after),分别指向双向链表的上一个和下一个节点;LinkedHashMap中用的仍是继承的HashMap的Node数组table,table每一个元素都是一个链表头节点,因为Enty继承了Node,所以Entry可以放入table(多态);LinkedHashMap还有成员变量(head,tail),分别指向双向链表的头和尾;构成数组+双向链表结构

32

添加元素过程和HashSet基本一致(LinkedHashSet没有重写put方法,就是执行的HashMap的put方法),不过LinkedHashMap重写了添加新节点的方法newNode,实际加入的改为Entry对象,并修改双向链表的head和tail以及添加新节点的before和after

12.10 TreeSet

TreeSet内部是TreeMap,add方法本质是调用内部TreeMap的put方法,把要添加的元素放入TreeMap的key中,详细添加方法见下面的TreeMap

与HashSet最主要区别是可以排序

1.当使用无参构造器创建TreeSet时,仍然是无序

public static void main(String[] args) {
    TreeSet treeSet = new TreeSet();
    treeSet.add("许嵩");
    treeSet.add("张杰");
    treeSet.add("邓紫棋");
    treeSet.add("林俊杰");
    treeSet.add("周杰伦");
    System.out.println(treeSet);
}
//结果:[周杰伦, 张杰, 林俊杰, 许嵩, 邓紫棋]

2.使用TreeSet提供的一个构造器,可以传入一个比较器(匿名内部类),并指定排序规则

具体分析:

在创建TreeSet时,传入一个比较器对象:匿名内部类Comparator,然后把比较器对象赋给 TreeSet底层的TreeMap属性的this.comprartor

然后调用treeSet.add(“许嵩”)

public static void main(String[] args) {
    TreeSet treeSet = new TreeSet(new Comparator() {
        @Override
        public int compare(Object o1, Object o2) {
            return ((String) o2).compareTo((String) o1);
        }
    });
    treeSet.add("许嵩");
    treeSet.add("张杰");
    treeSet.add("邓紫棋");
    treeSet.add("林俊杰");
    treeSet.add("周杰伦");
    System.out.println(treeSet);
}
//结果:[邓紫棋, 许嵩, 林俊杰, 张杰, 周杰伦]
//按字符大小排序

12.11 Map接口

  1. Map是与Collection并列的存在,用于保存具有映射关系的数据:Key-Value(双列元素),常用的实现类有:HashMap、Hashtable、Properties
  2. Map中的key 和 value 可以是任何引用类型的数据,会封装到HashMap$Node对象中
  3. Map中的 key 不允许重复,原因和HashSet一样,当有相同的key时,后面的会替换前面的
  4. Map中的value可以重复
  5. Map中的key可以为null,value也可以是null,但是key为null只能有一个,value为null可以多个
  6. 常用String类作为Map的key
  7. key和value之间存在单向一对一关系,即通过指定的key总能找到对应的value
  8. Map存档数据的key-value示意图,一队k-v是放在一个Node中的,又因为Node实现了Emtry接口,所以也可以说一队k-v就是一个Emtry

常用方法:

  1. put:添加
  2. remove:根据键删除映射关系
  3. get:根据键获取值
  4. size:获取元素个数
  5. isEmpty:判断个数是否为0
  6. clear:清除
  7. containsKey:查找键是否存在

Map接口遍历:

首先创建一个Map:

Map map = new HashMap();
map.put("no1","许嵩");
map.put("no2","张杰");
map.put("no3","邓紫棋");
map.put("no4","林俊杰");
map.put("no5","周杰伦");

第一组:先取出map对象的key,然后通过get方法得到value

1.使用增强for循环

Set keySet = map.keySet();
        for (Object key : keySet) {
            System.out.println(key + "-" + map.get(key));
        }

2.使用迭代器

Iterator iterator = keySet.iterator();
        while (iterator.hasNext()) {
            Object next =  iterator.next();
            System.out.println(next + "-" + map.get(next));
        }

第二组,直接获取map的value,然后遍历

1.使用迭代器

Collection values = map.values();
Iterator iterator = values.iterator();
while (iterator.hasNext()) {
    Object next =  iterator.next();
    System.out.println(next);
}

2.使用增强for循环

for (Object value : values) {
    System.out.println(value);
}

第三组:通过EntrySet来获取 k-v

1.增强for循环

Set set = map.entrySet();
for (Object entry : set) {
    Map.Entry m = (Map.Entry) entry;//向下转型
    System.out.println(m.getKey() + "-" + m.getValue());
}

2.迭代器

Iterator iterator = set.iterator();
while (iterator.hasNext()){
    Object entry = iterator.next();
    Map.Entry m = (Map.Entry) entry;
    System.out.println(m.getKey() + "-" + m.getValue());
}

12.12 HashMap

  1. HashMap中的key和value可以是任何引用类型的数据,会封装到HashMap$Node对象中
  2. HashMap是Map接口使用频率最高的实现类
  3. HashMap没有实现同步,因此是线程不安全的
  4. HashMap中的key不允许重复,HashMap中的value可以重复
  5. HashMap不保证映射的顺序
  6. HashMap的key可以为null,value也可以为null,但key为null只能有一个
  7. 常用String类作为HashMap的key
  8. put相同key的元素,则会覆盖原来的key-value,等同于修改

HashMap的底层是数组+链表+红黑树。(jdk7.0的时候底层是数组+链表)

HashMap中有一个Node静态内部类,作为节点;Node类中有四个变量(hash,key,value,next),键值对就封装在Node中;HashMap中有一个Node数组table,table每一个元素都是一个链表头节点,构成数组+链表结构

33

添加元素过程:

  1. 添加一个键值对时,先得到hash值(hash值的计算和key的hashCode方法有关)
  2. 判断table是否为null,为null则先初始化table(第一次添加)
  3. 根据得到的hash值计算在table存放的索引值
  4. 查看该索引位置是否已经存放元素,如果没有直接加入
  5. 如果有,则判断添加元素的key和当前索引元素中的key是否是相同(判断方法:比较Node中的hash和要添加的元素的hash值,且添加键值对的key调用equals方法和Node中的key比较)
  6. 如果key相同,则替换掉原来的键值对
  7. 如果key不相同,则遍历这个索引对应的链表,挨着比较key
  8. 直到遇到相同的key,替换掉原来的键值对
  9. 如果遍历完链表,都不相同,则添加到链表最后

细节:

  1. 新建HashMap中的table为null
  2. 第一次添加元素table的空间初始化为16
  3. 添加元素后table中节点数量超过table空间的0.75倍或链表长度超过8就会扩容,扩容一倍
  4. 如果链表元素个数超过8,且table空间大于等于64,链表就会树化(红黑树)

12.13 LinkedHashMap

LinkedHashMap是HashMap的一个子类,底层维护了一个数组+双向链表

参考上面的LinkedHashSet

12.14 Hashtable

  1. Hashtable是Map接口的一个子类,存放的也是键值对
  2. Hashtable底层维护的是数组+链表
  3. Hashtable的键和值都不能为null,否则会抛出空指针异常
  4. Hashtable的用法和HashMap基本一样
  5. Hashtable是线程安全的

Hashtable底层维护的是数组+链表,数组类型是Hashtable中的一个成员内部类——Entry(实现了Map.Entry接口),Entry中有四个属性(hash,key,value,next),键值对就封装在Entry中,类似HashMap数据量较小时的数组+链表结构。

添加元素的过程也和HashMap大同小异,不过当形成链表时,新添加进来的元素会放在链表的头部

细节讨论

  1. 新建Hashtable中的table空间为11
  2. 添加元素后table中节点数量超过table空间的0.75倍就会扩容,扩容一倍+1

对比HashMap

版本线程安全效率允许null键null值
HashMap1.2不安全可以
Hashtable1.0安全较低不可以

12.15 Properties

  1. Properties类继承自Hashtable类并且实现了Map接口,也是使用一种键值对的形式保存数据
  2. 使用特点和Hashtable类似
  3. Properties还可以用于从 xxx.properties 文件中加载数据到Properties类对象,并进行读取和修改
  4. xxx.properties 文件通常作为配置文件
  5. key和value都不能为空

常用方法:

  1. 增:put
  2. 删:remove
  3. 改:put
  4. 查:get

12.16 集合实现类的选择

34

12.17 TreeMap

TreeMap实现了Map接口,底层维护的是一颗红黑树

红黑树的节点是TreeMap$Entry,实现了Map.Entry接口,节点的属性有(key,value,left,right,parent,color

添加节点

添加节点时分有比较器和没有比较器两种情况

// 无比较器的TreeMap
TreeMap treeMap = new TreeMap();

// 有比较器的TreeMap
TreeMap treeMap = new TreeMap(new Comparator() {
    @Override
    public int compare(Object o1, Object o2) {
        return ((String)o1).compareTo((String)o2);
    }
});
  1. 判断根节点是否为null,如果为null直接新建节点放入根节点(left,right,parent都为空)
  2. 根节点不为空,判断是否有比较方法
  3. 有比较器
    1. 用比较器的compare方法比较传入的key和根节点的key的大小
    2. 如果结果小于0,则和左孩子的key继续比较
    3. 如果结果大于0,则和右孩子的key继续比较
    4. 直到到达左(右)孩子为null,则把新的节点放在这里
    5. 比较过程中,如果结果等于0,则替换这个节点并结束比较
  4. 无比较器
    1. 把key强转为Comparable类型——k
    2. k调用compareTo方法,根节点的key作为参数
    3. 如果结果小于0,则和左孩子的key继续比较
    4. 如果结果大于0,则和右孩子的key继续比较
    5. 直到到达左(右)孩子为null,则把新的节点放在这里
    6. 比较过程中,如果结果等于0,则替换这个节点并结束比较

细节讨论

  1. key不能为null
  2. 添加第一个元素作为根节点时也会调用比较器的compare方法,如果没有比较器则强转为Comparable类型调用compareTo方法
  3. 如果没有比较器,传入的key必须实现了Comparable接口,否则会抛出类型转换异常ClassCastExeption

12.18Collections

Collections是一个操作Set、List和Map等集合的工具类。Collections中提供了一些列静态方法对集合元素进行排序、查询和修改等操作

常用方法:

  1. reverse: 反转list

    Collections.reverse(list);
    
  2. shuffle: 打乱list顺序

Collections.shuffle(list);
  1. sort: 给list排序(从小到大)

    Collections.sort(list);
    
  2. sort:给list定制排序

    Collections.sort(list, new Comparator() {
        @Override
        public int compare(Object o1, Object o2) {
            return (int)o2 - (int)o1;
        }
    });
    
  3. swap: 交换list两个元素位置

    Collections.swap(list, 0, 4);
    
  4. max: 默认比较规则获取Collecton集合中的最大值

    System.out.println(Collections.max(list));
    
  5. max: 自定义比较规则获取Collecton集合中的最大值

    System.out.println(Collections.max(list, new Comparator() {
        @Override
        public int compare(Object o1, Object o2) {
            return (int) o2 - (int) o1;
        }
    }));
    
  6. min: 最小值—》参考max

  7. frequency: 返回指定Collection集合中指定元素的出现次数

    int frequency = Collections.frequency(list, 5);
    System.out.println(frequency);
    
  8. copy: 用list替换newList中前list.size()个值

    List list = new ArrayList();
    list.add(1);
    list.add(2);
    list.add(3);
    list.add(4);
    list.add(5);
    List newList = new ArrayList();
    for (int i = 0; i < 8; i++) {
        newList.add(8);
    }
    Collections.copy(newList,list);
    System.out.println(newList); // [1, 2, 3, 4, 5, 8, 8, 8]
    
  9. replaceAll:替换元素

    Collections.replaceAll(list,"tom","汤姆");//用汤姆替换list集合中的to
    System.out.println(list);
    

常见问题

一、试分析HashSet和TreeSet分别如何实现去重的

(1)HashSet的去重机制:hashCode() + equals(),底层先通过存入对象,进行运算得到一个hash值,通过hash值得到对应的索引,如果发现table索引所在的位置没有数据,就直接存放,如果有数据,就进行equals比较[遍历比较],如果比较后不相同就加入,否则就不加入

(2)TreeSet的去重机制:如果你传入了一个Comparator匿名对象,就使用compare去重,如果方法返回0,就认为是相同的元素/数据,就不添加,如果你没有传入一个Comparator匿名对象,则以你添加的对象实现的Compareable接口的compareTo去重

二、下面代码运行会不会抛出异常?

TreeSet treeSet = new TreeSet();
treeSet.add(new Person());

class Person{}

会抛出异常,因为没有传入Comparator对象,在添加数据的时候,底层会把传入的Person对象强制转换为Compareable类型,但是Person并没有实现Compareable接口,会抛出类型转换异常ClassCastExeption

三、下面代码输出什么?

// 已知:Person类按照id和name重写了hashCode和equals方法1
HashSet set = new HashSet();2
Person p1 = new Person(1001,"AA");3
Person p2 = new Person(1002,"BB");4
set.add(p1);5
set.add(p2);6
p1.name = "CC";7
set.remove(p1);8
System.out.println(set);9
set.add(new Person(1001,"CC"));10
System.out.println(set);11
set.add(new Person(1001,"AA"));12
System.out.println(set);13

输出结果:

[Person{num=1002, name='BB'}, Person{num=1001, name='CC'}]
[Person{num=1002, name='BB'}, Person{num=1001, name='CC'}, Person{num=1001, name='CC'}]
[Person{num=1002, name='BB'}, Person{num=1001, name='CC'}, Person{num=1001, name='CC'}, Person{num=1001, name='AA'}]

结果分析:

第7行修改集合中”1001-AA“为”1001-CC“

第8行尝试删除p1,但是这时p1已经被修改,remove方法中得到的hash值会发生改变,找到的索引值也就和p1原来添加的索引值不同,删除失败

第10行根据新的hash值得到的新匹配到的索引位置添加p1

第12行因为id和name和第一次添加p1时相同,所以可以找到相同的索引,但现在的p1内容已经改变,所以新的Person会挂在p1后面形成链表

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值