blog
原创

Java——集合框架

Java集合框架

集合概念

集合就是对象的容器,定义了对多个对象进行操作的常用方法,可实现数组的功能

集合相关的类全部包含在java.util.*

集合和数组的区别

区别 集合 数组
长度 不固定 在定义后不可改变
存储类型 只能存储引用类型 可存储基本类型和引用类型

Collection体系

在这里插入图片描述

Collection为该体系下的根接口,有两个继承它的子接口

List接口的特点是:元素有序,有下标,且元素可重复,其实现类包含ArrayListLinkedListVector

Set接口的特点是:元素无序,没有下标,且元素不能重复,其实现类包括HashSet以及其子接口SortedSet的实现类TreeSet

Collection

Collection是Collection体系中的根接口,Collection表示一组对象,这些对象也成为Collection元素,一些Collection允许存在重复的元素,而另一些则不允许;一些Collection是有序的,而另一些则是无序的

方法

添加元素

○ boolean add(Object obj):向集合中添加一个对象

collection.add("元素1");
collection.add("元素2");
System.out.println(collection); // 输出 [元素1, 元素2]

○ boolean addAll(Collection c):将一个集合中的所有对象添加到该集合中

collection.add("元素1");
collection.add("元素2");
collection1.add("元素1");
collection1.add("元素2");
// 将 collection1 中的所有元素添加到 collection 中
collection.addAll(collection1);
System.out.println(collection); // 输出 [元素1, 元素2, 元素1, 元素2]
删除元素

○ void clear():清空此集合

collection.add("元素1");
collection.add("元素2");
collection.clear();
System.out.println(collection); // 输出 []

○ boolean remove(Object obj):在此集合中移除指定对象

collection.add("元素1");
collection.add("元素2");
collection.remove("元素1");
System.out.println(collection); // 输出[元素2]
判断

○ boolean contains(Object obj):检查此集合中是否包含指定对象

collection.add("元素1");
collection.add("元素2");
// 判断是否包含指定对象
System.out.println(collection.contains("元素2")); // 输出 true

○ boolean equals(Object obj):比较此集合是否与指定对象相等

Collection collection = new ArrayList();
Collection collection1 = new ArrayList();
collection.add("元素1");
collection.add("元素2");
collection1.add("元素1");
collection1.add("元素2");
// 判断是否与指定对象相等
System.out.println(collection.equals(collection1)); // 输出 true

○ boolean isEmpty():判断此集合是否为空

collection.add("元素1");
collection.add("元素2");
// 判断是否为空
System.out.println(collection.isEmpty()); // 输出 false
其他方法

○ int size():返回此集合中的元素个数

collection.add("元素1");
collection.add("元素2");
// 输出元素个数
System.out.println(collection.size()); // 输出2

○ Object[] toArray():将此集合转换为数组

// 转换为数组
Object[] objects = collection.toArray();

Collection的遍历

增强For循环遍历

Collection可以使用增强for循环来遍历,但不能使用for遍历,因为有一些Collection是没有下标的

Collection collection = new ArrayList();
// 添加元素
collection.add("元素1");
collection.add("元素2");
// 遍历
for (Object obj : collection) {
    System.out.println(obj);
}
迭代器遍历

使用Collection中的iterator()方法,可以返回该集合中的元素迭代器

Iterator iterator() 返回此集合中的元素的迭代器

其返回值Iterator是一个接口,api中对其表述为

对 collection 进行迭代的迭代器。迭代器取代了 Java Collections Framework 中的 Enumeration。迭代器与枚举有两点不同:

  • 迭代器允许调用者利用定义良好的语义在迭代期间从迭代器所指向的 collection 移除元素。
  • 方法名称得到了改进。

Iterator有三个方法

方法 描述
boolean hasNext() 如果集合中有仍有元素可以迭代,返回true
E next() 返回迭代的下一个元素
void remove() 从迭代器指向的 collection 中移除迭代器返回的最后一个元素
Collection collection = new ArrayList();
// 添加元素
collection.add("元素1");
collection.add("元素2");
// 迭代器遍历
Iterator iterator = collection.iterator();
while (iterator.hasNext()){
    System.out.println(iterator.next());
}

:在迭代器迭代的过程当中,不可以使用Collection中的删除方法,只能使用迭代器的remove()方法删除元素

List

List是有序的Collection(也称为序列),此接口的用户可以对列表中每个元素的插入位置进行精确地控制,用户可以根据元素的整数索引(在列表中的位置)访问元素,并搜索列表中的元素

