Java集合类API详解(上)单列集合Set类

Java集合类API详解

  • 单列集合

一次添加一个数据的集合。它们的根接口是Collection,具体体系如图:

Colletion体系架构

List系列集合的特点:添加的元素是有序可重复有索引的。也即存和取的顺序是一致的;包含的元素是可以有重复的;包含的元素是有索引的,可以通过索引对元素进行操作。

Set系列集合的特点:添加的元素是无序、不重复、无索引。也即存和取的顺序有可能是不一样的;包含的元素是不能也不会有重复,也即元素是唯一的;包含的元素是无索引的,不能通过索引对元素进行操作。

  • 祖宗接口Collection

Collection是单列集合的祖宗接口(顶层接口),它的功能是全部单列集合都可以继承使用的。

方法名称

说明

public boolean add(E e)

把给定的对象添加到当前集合中

public void clear()

清空集合中所有的元素

public boolean remove(E e)

把给定的对象在当前集合中删除

public boolean contains(Object obj)

判断当前集合中是否包含给定的对象

public boolean isEmpty()

判断当前集合是否为空

public int size()

返回集合中元素的个数/集合的长度

  1. add方法

add方法的返回值是一个boolean,如果是往List对象中添加数据,由于List允许重复,所以永远返回true;而对于Set对象,如果添加的元素不存在,返回true,表示添加成功,否则返回false,表示添加失败。

  1. clear方法

清空集合中所有的元素。

  1. remove方法

删除一个元素。返回布尔类型,删除成功返回true,删除失败返回false。如果要删除的元素不存在,那么就会删除失败。

  1. contains方法

判断是否包含某个元素。该方法底层是依赖equals方法进行判断是否存在,所以,如果集合中存储的是自定义对象,也想通过contains方法来判断是否包含,那么再JavaBean类中,需要重写equals方法。

Idea中查看具体实现类的源码,比如点击contains()方法,右击 - Goto - Implementation即可。

  1. isEmpty方法

判断集合是否为空,如果长度为0则返回true,否则返回false。

  1. size方法

获取集合的长度。

  1. 基本方法演示代码
import java.util.ArrayList;

import java.util.Collection;



public class CollectionDemo {



    public static void main(String[] args) {

        // Collection是个接口,不能直接实例化

        Collection<String> collection = new ArrayList<>();

        // 1.添加元素

        collection.add("Hi!Collection.");

        collection.add("aaa");

        collection.add("bbb");

        collection.add("ccc");

        System.out.println(collection); // [Hi!Collection., aaa, bbb, ccc]



        // 2. 清空集合

        //collection.clear();

        //System.out.println(collection); // []



        // 3. 删除

        // 如果是Collection的对象,那么不能通过索引操作元素,只能通过元素的对象进行删除

        collection.remove("Hi!Collection.");

        System.out.println(collection); // [aaa, bbb, ccc]



        // 4. 判断集合是否包含某个元素

        boolean isContained1 = collection.contains("Hi!Collection.");

        boolean isContained2 = collection.contains("aaa");

        System.out.println(isContained1);   // false

        System.out.println(isContained2);   // true



        // 5. 判断集合是否为空

        boolean isEmpty = collection.isEmpty();

        System.out.println(isEmpty);    // flase



        // 6.获取集合的长度

        int size = collection.size();

        System.out.println(size);       // 3

    }

}

  • Collection的遍历方式

有三种通用的遍历方法:迭代器遍历,增强for遍历,Lambda表达式遍历。

  1. 迭代器遍历

迭代器用于获取集合里的每一个元素,它不依赖与索引。迭代器在Java中的类是Iterator,迭代器是集合专用的遍历方式。

Collection集合获取迭代器

方法名称

说明

Iterator<E>iterator()

返回迭代器对象,默认指向当前集合的0索引

Iterator中的常用方法

方法名称

说明

boolean hasNext()

判断当前位置是否有元素,有元素返回true,没有元素返回false

E next()

获取当前位置的元素,并将迭代器对象移向下一个位置。

迭代器使用示例:

import java.util.ArrayList;

import java.util.Collection;

import java.util.Iterator;



public class IteratorTest {