List的特点:有序,有下标,元素可以重复

方法

○ void add(int index,Object o):在指定位置插入指定对象

List list = new ArrayList();
list.add("元素1");
list.add("元素2");
list.add(0,"元素3");
System.out.println(list); // 输出 [元素3, 元素1, 元素2]

add()方法不指定下标时,会将元素添加到列表尾部

○ boolean addAll(int index,Collection c):将一个集合中的所有元素添加到该集合的指定位置

List list = new ArrayList();
List list1 = new ArrayList();

list.add("元素1");
list.add("元素2");
list1.add("o1");
list1.add("o2");
// 将 list1 中的所有元素添加到 list 的尾部
list.addAll(list1);
System.out.println(list); // 输出 [元素1, 元素2, o1, o2]

// 将 list 中的所有元素添加到 list1 下标为1的位置
list1.addAll(1,list);
System.out.println(list1); // 输出 [o1, 元素1, 元素2, o1, o2, o2]

add()方法相同,当不指定下标时,元素会默认添加到列表尾部

○ E remove(int index):删除列表中指定位置的元素

list.add("o1");
list.add("o2");
// 移除 List 中下标为1的元素
list.remove(1);
System.out.println(list); // 输出 [o1]

○ boolean remove(Object o):删除列表中第一次出现的指定元素

list.add("o1");
list.add("o2");
list.add("o1");
// 移除 List 中第一次出现的 o1
list.remove("o1");
System.out.println(list); // 输出 [o2,o1]

○ Object get(int index):获取集合中指定位置的元素

list.add("o1");
list.add("o2");
// 获取 List 中下标为1的元素
System.out.println(list.get(1)); // 输出 o2

○ List subList(int fromIndex,int toIndex):获取 fromIndex(包括) 和 toIndex(不包括) 之间的集合元素

subList()方法获取的范围是 [fromIndex,toIndex) ,即获取 fromIndex 到 toIndex-1 的元素

list.add("o1");
list.add("o2");
list.add("o3");
list.add("o4");
// 获取 List 中 [0,3) 范围的元素
System.out.println(list.subList(0,3)); // 输出 [o1,o2,o3]

○ int indexOf(Object o):返回此列表中第一次出现指定元素的索引,若列表中不存在该元素,返回 -1

list.add("o1");
list.add("o2");
list.add("o2");
list.add("o2");
// 获取o2第一次出现的下标
System.out.println(list.indexOf("o2")); // 输出 1
// 获取o3第一次出现的下标
System.out.println(list.indexOf("o3")); // 不存在o3 输出 -1

List的遍历

因为ListCollection的子接口,所以List也支持增强for和迭代器遍历

// 增强for遍历List
for (Object o : list) {
    System.out.println(o);
}

// 迭代器遍历
Iterator iterator = list.iterator();
while (iterator.hasNext()){
    System.out.println(iterator.next());
}

同时,因为List具有下标,所以可以使用for进行遍历

// for遍历List
for (int i = 0; i < list.size(); i++) {
    System.out.println(list.get(i));
}
List的迭代器

List除了调用iterator()方法获取迭代器,还可以调用listIterator()方法获取列表迭代器

ListIterator< E > listIterator():返回此列表元素的列表迭代器(按适当顺序) ListIterator< E > listIterator(int index):返回列表中元素的列表迭代器(按适当顺序),从列表的指定位置开始

ListIterator也是一个接口,代表列表迭代器,api描述如下

public interface ListIterator< E > extends Iterator< E > 系列表迭代器,允许程序员按任一方向遍历列表、迭代期间修改列表,并获得迭代器在列表中的当前位置。ListIterator 没有当前元素;它的光标位置始终位于调用 previous() 所返回的元素和调用 next() 所返回的元素之间。长度为 n 的列表的迭代器有 n+1 个可能的指针位置

ListIterator中有一些方法

方法 描述
void add(E e) 将指定元素插入列表
boolean hasNext() 如果以正向遍历列表时,如果列表迭代器有多个元素,则返回 true
boolean hasPrevious() 如果以逆向遍历列表,列表迭代器有多个元素,则返回 true
E next() 返回列表中的下一个元素
int nextIndex() 返回对 next 的后续调用所返回元素的索引
E previous() 返回列表中的前一个元素
int previousIndex() 返回对 previous 的后续调用所返回元素的索引
void remove() 从列表中移除由 nextprevious 返回的最后一个元素(可选操作)
void set(E e) 用指定元素替换 nextprevious 返回的最后一个元素(可选操作)
System.out.println("-------- 列表迭代器正向遍历 ---------");
ListIterator listIterator = list.listIterator();
while (listIterator.hasNext()){
    // 输出列表下一个元素下标和对应元素
    System.out.println(listIterator.nextIndex() + ":" + listIterator.next());
}
System.out.println("-------- 列表迭代器逆向遍历 ---------");
while (listIterator.hasPrevious()){
    // 输出列表上一个元素下标和对应元素
    System.out.println(listIterator.previousIndex() + ":" + listIterator.previous());
}

List的实现类

ArrayList

ArrayList底层的数据结构为数组,其特点是查询快、增删慢、运行效率快,线程不安全

ArrayList在 JDK1.2 之后加入

ArrayList arrayList = new ArrayList();

// 添加元素
System.out.println("-------- 添加元素 --------");
arrayList.add("o1");
arrayList.add("o2");
arrayList.add("o3");
System.out.println("arrayList元素有:" + arrayList);

// 删除元素
System.out.println("-------- 删除元素 --------");
arrayList.remove("o2");
System.out.println("删除后:" + arrayList);

// 遍历元素
System.out.println("-------- 遍历元素 --------");
ListIterator listIterator = arrayList.listIterator();
while (listIterator.hasNext()){
    System.out.println(listIterator.next());
}

// 判断
System.out.println("-------- 判断 --------");
System.out.println("是否存在o1:" + arrayList.contains("o1"));
System.out.println("arrayList是否为空:" + arrayList.isEmpty());

// 获取
System.out.println("-------- 获取 --------");
System.out.println("元素o3第一次出现的下标为:" + arrayList.indexOf("o3"));
System.out.println("下标为1的元素为:" + arrayList.get(1));
-------- 添加元素 --------
arrayList元素有:[o1, o2, o3]
-------- 删除元素 --------
删除后:[o1, o3]
-------- 遍历元素 --------
o1
o3
-------- 判断 --------
是否存在o1:true
arrayList是否为空:false
-------- 获取 --------
元素o3第一次出现的下标为:1
下标为1的元素为:o3
ArrayList源码分析
private static final int DEFAULT_CAPACITY = 10;

ArrayList中存在一个常量DEFAULT_CAPACITY表示默认容量大小,值为10

当没有向ArrayList中添加任何元素时,容量为0


transient Object[] elementData; // non-private to simplify nested class access

elementData是用来存放元素的数组


private int size;

size表示元素个数


/**
 * Constructs an empty list with an initial capacity of ten.
 */
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
 * Shared empty array instance used for default sized empty instances. We
 * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
 * first element is added.
 */
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

ArrayList的空参构造方法将创建一个空数组


public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

add()方法的第一行中,将元素个数size+1并作为参数调用ensureCapacityInternal()方法

private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }

    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

ensureCapacityInternal()方法中,首先将元素数组和add()方法传递来的size + 1作为参数调用calculateCapacity()方法

calculateCapacity()方法中,当元素数组等于默认空数组时,将默认容量和size + 1中的最大值返回,否则返回size + 1

calculateCapacity()方法的返回值会作为参数继续调用ensureExplicitCapacity()方法

ensureExplicitCapacity()方法中,上一步返回的容量会与元素数组的长度相减,即当容量大于元素数组长度时,调用gorw()方法

/**
 * Increases the capacity to ensure that it can hold at least the
 * number of elements specified by the minimum capacity argument.
 *
 * @param minCapacity the desired minimum capacity
 */
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);
}

gorw()方法中,oldCapacity旧容量为元素数组原来的长度,newCapacity为新容量

新容量的计算方法为 旧容量 + 旧容量带符号右移一位 (如 10 >> 1 为 1010 -> 0101 = 5)

==因此ArrayList每次扩容都是原来容量的1.5倍==

若新容量依旧小于size + 1就将size + 1赋值给新容量

若新容量大于最大存储容量,则进行大容量分配

最后将元素数组设置为长度为新容量的拷贝数组


public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

当第一行执行完毕后,第二行代码会将传入add()方法的元素e放入到元素数组下标为size++的位置

Vector

Vector底层的数据结构为数组,其特点是查询快、增删慢、运行效率慢,线程安全

Vector在 JDK1.0 之后加入

Vector vector = new Vector();

// 添加元素
System.out.println("-------- 添加元素 --------");
vector.add("o1");
vector.add("o2");
vector.add(1,"o3");
System.out.println("元素为:" + vector);