    public static void main(String[] args) {

        // 1.创建集合并添加元素

        Collection<String> collection = new ArrayList<>();

        collection.add("aaa");

        collection.add("bbb");

        collection.add("ccc");

        collection.add("ddd");

        collection.add("eee");



        // 2.获取迭代器

        Iterator<String> iter = collection.iterator();



        // 3.利用迭代器循环遍历集合中的每一个元素

        while (iter.hasNext()){

            // next方法,获取元素并移动指针

            String str = iter.next();

            System.out.println(str);

        }

    }

}
  1. 迭代器注意事项
  1. 迭代器到达末尾会报错NoSuchElementException
  2. 迭代器遍历完毕,指针不会复位(如果我们需要第二次遍历集合,只能再次获取一个新的迭代器)
  3. 循环中只能用一次next方法(循环中可以多次调用next方法,但是指针会停留在非预期的位置上,从而造成异常)
  4. 迭代器遍历的过程中,不能用集合的方法进行增加或者删除(在迭代时不能使用集合的删除方法,需要用迭代器里的remove方法删除元素)
  1. 小结

1.迭代器在遍历集合的时候是不依赖索引的

2.迭代器需要掌握三个方法:

Iterator<String> iter =list.iterator();

while(iter.hasNext()){

    String str =iter.next();System.out.println(str);

}

3.迭代器的四个细节:

●如果当前位置没有元素,还要强行获取,会报NoSuchElementException

●迭代器遍历完毕,指针不会复位

●循环中只能用一次next方法

●迭代器遍历时,不能用集合的方法进行增加或者删除

  1. 增强for遍历
  1. 增强for的底层就是迭代器,为了简化迭代器的代码书写的。
  2. 它是JDK5之后出现的,其内部原理就是一个lterator迭代器
  3. 所有的单列集合数组才能用增强for进行遍历。

格式:

for(元素的数据类型 变量名:数组或者集合){

}

for(String s : list){

System.out.println(s);

}

例如:

import java.util.ArrayList;

import java.util.Collection;



public class ForeachTest {

    public static void main(String[] args) {

        // 1.创建集合并添加元素

        Collection<String> collection = new ArrayList<>();

        collection.add("aaa");

        collection.add("bbb");

        collection.add("ccc");

        collection.add("ddd");

        collection.add("eee");



        // 2.利用增强for遍历

        for (String s : collection) {

            System.out.println(s);

        }

    }

}

注意点:修改增强for中的变量,不会改变集合中原本的数据。上例中的变量s只是个第三方变量,更改它的值并不会修改集合元素的值。

  1. Labmda表达式遍历

lambda表达式遍历,也即使用Collection类的forEach方法,并使用匿名类或lambda实现参数的功能接口Consumer。

default void forEach(Consumer<? super T> action);

代码如下:

import java.util.ArrayList;

import java.util.Collection;

import java.util.function.Consumer;



public class LambdaTest {

    public static void main(String[] args) {

        // 1.创建集合并添加元素

        Collection<String> collection = new ArrayList<>();

        collection.add("aaa");

        collection.add("bbb");

        collection.add("ccc");

        collection.add("ddd");

        collection.add("eee");



        // 2-1. 利用匿名内部类遍历

        collection.forEach(new Consumer<String>() {

            @Override

            public void accept(String s) {

                System.out.println(s);

            }

        });

        System.out.println("-----------");



        // 2-2. 利用lambda遍历

        collection.forEach((String s) -> {

                    System.out.println(s);

                }

        );

        System.out.println("-----------");



        // 2-3. 利用lambda遍历(简化代码)

        collection.forEach(s -> System.out.println(s));

    }

}
  • List系列集合

List集合是单列集合一类。具有如下特点:

  1. 有序:存和取的元素顺序一致
  2. 有索引:可以通过索引操作元素
  3. 可重复:存储的元素可以重复

List集合继承了Colletion接口,但是List接口的类型都有索引,因此List集合会有多一些索引操作的方法:

方法名称

说明

void add(int index,E element)

在此集合中的指定位置插入指定的元素

E remove(int index)

删除指定索引处的元素,返回被删除的元素

E set(int index,E element)

修改指定索引处的元素,返回被修改的元素

E get(int index)

返回指定索引处的元素

  1. add方法

在指定位置上添加元素,原来索引及后面的元素依次往后移。

  1. remove方法

删除指定位置上的元素,并返回被删除的元素。

  1. set方法

修改指定索引处的元素,并返回被修改的元素(老元素)

例程如下:

import java.util.ArrayList;

import java.util.List;



public class ListDemo {