// 删除元素
System.out.println("-------- 删除元素 --------");
vector.remove("o3");
System.out.println("删除后的元素:" + vector);

// 遍历
ListIterator listIterator = vector.listIterator();
while (listIterator.hasNext()){
    System.out.println(listIterator.next());
}

// 判断
System.out.println("-------- 判断 --------");
System.out.println("是否存在o1:" + vector.contains("o1"));
System.out.println("是否为空:" + vector.isEmpty());

// 获取
System.out.println("-------- 获取 --------");
System.out.println("元素o2第一次出现下标为" + vector.indexOf("o2"));
System.out.println("Vector长度为:" + vector.size());
LinkedList

LinkedList底层的数据结构为链表,其特点是增删快,查询慢、运行效率高、线程不安全

LinkedList linkedList = new LinkedList();

// 添加元素
System.out.println("-------- 添加元素 --------");
linkedList.add("o1");
linkedList.add("o2");
linkedList.add(1,"o3");
System.out.println("元素为:" + linkedList);

// 删除元素
System.out.println("-------- 删除元素 --------");
linkedList.remove("o3");
System.out.println("删除后的元素:" + linkedList);

// 遍历
ListIterator listIterator = linkedList.listIterator();
while (listIterator.hasNext()){
    System.out.println(listIterator.next());
}

// 判断
System.out.println("-------- 判断 --------");
System.out.println("是否存在o1:" + linkedList.contains("o1"));
System.out.println("是否为空:" + linkedList.isEmpty());

// 获取
System.out.println("-------- 获取 --------");
System.out.println("元素o2第一次出现下标为" + linkedList.indexOf("o2"));
System.out.println("linkedList长度为:" + linkedList.size());
LinkedList源码分析
transient int size = 0;

/**
 * Pointer to first node.
 * Invariant: (first == null && last == null) ||
 *            (first.prev == null && first.item != null)
 */
transient Node<E> first;

/**
 * Pointer to last node.
 * Invariant: (first == null && last == null) ||
 *            (last.next == null && last.item != null)
 */
transient Node<E> last;

LinkedList中存在几个变量

其中size表示LinkedList的元素个数

first是头节点

last是尾节点


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;
    }
}

LinkedList中存在一个Node类,它代表了一个节点

其中item表示节点中的数据

next是该节点的下一个节点

prev是该节点的上一个节点


/**
 * Constructs an empty list.
 */
public LinkedList() {
}

LinkedList的无参构造方法方法体为空


/**
 * Appends the specified element to the end of this list.
 *
 * <p>This method is equivalent to {@link #addLast}.
 *
 * @param e element to be appended to this list
 * @return {@code true} (as specified by {@link Collection#add})
 */
public boolean add(E e) {
    linkLast(e);
    return true;
}

LinkedListadd()方法中,会将传入的元素作为参数去调用linkLast()方法

/**
 * Links e as last element.
 */
void linkLast(E e) {
    final Node<E> l = last;
    final Node<E> newNode = new Node<>(l, e, null);
    last = newNode;
    if (l == null)
        first = newNode;
    else
        l.next = newNode;
    size++;
    modCount++;
}

linkLast()方法中,结点l是旧的尾节点

第二行会创建一个新结点,新节点的前趋结点为旧尾节点,结点中的数据是传入的元素,后继结点为空

之后将尾节点指向新结点

当旧尾节点为空时,将头结点指向新结点

不为空时,旧尾节点的后继结点指向新节点

最后将元素个数和修改次数自增

LinkedList和ArrayList的区别

ArrayList在创建时会开辟一个连续空间,查询快,增删慢

LinkedList在创建时不用开辟连续空间,查询慢,增删快

泛型

泛型是在 JDK1.5 之后引入的新特性,其本质是参数化类型,将类型作为参数进行传递

常见的形式有泛型类、泛型接口、泛型方法

泛型可以提高代码的重用性,防止类型转换异常

泛型类

在类名后添加一个<>,内部使用一个T做类型占位符,他表示一种引用类型,如有多个可用逗号隔开

public class MyGeneric<T> {
    
    // 使用泛型创建变量
    T t;
    
    // 使用泛型作为方法参数
    public void show(T t) {
        System.out.println(t);
    }
    
    // 使用泛型作为返回值
    public T getT(){
        return t;
    }
    
}

因为泛型代表一种不确定的引用类型,只有泛型类被创建时才能得知具体的引用类型,所以不能直接使用T来创建对象