    public static void main(String[] args) {

        // 1. 创建一个list

        List<String> list = new ArrayList<>();



        // 2. 添加元素

        list.add("aaa");

        list.add("bbb");

        list.add("ccc");

        list.add("ddd");



        // 3. add方法添加

        list.add(1, "gg");



        // 4. remove方法删除:删除指定位置上的元素,并返回被删除的元素

        String removed = list.remove(0);

        System.out.println(removed);



        // 5.set方法

        String setted = list.set(0, "xxx");

        System.out.println(setted);



        // 6. get方法

        String i2 = list.get(2);

        System.out.println(i2);



        // 打印集合

        System.out.println(list);

    }

}



// 输出结果:

// aaa

// gg

// ccc

// [xxx, bbb, ccc, ddd]

  • List集合的遍历方式

List集合共有5中遍历方式。分别是

  1. 迭代器遍历
  2. 列表迭代器遍历
  3. 增强for遍历
  4. Lambda表达式遍历
  5. 普通for循环(因为List集合存在索引)
  1. 列表迭代器ListIterator

ListIterator接口继承于Iterator接口,提供了基于索引的更多的方法,并可以在遍历的同时进行添加和删除操作。

  1. 五种遍历方式的对比

遍历方法

特点

迭代器遍历

在遍历的过程中需要删除元素,请使用迭代器。

列表迭代器

在遍历的过程中需要添加元素,请使用列表迭代器。

增强for遍历

仅仅想遍历,那么使用增强for或Lambda表达式。

Lambda表达式

普通for

如果遍历的时候想操作索引,可以用普通for

  • ArrayList类

  1. ArrayList集合底层原理
  • 利用空参创建的集合,在底层创建一个默认长度为0的数组
  • 添加第一个元素时,底层会创建一个新的长度为10的数组
  • 存满时,会扩容1.5倍
  • 如果一次添加多个元素,1.5倍还放不下,则新创建数组的长度以实际为准

在Idea中查看类的方法,Ctrl + N打开搜索框,搜索相应的内容,在打开的类中,按Alt+7打开侧边栏的大纲视图,同时也可以按Ctrl+F12弹出相应的大纲对话框。

  1. transient关键字(补充知识点)

在ArrayList类中,实际存储数据的属性数组elementData在声明时是被transient关键字修饰的:

transient Object[] elementData; // non-private to simplify nested class access
// 非私有以简化嵌套类访问

简单说,transient修饰的变量表明其在类序列化时会被忽略,被transient的字段称为“瞬态字段(transient field)”。序列化是指将对象转换为字节流的过程(比如将对象序列化后存入文件或通过网络传输等;另外,可以被序列化的类必须实现Serializable标志性接口),反序列化与之相反(从文件或网络流中读取后并重建对象)。当我们将任何变量标记为transient时,该变量不会被序列化。由于对象的序列化形式中不存在瞬态字段(也即被transient修饰的字段不会被序列化,在保存后的文件里也不会存在瞬态字段的值),因此在从序列化形式创建对象时,反序列化过程将使用此类字段的默认值(为null或0)。

瞬态关键字在以下几种情况中有用:

  1. 可以用它来表示派生字段
  2. 对于不代表对象状态的字段,瞬态关键字非常有用
  3. 对于任何不可序列化的引用,我们可以使用瞬态关键字
  4. 当我们想存储敏感信息,且不想通过网络发送时,可以使用瞬态关键字

在final和transient共同修饰的成员变量时,是会被序列化的,但是如果被final和transient共同修饰的变量值在赋值时使用new操作的,那么将不会被序列化。

参考资料: https://www.baeldung.com/java-transient-keyword

  • LinkedList类
  1. 底层数据结构是双链表,查询慢,增删快,但是如果操作的是首尾元素,速度也是极快的。
  2. LinkedList本身多了很多直接操作首尾元素的特有API

特有方法

说明

public void addFirst(E e)

在该列表开头插入指定的元素

public void addLast(E e)

将指定的元素追加到此列表的末尾

public E getFirst()

返回此列表中的第一个元素

public E getLast()

返回此列表中的最后一个元素

public E removeFirst()

从此列表中删除并返回第一个元素

public E removeLast()

从此列表中删除并返回最后一个元素

每个节点的真实数据存储在内部类Node<E>中。

  • 数据结构知识
  1. 二叉树
  1. 树节点的内部结构

  1. 二叉树的相关知识

度:每一个节点的子节点数量

树高:树的总层数

根节点:最顶层的节点

左子节点:左下方的节点

右子节点:右下方的节点

根节点的左子树:蓝色虚线

根节点的右子树:绿色虚线

  1. 二叉查找树