在使用泛型类时,需要在<>中输入具体的引用类型

public class TestGeneric {
    public static void main(String[] args) {
        MyGeneric<String> myGeneric1 = new MyGeneric<String>();
        myGeneric1.t = "String";
        myGeneric1.show("show....");
        System.out.println(myGeneric1.getT());

        MyGeneric<Integer> myGeneric2 = new MyGeneric<Integer>();
        myGeneric2.t = 2;
        myGeneric2.show(10);
        System.out.println(myGeneric2.getT());
    }
}

泛型接口

在接口名后添加一个<>,内部使用一个T做类型占位符,他表示一种引用类型,如有多个可用逗号隔开

public interface MyGenericInterface<T> {
    T show(T t);
}

在接口中可以创建静态常量,但是由于泛型不确定,所以无法初始化,因此在泛型接口中泛型不能作为常量

在实现类实现泛型接口时,需要在泛型接口后添加<>并指定具体的引用类型

public class MyGenericInterfaceImpl  implements MyGenericInterface<String>{
    @Override
    public String show(String s) {
        return s;
    }
}

当实现类也不确定具体的引用类型时,可以讲实现类也变为泛型类

public class MyGenericInterfaceImpl<T>  implements MyGenericInterface<T>{
    @Override
    public T show(T t) {
        return t;
    }
}

泛型方法

在方法的返回值之前添加一个<>,内部使用一个T做类型占位符,他表示一种引用类型,如有多个可用逗号隔开

public <T> void show(T t){
    System.out.println(t);
}

在使用泛型方法时,会自动根据传递的数据来使用具体的引用类型

MyGenericMethod myGenericMethod = new MyGenericMethod();
myGenericMethod.show("method");

泛型集合

泛型集合是一种参数化类型、类型安全的集合,它保证了集合元素的类型一致

泛型集合在编译时即可检查,而不是在运行时抛出异常

在访问泛型集合时,不需要进行类型转换

不同泛型类之间引用不能相互赋值,泛型不存在多态

ArrayList<String> arrayList = new ArrayList<String>();
arrayList.add("aaa");
arrayList.add(1); // 参数不匹配; int无法转换为java.lang.String

Set

Set是一种不包含重复元素的Collection

Set的特点:无序,无下标,元素不可重复

Set中的方法全部继承自Collection

Set<String> set = new HashSet<String>();

// 添加数据
set.add("o2");
set.add("o1");
set.add("o3");
set.add("o3");
System.out.println("Set元素为:" + set);

// 删除数据
set.remove("o3");
System.out.println("删除后的元素为:" + set);

// 遍历
System.out.println("-------- 增强for遍历 ---------");
for (String s : set) {
    System.out.println(s);
}

System.out.println("-------- 迭代器遍历 --------");
Iterator<String> iterator = set.iterator();
while(iterator.hasNext()){
    System.out.println(iterator.next());
}

// 判断
System.out.println("是否存在o1元素:" + set.contains("o1"));
System.out.println("Set是否为空:" + set.isEmpty());
Set元素为:[o1, o2, o3]
删除后的元素为:[o1, o2]
-------- 增强for遍历 ---------
o1
o2
-------- 迭代器遍历 --------
o1
o2
是否存在o1元素:true
Set是否为空:false

Set的实现类

HashSet

HashSet的底层数据结构是哈希表

HashSet是基于HashCode计算元素存放位置

当存入的元素哈希值相同时,会调用equals()进行确认,如结果为true,则拒绝后者存入

HashSet<String> hashSet = new HashSet<String>();

// 添加元素
hashSet.add("o1");
hashSet.add("o3");
hashSet.add("o2");
hashSet.add("o3");
System.out.println("HashSet中的元素为:" + hashSet);

// 移除元素
hashSet.remove("o3");
System.out.println("删除后的元素为:" + hashSet);

// 遍历
System.out.println("-------- 增强for遍历 --------");
for (String s : hashSet) {
    System.out.println(s);
}
System.out.println("-------- 迭代器遍历 --------");
Iterator<String> iterator = hashSet.iterator();
while (iterator.hasNext()){
    System.out.println(iterator.next());
}

// 判断
System.out.println("是否存在o1元素:" + hashSet.contains("o1"));
System.out.println("HashSet是否为空:" + hashSet.isEmpty());
HashSet中的元素为:[o1, o2, o3]
删除后的元素为:[o1, o2]
-------- 增强for遍历 --------
o1
o2
-------- 迭代器遍历 --------
o1
o2
是否存在o1元素:true
HashSet是否为空:false
HashSet的存储过程
  1. 根据HashCode计算存储位置,当此位置为空(没有数据)时,则保存数据
  2. 当位置不为空时,执行equals()方法,若equals()方法为true则认为是重复元素,反之则会形成链表
TreeSet

TreeSet的数据结构是红黑树,它基于排序顺序实现元素不重复,通过ComparaTo方法确定是否为重复元素

TreeSet实现了SortedSet接口,可以对元素集合自动排序

TreeSet中的元素对象必须实现Comparable接口,用来指定排序规则

TreeSet<String> treeSet = new TreeSet<String>();

// 添加元素
treeSet.add("o1");
treeSet.add("o3");
treeSet.add("o2");
treeSet.add("o3");
System.out.println("TreeSet的元素为:" + treeSet);

// 删除元素
treeSet.remove("o3");
System.out.println("删除后的元素为:" + treeSet);

// 遍历
System.out.println("-------- 使用for迭代 --------");
for (String s : treeSet) {
    System.out.println(s);
}

System.out.println("-------- 迭代器迭代 --------");
Iterator<String> iterator = treeSet.iterator();
while (iterator.hasNext()){
    System.out.println(iterator.next());
}

// 判断
System.out.println("是否存在o1元素:" + treeSet.contains("o1"));
System.out.println("TreeSet是否为空:" + treeSet.isEmpty());
TreeSet的元素为:[o1, o2, o3]
删除后的元素为:[o1, o2]
-------- 使用for迭代 --------
o1
o2
-------- 迭代器迭代 --------
o1
o2
是否存在o1元素:true
TreeSet是否为空:false
TreeSet存储复杂类型

User类

package domain;

public class User {
    private String username;
    private int age;

    @Override
    public String toString() {
        return "User{" +
                "username='" + username + '\'' +
                ", age=" + age +
                '}';
    }

    public User(String username, int age) {
        this.username = username;
        this.age = age;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }
}

TreeSet中添加User

TreeSet<User> treeSet = new TreeSet<>();
User u1 = new User("abc",15);
User u2 = new User("bcd",19);
User u3 = new User("acb",17);
// 添加元素
treeSet.add(u1);
treeSet.add(u2);
treeSet.add(u3);

执行后会报一个java.lang.ClassCastException异常,描述为

Exception in thread "main" java.lang.ClassCastException: domain.User cannot be cast to java.lang.Comparable
	at java.util.TreeMap.compare(TreeMap.java:1294)
	at java.util.TreeMap.put(TreeMap.java:538)
	at java.util.TreeSet.add(TreeSet.java:255)
	at MyTreeSet.main(MyTreeSet.java:13)

因为在TreeSet中,添加的元素必须实现Comparable接口来指定排序规则

public class User implements Comparable<User>

在实现Comparable接口后,存在一个ComparaTo方法需要被实现,在该方法中来编写元素的排序规则

@Override
public int compareTo(User o) {
    int username = this.getUsername().compareTo(o.getUsername());
    int age = this.getAge() - o.getAge();

    // 当username相同时,返回age的比较值,否则返回username的比较值
    return username == 0 ? age : username;
}

当元素的ComparaTo方法返回值为0时,认为是重复元素

最后输出结果为

[User{username='abc', age=15}, User{username='acb', age=17}, User{username='bcd', age=19}]
Comparator接口

Comparator接口可以实现定制比较

public static void main(String[] args) {
    TreeSet<User> treeSet = new TreeSet<User>(new Comparator<User>() {
        @Override
        public int compare(User o1, User o2) {
            int username = o1.getUsername().compareTo(o2.getUsername());
            int password = o1.getAge() - o2.getAge();
            return username == 0 ? password : username;
        }
    });
    User u1 = new User("abc",15);
    User u2 = new User("bcd",19);
    User u3 = new User("acb",17);
    // 添加元素
    treeSet.add(u1);
    treeSet.add(u2);
    treeSet.add(u3);
    System.out.println(treeSet);
}

Map体系

在这里插入图片描述

Map为该体系下的根接口

HashMapMap接口的一个实现类,是无序的

TreeMapMap子接口SortedMap的一个实现类,是有序的

Map

Map是将键映射到值的对象。一个映射不能包含重复的键;每个键最多只能映射到一个值

Map可以用来存放键值对,键值对都是无序,无下标的,其中不可重复,允许重复