二叉查找树,又称二叉排序树或者二叉搜索树。它有如下特点:

  1. 每一个节点上最多有两个子节点
  2. 任意节点左子树上的值都小于当前节点
  3. 任意节点右子树上的值都大于当前节点
  1. 添加节点的规则

小的存左边,大的存右边,一样的不存。

  1. 数据结构(二叉树)前序遍历

从根结点开始,然后按照当前结点,左子结点,右子结点的顺序遍历

  1. 数据结构(二叉树)后序遍历

从最左边的子节点开始,然后按照左子结点,右子结点,当前结点的顺序遍历

  1. 数据结构(二叉树)遍历方式小结

①前序遍历:当前节点,左子节点,右子结点公

②中序遍历:左子节点,当前节点,右子结点

③后序遍历:左子节点,右子结点,当前节点

④层序遍历:一层一层的去遍历

  1. 平衡二叉树

任意节点左右子树高度差不超过1。

  1. 如何保持二叉树平衡?

通过旋转机制。旋转分为左旋和右旋,当添加一个节点之后,该树不再是一颗平衡二叉树时触发。

  1. 左旋

确定支点:从添加的节点开始,不断的往父节点找不平衡的节点,该不平衡节点即为支点。

方式一:

  1. 以不平衡的点作为支点
  2. 把支点左旋降级,变成左子节点
  3. 晋升原来的右子节点

方式二:

  1. 以不平衡的点作为支点
  2. 将根节点的右侧往左拉
  3. 原先的右子节点变成新的父节点,并把原根节点多余的左子节点出让,给已经降级的根节点当右子节点
  1. 右旋

方式一:

  1. 以不平衡的点作为支点
  2. 把支点右旋降级,变成右子节点
  3. 晋升原来的左子节点

①左左 一次右旋

②左右 先局部左旋,再整体右旋

③右右 一次左旋

④右左 先局部右旋,再整体左旋

  1. 红黑树

红黑树是一种自平衡的二叉查找树,是计算机科学中用到的一种数据结构。1972年出现,当时被称之为平衡二叉B树。后来,1978年被修改为如今的"红黑树"。

●它是一种特殊的二叉查找树,红黑树的每一个节点上都有存储位表示节点的颜色,

●每一个节点可以是红或者黑;红黑树不是高度平衡的,它的平衡是通过"红黑规则"进行实现的

红黑树:

  1. 是一个二叉查找树:
  2. 但是不是高度平衡的
  3. 条件:特有的红黑规则
  1. 数据结构(红黑树)红黑规则
  1. 每一个节点或是红色的,或者是黑色的
  2. 根节点必须是黑色
  3. 如果一个节点没有子节点或者父节点,则该节点相应的指针属性值为Nil,这些Nil视为叶节点,每个叶节点(Nil)是黑色的
  4. 如果某一个节点是红色,那么它的子节点必须是黑色(不能出现两个红色节点相连的情况)
  5. 对每一个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点;

  1. 添加节点的规则

红黑树在添加节点的时候,添加的节点默认是红色的

  • Set系列集合
  1. Set系列集合
  1. 无序:存取顺序不一致
  2. 不重复:可以去除重复
  3. 无索引:没有带索引的方法,所以不能使用普通for循环遍历,也不能通过索引来获取元素
  1. Set集合的实现类
  1. HashSet:无序、不重复、无索引
  2. LinkedHashSet:有序、不重复、无索引
  3. TreeSet:可排序、不重复、无索引

Set接口中的方法上基本上与Collection的API一致。

练习1:利用Set系列的集合,添加字符串,并使用多种方式遍历。迭代器、增强for、Lambda表达式

  • HashSet类

HashSet底层原理

● HashSet集合底层采取哈希表存储数据

●哈希表是一种对于增删改查数据性能都较好的结构

哈希表组成

  1. JDK8之前:数组+链表
  2. JDK8开始:数组+链表+红黑树
  1. 哈希值

●根据hashCode方法算出来的int类型的整数

●该方法定义在Object类中,所有对象都可以调用,默认使用地址值进行计算

●一般情况下,会重写hashCode方法,利用对象内部的属性值计算哈希值

  1. 对象的哈希值特点

●如果没有重写hashCode方法,不同对象计算出的哈希值是不同的

●如果已经重写hashcode方法,不同的对象只要属性值相同,计算出的哈希值就是一样的

●在小部分情况下,不同的属性值或者不同的地址值计算出来的哈希值也有可能一样。  (哈希碰撞)

  1. HashSet底层原理

①创建一个默认长度16,默认加载因子为0.75的数组,数组名table