方法

V put(K key, V value):将键值对存入到集合中,若key重复则覆盖原值

// 添加元素
map.put("username","Melonico");
map.put("password","123456");
map.put("password","654321");
System.out.println(map); // 输出 {password=654321, username=Melonico}

V get(Object key):根据键名获取对应值

// 获取元素
System.out.println(map.get("username"));

Collection values():返回包含所有ValueCollection集合

// 获取所有value
System.out.println(map.values());

Set keySet():返回所有KeySet集合

// 获取所有key
System.out.println(map.keySet());

Set<Map.Entry<K,V>> entrySet():返回此映射中包含的映射关系的Set集合

Set<Map.Entry<String, String>> entries = map.entrySet();

Map的遍历

使用keySet()方法获取所有key,并使用get()方法获取对应的值

Set<String> keySet = map.keySet();
for (String key : keySet) {
    System.out.println(key + ":" + map.get(key));
}

使用entrySet()方法遍历(效率较高)

Set<Map.Entry<String, String>> entries = map.entrySet();
for (Map.Entry<String,String> entry : entries) {
    System.out.println(entry.getKey() + ":" + entry.getValue());
}

Map的实现类

HashMap

HashMap是基于哈希表的Map接口的实现

在 JDK1.2 版本加入,特点是运行效率快、线程不安全HashMap允许使用null作为keyvalue

HashMap<String,String> hashMap = new HashMap<>();

// 添加数据
hashMap.put("name","Melonico");
hashMap.put("password","123");
hashMap.put("password","321");
System.out.println("HashMap中的数据为:" + hashMap);

// 移除数据
hashMap.remove("password");
System.out.println("删除后的数据为:" + hashMap);
hashMap.put("password","321");

// 遍历
System.out.println("-------- keySet遍历 --------");
Set<String> keySet = hashMap.keySet();
for (String key : keySet) {
    System.out.println(key + ":" + hashMap.get(key));
}

System.out.println("-------- entrySet遍历 --------");
Set<Map.Entry<String, String>> entries = hashMap.entrySet();
for (Map.Entry<String,String> entry : entries) {
    System.out.println(entry.getKey() + ":" + entry.getValue());
}

// 判断
System.out.println("是否存在key-password:" + hashMap.containsKey("password"));
System.out.println("是否存在值-321:" + hashMap.containsValue("321"));
System.out.println("HashMap是否为空:" + hashMap.isEmpty());
HashMap中的数据为:{password=321, name=Melonico}
删除后的数据为:{name=Melonico}
-------- keySet遍历 --------
password:321
name:Melonico
-------- entrySet遍历 --------
password:321
name:Melonico
是否存在key-password:true
是否存在值-321true
HashMap是否为空:false
HashMap源码分析

变量或常量

/**
 * The default initial capacity - MUST be a power of two.
 * 默认初始容量为16 - 必须是2的幂.
 */
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
/**
 * The maximum capacity, used if a higher value is implicitly specified
 * by either of the constructors with arguments.
 * MUST be a power of two <= 1<<30.
 * 最大容量为2的30次幂 
 */
static final int MAXIMUM_CAPACITY = 1 << 30;
/**
 * The load factor used when none specified in constructor.
 * 负载因子
 */
static final float DEFAULT_LOAD_FACTOR = 0.75f;
/**
 * The bin count threshold for using a tree rather than list for a
 * bin.  Bins are converted to trees when adding an element to a
 * bin with at least this many nodes. The value must be greater
 * than 2 and should be at least 8 to mesh with assumptions in
 * tree removal about conversion back to plain bins upon
 * shrinkage.
 *
 * 当一个链表上的元素达到8个时,会转变为树 jdk1.8
 */
static final int TREEIFY_THRESHOLD = 8;
/**
 * The bin count threshold for untreeifying a (split) bin during a
 * resize operation. Should be less than TREEIFY_THRESHOLD, and at
 * most 6 to mesh with shrinkage detection under removal.
 * 当一个链表上的元素少于6个时,从树转为链表 jdk1.8
 */
static final int UNTREEIFY_THRESHOLD = 6;
/**
 * The smallest table capacity for which bins may be treeified.
 * (Otherwise the table is resized if too many nodes in a bin.)
 * Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts
 * between resizing and treeification thresholds.
 * 当发生树表转换时,容量必须大于64
 */