②根据元素的哈希值跟数组的长度计算出应存入的位置

③判断当前位置是否为null,如果是null直接存入

④如果位置不为null,表示有元素,则调用equals方法比较属性值

⑤一样:不存不一样:存入数组,形成链表

  1. JDK8前后对比
  1. JDK8以前:新元素存入数组,老元素挂在新元素下面
  2. JDK8以后:新元素直接挂在老元素下面
  3. JDK8以后,当链表长度超过8,而且数组长度大于等于64时,自动转换为红黑树
  4. 如果集合中存储的是自定义对象,必须要重写hashCode和equals方法

问题1:HashSet为什么存和取的顺序不一样?

问题2:HashSet为什么没有索引?

问题3:HashSet是利用什么机制保证数据去重的?

  1. 小结

1.HashSet集合的底层数据结构是什么样的?

2.HashSet添加元素的过程?

3.HashSet为什么存和取的顺序不一样?

4.HashSet为什么没有索引?

5.HashSet是利用什么机制保证去重的?

  1. 练习 利用HashSet集合去重

需求:创建一个存储学生对象的集合,存储舞个学生对象。使用程序实现在控制台遍历该集合。

要求:学生对象的成员变量值相同,我们就认为是同一个对象

需要重写hashCode和equals方法

  • LinkedHashSet类

●有序、不重复、无索引。

●这里的有序指的是保证存储和取出的元素顺序一致

●原理:底层数据结构是依然哈希表,只是每个元素又额外的多了一个双链表的机制记录存储的顺序。

  1. LinkedHashSet集合小结
  1. LinkedHashSet集合的特点和原理是怎么样的?

●有序、不重复、无索引

●底层基于哈希表,使用双链表记录添加顺序

  1. 在以后如果要数据去重,我们使用哪个?

默认使用HashSet

如果要求去重且存取有序,才使用LinkedHashSet

  • TreeSet类

●不重复、无索引、可排序

●可排序:按照元素的默认规则(有小到大)排序。

● TreeSet集合底层是基于红黑树的数据结构实现排序的,增删改查性能都较好。

注意:TreeSet底层是红黑树,所以不需要重写hashCode和equals方法,但是必须重写排序规则。

需求:利用TreeSet存储整数并进行排序

  1. TreeSet实现比较的方法

方式一:

默认排序/自然排序:Javabean类实现Comparable接口指定比较规则

方式二:

比较器排序:创建TreeSet对象时候,传递比较器Comparator接口匿名对象指定规则

//o1:表示当前要添加的元素

//o2:表示已经在红黑树存在的元素//返回值规则跟之前是一样的

TreeSet<String>ts =new TreeSet<>(new Comparator<String>(){@override

public int compare(String o1,String o2){

//按照长度排序

int i=01.length()-o2.length();//如果一样长则按照首字母排序

i=i==0?o1.compareTo(o2):i;return i;

});

练习:需求:创建5个学生对象

属性:(姓名,年龄,语文成绩,数学成绩,英语成绩),

按照总分从高到低输出到控制台

如果总分一样,按照语文成绩排

如果语文一样,按照数学成绩排

如果数学成绩一样,按照英语成绩排

如果英文成绩一样,按照年龄排

如果年龄一样,按照姓名的字母顺序排

如果都一样,认为是同一个学生,不存。

  1. TreeSet小结
  1. TreeSet集合的特点是怎么样的?

●可排序、不重复、无索引

●底层基于红黑树实现排序,增删改查性能较好

  1. TreeSet集合自定义排序规则有几种方式

●方式一:Javabean类实现Comparable接口,指定比较规则

●方式二:创建集合时,自定义Comparator比较器对象,指定比较规则

  1. 方法返回值的特点

●负数:表示当前要添加的元素是小的,存左边

●正数:表示当前要添加的元素是大的,存右边

● 0:表示当前要添加的元素已经存在,舍弃

  • 使用场景

1.如果想要集合中的元素可重复

●用ArrayList集合,基于数组的。(用的最多)

2.如果想要集合中的元素可重复,而且当前的增删操作明显多于查询

●用LinkedList集合,基于链表的。

3.如果想对集合中的元素去重

●用HashSet集合,基于哈希表的。  (用的最多)

4.如果想对集合中的元素去重,而且保证存取顺序

●用LinkedHashSet集合,基于哈希表和双链表,效率低于HashSet。

5.如果想对集合中的元素进行排序

●用TreeSet集合,基于红黑树。后续也可以用List集合实现排序。

  • 7
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

唐骁虎

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值