static final int MIN_TREEIFY_CAPACITY = 64;
/**
 * The table, initialized on first use, and resized as
 * necessary. When allocated, length is always a power of two.
 * (We also tolerate length zero in some operations to allow
 * bootstrapping mechanics that are currently not needed.)
 * table,在第一次使用时被初始化,必要时会调整大小。被分配后,其长度一直是2的次方
 */
transient Node<K,V>[] table;
/**
 * The number of key-value mappings contained in this map.
 * 这个map里面包含的键值对的个数
 */
transient int size;

DEFAULT_INITIAL_CAPACITYHashMap的初始容量大小

MAXIMUM_CAPACITYHashMap的最大容量

DEFAULT_LOAD_FACTORHashMap的负载因子

当链表长度大于TREEIFY_THRESHOLD时,从链表变为树

当链表长度小于UNTREEIFY_THRESHOLD时,从树变回链表

当链表长度大于TREEIFY_THRESHOLD且集合元素个数大于等于MIN_TREEIFY_CAPACITY时,调整为树

tableHashMap中哈希表的数组

sizeHashMap的元素个数


构造方法

/**
 * Constructs an empty <tt>HashMap</tt> with the default initial capacity
 * (16) and the default load factor (0.75).
 * 构造一个具有默认容量和默认加载因子的空HashMap
 */
public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted 所有其他字段默认
}

HashMap的无参构造方法会创建一个具有默认容量和默认加载因子的空HashMap


方法

/**
 * Associates the specified value with the specified key in this map.
 * If the map previously contained a mapping for the key, the old
 * value is replaced.
 *
 * 将指定值与此映射中的指定键相关联
 * 如果在映射前包含对这个键的映射,那么旧的值会被替代
 *
 * @param key key with which the specified value is to be associated
 * key 是要与指定值关联的键
 * @param value value to be associated with the specified key
 * value 是要与指定键关联的值
 * @return the previous value associated with <tt>key</tt>, or
 *         <tt>null</tt> if there was no mapping for <tt>key</tt>.
 *         (A <tt>null</tt> return can also indicate that the map
 *         previously associated <tt>null</tt> with <tt>key</tt>.)
 * 返回值返回与键关联的上一个值,如果没有键的映射,则返回null
 * (之前与null关联的映射也会返回null)
 */
public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

HashMapput()方法中,会继续调用一个putVal()方法

/**
 * Implements Map.put and related methods.
 * 实现了 Map.put 和相关方法
 *
 * @param hash hash for key
 * @param hash 键的hash值
 *
 * @param key the key
 * @param key 指定的键
 *
 * @param value the value to put
 * @param value 需要添加的value
 *
 * @param onlyIfAbsent if true, don't change existing value
 * @param onlyIfAbsent 如果为true,则不更改现有值
 *
 * @param evict if false, the table is in creation mode.
 * @param evict 如果为false,则表处于创建模式
 * 
 * @return previous value, or null if none
 * 上一个值,若没有,则返回null
 */
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. HashMap刚创建时,tablenull,当添加第一个元素后,容量调整为16
  2. 当元素个数大于阀值时(元素个数 * 负载因子),会扩容为原来的两倍
  3. JDK1.8后,当每个链表长度大于8,且元素个数大于等于64时,会变为红黑树
  4. JDK1.8后,当链表长度小于6时,会变回链表
  5. JDK1.8以前的链表为头插入,JDK1.8之后的链表为尾插入
HashTable

HashTable在 JDK1.0加入,特点是运行效率慢、线程安全

HashMap不同,HashTable不允许null作为键或值

Properties

PropertiesHashtable的子类,要求keyvalue都是String,通常用于读取配置文件

TreeMap

TreeMap的底层数据结构是红黑树,它实现了SortedMap接口,可以对key自动排序

Collection工具类

Collection工具类定义了除了存取以外的常用集合方法

List<Integer> ints = new ArrayList<>();
ints.add(12);
ints.add(5);
ints.add(6);
ints.add(2);
ints.add(15);

// sort排序
System.out.println("排序前:" + ints);
Collections.sort(ints);
System.out.println("排序后:" + ints);

// binarySearch二分查找
System.out.println(Collections.binarySearch(ints,5));

// reverse反转
Collections.reverse(ints);
System.out.println("反转后:" + ints);

// shuffle打乱
Collections.shuffle(ints);
System.out.println("打乱后:" + ints);
Java
  • 作者:Melonico
  • 发表时间:2021-03-15 17:59
  • 更新时间:2021-03-15 17:59

评论

暂无评论,快来发表第一个评论吧!
留言
TOP