Java基础篇——集合进阶(一)

目录

1.集合进阶

1.1.集合体系结构

1.1.1.单列集合体系结构

1.1.2.单列集合应用场景

1.1.2.1.Collection的常用成员方法

1.1.2.2.Collection系列集合的遍历

1.1.2.2.1.迭代器遍历(含源码)

1.1.2.2.2.增强for遍历

1.1.2.2.3.Lambda表达式遍历

1.1.2.3.List系列

1.1.2.3.1.List系列的成员方法

1.1.2.3.2.List系列的遍历

1.1.2.3.3.ArrayList

1.1.2.3.4.LinkedList

1.1.2.4.Set系列

1.1.2.4.1.Set系列的成员方法和遍历

1.1.2.4.2.HashSet

1.1.2.4.3.LinkedHashSet  

1.1.2.4.4.TreeSet 

1.集合进阶

Java中有众多集合,ArrayList只是其中一种。

1.1.集合体系结构

集合可以分成两类,单列集合(Collection)、双列集合(Map)。单列一次添加一个数据,双列集合一次添加一对数据

1.1.1.单列集合体系结构

Collection:单列集合的顶层接口,它的功能全部单列集合都可以使用

Collection的两个系列:

  • List系列:添加元素时有序、可重复、有索引。

    • 有序:指存放时的顺序和取出来的顺序要相同,而非大小关系顺序

    • 可重复:字面含义

    • 有索引:字面含义

  • Set系列:无序、不重复、无索引

    • 无序:存和取的顺序不一定相同

    • 不重复:当中的每个元素是唯一的->应用场景:去重

    • 无索引:不能通过索引来获取集合中的元素

1.1.2.单列集合应用场景
  • 如果想要集合中的元素可重复,用ArrayList集合最多,基于数组;

  • 如果想要集合中的元素去重,用HashSet最多,基于哈希表;

  • 如果想要集合中的元素可重复,且增删操作明显大于查询,用LinkedList,基于链表;

  • 如果想要集合中的元素去重,且保证存取的顺序,用LinkedHashSet,基于哈希表和链表。

  • 如果想要集合中的元素排序,用TreeSet,基于红黑树。

1.1.2.1.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()                   //集合长度
//举例
public class Demo {
    public static void main(String[] args) {
       	//注意:Collection是一个接口,我们不能直接创建他的对象。所以,现在我们学习他的方法时,只能创建他实现类的对象。实现类:ArrayList。
       	//接口多态
        Collection<String> coll = new ArrayList<>();

        //1.添加元素
        //细节1:如果我们要往List系列集合中添加数据,那么方法永远返回true,因为List系列的是允许元素重复的。
        //细节2:如果我们要往Set系列集合中添加数据,如果当前要添加元素不存在,方法返回true,表示添加成功。如果当前要添加的元素已经存在,方法返回false,表示添加失败,因为Set系列的集合不允许重复。
        coll.add("aaa");
        coll.add("bbb");
        coll.add("ccc");
        System.out.println(coll);

        //2.清空,没有返回值
        coll.clear();

        //3.删除
        //细节1:因为Collection里面定义的是共性的方法,所以此时不能通过索引进行删除。只能通过元素的对象进行删除。
        //细节2:方法会有一个布尔类型的返回值,删除成功返回true,删除失败返回false
        //如果要删除的元素不存在,就会删除失败。
        System.out.println(coll.remove("aaa"));
        System.out.println(coll);//true

        //4.判断元素是否包含
        //细节:ArrayList底层是依赖equals方法进行判断是否存在的。(ps:如果想要查看equals的底层源码,不能到Collection接口中查看,因为接口中的抽象方法没有方法体。因此需要右键点击contains方法->go to->选择ArrayList类查看,或者直接Ctrl点进ArrayList中查看equals方法)
        //所以,如果集合中存储的是其他的自定义对象,如Student等,也想调用contains方法来判断是否包含,那么在javabean类中,一定要自己重写equals方法。
        boolean result1 = coll.contains("bbb");
        System.out.println(result1);//true

        //5.判断集合是否为空
        boolean result2 = coll.isEmpty();
        System.out.println(result2);//false

        //6.获取集合的长度
        coll.add("ddd");
        int size = coll.size();
        System.out.println(size);//3
    }
}
1.1.2.2.Collection系列集合的遍历

for循环的弊端:只能遍历有索引的容器,对于Set集合无法遍历,可以通过使用迭代器接口,是集合专用的遍历方式。

Collection系列集合三种通用的遍历方式:

  • 迭代器遍历

  • 增强for遍历

  • lambda表达式遍历

如果在遍历的时候需要删除元素,那么需要使用迭代器;如果仅仅只是想遍历,那么使用增强for和Lambda表达式。

1.1.2.2.1.迭代器遍历(含源码)

迭代器遍历的本质:

  • 迭代器不依赖索引就可以遍历。本质是通过创建指针、移动指针来实现遍历。

迭代器遍历的流程:

  • Iterator<E> it = new iterator(); :获取一个迭代器对象,指针默认指向集合的0位置处

  • boolean hasNext() :判断当前指针指向的位置是否有元素,有则返回true,没有返回false。配合while循环使用

  • E next() :每调用一次方法,就完成两件事情:①获取当前指针指向的元素移动指针到下一个位置

迭代器遍历举例:

public class Demo {
    public static void main(String[] args) {
        //1.创建集合并添加元素
        Collection<String> coll = new ArrayList<>();
        coll.add("aaa");
        coll.add("bbb");
        coll.add("ccc");
        coll.add("ddd");

        //2.获取迭代器对象
        Iterator<String> it = coll.iterator();
        //3.利用循环不断的去获取集合中的每一个元素
        while(it.hasNext()){
            //4.next方法的两件事情:获取元素并移动指针到下一个位置
            String str = it.next();
            System.out.println(str);
        }
    }
}

注意事项:

  • 如果迭代器已经指到末尾,继续调用next方法,那么会报错NoSuchElementException(没有元素异常)

  • 迭代器遍历完毕,指针不会复位,如果想要再次遍历,需要再创建一个新的迭代器对象

  • 每次循环中只能用一次next方法,如果一个循环中调用多次,有可能会报错NoSuchElementException(没有元素异常)

  • 迭代器遍历时,不能用集合的方法进行增加或者删除,会报错(并发编改异常);需要使用迭代器自身的remove方法进行删除,也就是迭代器对象.remove()

迭代器的底层源码:

//获取迭代器对象的底层
Iterator<String> it = list.iterator();
//创建一个集合对象list,然后调用父类接口Collection当中的iterator()方法
//iterator()方法本质就是创建了ArrayList类中的内部类Itr的对象
//因此迭代器对象实际上这个内部类Itr的对象
//通过这个内部类Itr的对象,我们就可以调用其中的方法进行遍历

1.1.2.2.2.增强for遍历

JDK5以后出现的,底层就是迭代器

所有单列集合和数组才能用增强for遍历

for(元素数据类型 变量名:数组或者单列集合){
    ...
}

举例:

for(String s:list){
	print(s);
}
//结果是会遍历list中的每一个元素并进行打印

注意: s本质是一个第三方变量,和集合没有直接关系。作用是在循环中依次记录集合中的每一个数据。如果修改for中的变量,例如在上面的for中给s赋值,不会改变集合中原本的数据,因为s本质上是一个第三方变量

for(String s:list){
    s = "a";
}
	print(list);
//打印结果仍然是集合中的数据不变。如果把print语句放在for循环中,打印list呢?
for(String s:list){
    s = "a";
    print(list);
}
//注意:如果把print语句放在for循环中,此时打印list仍然不会改变,这原因是s是一个第三方变量。如果打印s,此时结果就会发生改变了。

快捷键:集合变量名字.for,可以直接生成增强for循环

1.1.2.2.3.Lambda表达式遍历

foreach方法

forEach在底层是一个ArraList类中被重写了的方法,采用的原理就是普通的for循环

//采用匿名内部类的形式,创建接口Consumer的一个实现类的对象
list.forEach(new Consumer<String>() {
   @Override
   public void accept(String s) {
       System.out.println(s);
   }
);
//采用Lambda表达式,简单记为(形参)->{方法体},数据类型可以省略;当形参只有一个的时候,小括号可以省略;当方法体只有一行代码的时候,可以省略大括号、分号、return
list.forEach(s->System.out.println(s));
1.1.2.3.List系列

特点:

  • 有序

  • 有索引

  • 可重复

1.1.2.3.1.List系列的成员方法

List系列也是一个接口,顶级接口Collection的子接口,因此Collection中的所有方法其都可以使用。这里介绍其特有的方法,都是跟索引相关的。

        //List系列集合独有的方法:
        void add(int index,E element)       //在此集合中的指定位置插入指定的元素,原来索引上的元素会向后移
        E remove(int index)                 //删除指定索引处的元素,返回被删除的元素
        E set(int index,E element)          //修改指定索引处的元素,返回被修改的元素
        E get(int index)                    //返回指定索引处的元素

        //1.创建一个集合
        List<String> list = new ArrayList<>();

        //2.添加元素
        list.add("aaa");
        list.add("bbb");//1
        list.add("ccc");

        void add(int index,E element)       //在此集合中的指定位置插入指定的元素
        //细节:在一个指定索引上面添加元素,原来索引上的元素会依次往后移
        list.add(1,"QQQ");

        E remove(int index)                 //删除指定索引处的元素,返回被删除的元素
        String remove = list.remove(0);
        System.out.println(remove);//aaa


        E set(int index,E element)          //修改指定索引处的元素,返回被修改的元素
        String result = list.set(0, "QQQ");
        System.out.println(result);

        E get(int index)                    //返回指定索引处的元素
        String s = list.get(0);
        System.out.println(s);


        //3.打印集合
        System.out.println(list);

    }
}
1.1.2.3.2.List系列的遍历

Collection中的三种遍历List均可以使用。

        //首先创建集合并添加元素
        List<String> list = new ArrayList<>();
        list.add("aaa");
        list.add("bbb");
        list.add("ccc");
  • 迭代器遍历

应用场景:想要在遍历的过程中删除元素的时候使用。

Iterator<String> it = list.iterator();
    while(it.hasNext()){
         String str = it.next();
         System.out.println(str);
}
  • 增强for

应用场景:代码简单,仅仅想要遍历的时候使用。

for (String s : list) {
      System.out.println(s);
}
  • 普通for循环

size方法跟get方法还有for循环结合的方式,利用索引获取到集合中的每一个元素。应用场景:想要在遍历的过程中操作索引的时候使用。

for (int i = 0; i < list.size(); i++) {
	String s = list.get(i);
	System.out.println(s);
}
  • Lambda表达式
list.forEach(s->System.out.println(s) );
  • 列表迭代器

应用场景:想要在遍历的过程中增加元素的时候使用。

        ListIterator<String> it = list.listIterator();
        while(it.hasNext()){
            String str = it.next();
            if("bbb".equals(str)){
                //qqq
                it.add("qqq");
            }
        }
        System.out.println(list);

五种遍历方式的对比:

  • - 遍历过程中需要删除元素,使用迭代器遍历;
  • - 遍历过程中需要增加元素,使用列表迭代器;
  • - 仅仅想遍历,使用增强for和Lambda表达式遍历;
  • - 遍历的时候想要操作索引,采用普通for遍历。
1.1.2.3.3.ArrayList

底层源码见Java基础上。底层数据结构是数组

1.1.2.3.4.LinkedList

底层数据结构是双向链表,查询慢(但是比单向链表快),增删快,但是如果操作的是首尾元素,速度也是极快的。因此多了很多首尾操作的特有API。LinkedList自身方法用的很少,基本上使用Collection和List中的方法就足以。

LinkedList底层源码

LinkedList添加元素的底层:尾插法

final Node<E> l = last //表示用l这个Node类型的变量来临时存储上一个结点的地址值last(如果是第一次添加那么last为null)。
final Node<E> newNode = new Node<>(l,e,null);//创建新结点newNode,上一个结点地址值为l,数据为变量e,下一个结点地址值为null
last = newNode;//把新结点的地址值赋值给last
if else语句;//如果l中是null(表示第一次添加),那么就用first这个变量记录新结点的地址值,表示是头结点;如果不是null,说明新结点之前已经有一个结点,那么把新结点newNode的地址值赋值给l(l记录上一个结点的地址值,这里表示上一个结点)的成员变量next(记录新结点的地址值)。

1.1.2.4.Set系列

特点:

  • 无序:存和取的顺序有可能不一致。

  • 不重复:可以去重

  • 无索引:没有带索引的方法

Set三个实现类的特点:

  • HashSet:无序、不重复、无索引

  • LinkedSet:有序、不重复、无索引

  • TreeSet:可排序、不重复、无索引

1.1.2.4.1.Set系列的成员方法和遍历

Set系列是一个接口。其方法基本与Collection中一致。

    //成员方法和遍历方式的演示
	public static void main(String[] args) {
        //创建一个Set集合的对象
        Set<String> s = new HashSet<>();
        //添加元素
        //如果当前元素是第一次添加,那么可以添加成功,返回true
        //如果当前元素是第二次添加,那么添加失败,返回false
        s.add("张三");//true
        s.add("张三");//false
        s.add("李四");//true
        s.add("王五");//true

        //打印集合,发现和存的顺序不一致
        //System.out.println(s);//[李四, 张三, 王五]

        //迭代器遍历
        Iterator<String> it = s.iterator();
        while (it.hasNext()){
            String str = it.next();
            System.out.println(str);
        }

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

        // Lambda表达式遍历
        s.forEach(str->System.out.println(str));
    }
1.1.2.4.2.HashSet

应用背景:需要去重的时候使用HashSet。

底层结构:采用哈希表数据结构。增删改查数据性能都较好。

JDK8之前,数组+链表。JDK8以后,数组+链表+红黑树。

HashSet三个特点的底层原理:

  • 无序:存的时候是按照哈希值来存的,而取的时候是从头按链表-链表来遍历的

  • 不重复:通过hashCode方法计算哈希值,从而确定了唯一存储的位置;如果哈希值相同,再通过equals方法进一步比较对象是否相同,如果相同那么就不会存,确定了唯一性。

    • 注意:如果集合中存储的是自定义对象,必须要重写hashCode和equals方法,不然会默认采用地址值去计算哈希值,就会导致不同对象也能存储。String和Integer底层已经重写好了。

  • 无索引:因为是由数组+链表+红黑树复合而成的,没有索引机制。

哈希值:对象的整数表现形式。是根据hashCode方法算出来的int类型的整数。

  • hashCode方法:这个方法定义在Object类中,所有对象都可以调用,默认用地址值计算(但意义不大),因此一般情况下会重写hashCode方法,用对象内部属性值计算哈希值

对象的哈希值的特点:

  • 如果没有重写,用地址值计算,因此不同对象计算出来的哈希值不同

  • 如果重写,不同对象属性值相同,那么计算出来的哈希值相同

  • 哈希碰撞:小概率情况下,地址值不同或者不同属性值,计算出来的哈希值却相同。(底层是因为对象数量大于整数的范围)

    public static void main(String[] args) {
        //创建对象
        Student s1 = new Student("zhangsan",23);
        Student s2 = new Student("zhangsan",23);

        //如果没有重写hashCode方法,不同对象计算出的哈希值是不同的
        //如果已经重写hashcode方法,不同的对象只要属性值相同,计算出的散列值就是一样的
        System.out.println(s1.hashCode());//-1461067292
        System.out.println(s2.hashCode());//-1461067292

        //在小部分情况下,不同的属性值或者不同的地址值计算出来的散列值也有可能一样。
        //哈希碰撞
        System.out.println("abc".hashCode());//96354
        System.out.println("acD".hashCode());//96354
    }

HashSet添加元素的底层原理

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

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

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

    如果位置不为null,表示有元素,则调用equals方法比较。如果是链表,会依次和链表中所有元素比较

  • 属性值一样:不存

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

    JDK8以前:新元素存入数组,老元素挂在新元素下面;JDK8以后:新元素直接挂在老元素下面

    JDK8以后:当链表长度超过8,并且数组长度≥64,会自动将链表转换为红黑树

练习:利用HashSet去重

public class Demo {
    public static void main(String[] args) {
        /* 需求:创建一个存储学生对象的集合,存储多个学生对象。
            使用程序实现在控制台遍历该集合。
            要求:学生对象的成员变量值相同,我们就认为是同一个对象
            注意要重写hashCode和equals方法!
*/
        //1.创建三个学生对象
        Student s1 = new Student("zhangsan",23);
        Student s2 = new Student("lisi",24);
        Student s3 = new Student("wangwu",25);
        Student s4 = new Student("zhangsan",23);

        //2.创建集合用来添加学生
        HashSet<Student> hs = new HashSet<>();

        //3.添加元素
        System.out.println(hs.add(s1));
        System.out.println(hs.add(s2));
        System.out.println(hs.add(s3));
        System.out.println(hs.add(s4));

        //4.打印集合
        System.out.println(hs);

    }
}
1.1.2.4.3.LinkedHashSet  

应用背景:需要去重的同时保证有序。否则默认使用HashSet(效率更高)。

特点:有序、不重复、无索引

  • 有序的底层原理:底层结构也是哈希表,但是每个元素额外多了双链表机制,元素会互相记录地址值,相当于记录了存储的先后顺序。存是如何存,取就是如何取。

    public static void main(String[] args) {
        //创建4个学生对象
        Student s1 = new Student("zhangsan",23);
        Student s2 = new Student("lisi",24);
        Student s3 = new Student("wangwu",25);
        Student s4 = new Student("zhangsan",23);

        //创建集合的对象
        LinkedHashSet<Student> lhs = new LinkedHashSet<>();

        //添加元素
        System.out.println(lhs.add(s1));//true
        System.out.println(lhs.add(s2));//true
        System.out.println(lhs.add(s3));//true
        System.out.println(lhs.add(s4));//false

        //打印集合
        System.out.println(lhs);//最终打印出来的顺序和添加时候的顺序相同[s1,s2,s3]
    }
}
1.1.2.4.4.TreeSet 

TreeSet底层数据结构是红黑树

特点:不重复、无索引、可排序

  • 可排序:默认按照元素大小顺序进行排列。
//TreeSet的可排序和遍历方式演示
	public static void main(String[] args) {
        //创建TreeSet集合对象,同时指定数据类型Integer
        TreeSet<Integer> ts = new TreeSet<>();

        //添加元素
        ts.add(4);
        ts.add(5);
        ts.add(1);
        ts.add(3);
        ts.add(2);

        //打印集合
        System.out.println(ts);//打印得[1,2,3,4,5]

        //遍历集合(三种遍历)
        //迭代器
        Iterator<Integer> it = ts.iterator();
        while(it.hasNext()){
            int i = it.next();
            System.out.println(i);
        }

        //增强for
        for (int t : ts) {
            System.out.println(t);
        }

        //lambda
        ts.forEach( i-> System.out.println(i));

    }

TreeSet集合添加时的比较规则(Comparable 和 Comparator 的区别)

  • 对于数值类型,默认按照升序排列;

    • 底层:对于Integer这类包装类,已经默认实现了Comparable接口,并重写了其中的方法。

  • 对于字符、字符串类型,按照字符在ASCII码表中数字的升序进行排列

    • 底层:对于String,已经默认实现了Comparable接口,并重写了其中的方法。

    • 比较字符串时,先比第一个字母,如果能比出结果,则直接排序;如果比不出结果,再比第二个字母,以此类推;如果比到某一位没有字母,而另一个字符串有字母,那么认为有字母的比较大。

  • 对于自定义对象类型,如果没有指定比较规则,那么在给TreeSet添加自定义对象的时候就会出现报错。

    • 对象类型比较规则:默认使用第一种,如果不能满足需求则使用第二种

      • 默认比较:JavaBean类实现Comparable接口,重写里面的抽象方法,再指定比较规则

 

  • 比较器排序:创建TreeSet对象的时候采用TreeSet当中的有参构造方法,传递Comparator比较器,也就是传递一个Comparator接口的实现类对象,在重写的方法中指定比较规则

   //练习1:默认排序	
	/* 按照总分从高到低输出到控制台
    如果总分一样,按照语文成绩排
    如果语文一样,按照数学成绩排
    如果数学成绩一样,按照英语成绩排
    如果英文成绩一样,按照年龄排
    如果年龄一样,按照姓名的字母顺序排
    如果都一样,认为是同一个学生,不存。*/
    //采用默认比较的规则,JavaBean类实现Comparable接口,重写里面的抽象方法compareTo(),再指定比较规则
	@Override
    public int compareTo(Student2 o) {
        int sum1 = this.getChinese() + this.getMath() + this.getEnglish();
        int sum2 = o.getChinese() + o.getMath() + o.getEnglish();

        //比较两者的总分
        int i = sum1 - sum2;
        //如果总分一样,就按照语文成绩排序
        i = i == 0 ? this.getChinese() - o.getChinese() : i;
        //如果语文成绩一样,就按照数学成绩排序
        i = i == 0 ? this.getMath() - o.getMath() : i;
        //如果数学成绩一样,按照英语成绩排序(可以省略不写)
        i = i == 0 ? this.getEnglish() - o.getEnglish() : i;
        //如果英文成绩一样,按照年龄排序
        i = i == 0 ? this.getAge() - o.getAge() : i;
        //如果年龄一样,按照姓名的字母顺序排序
        i = i == 0 ? this.getName().compareTo(o.getName()) : i;
        return i;
    }

		//在测试类中添加对象
	    //创建学生对象
        Student2 s1 = new Student2("zhangsan",23,90,99,50);
        Student2 s2 = new Student2("lisi",24,90,98,50);
        Student2 s3 = new Student2("wangwu",25,95,100,30);
        Student2 s4 = new Student2("zhaoliu",26,60,99,70);
        Student2 s5 = new Student2("qianqi",26,70,80,70);

        //创建集合
        TreeSet<Student2> ts = new TreeSet<>();

        //添加元素
        ts.add(s1);
        ts.add(s2);
        ts.add(s3);
        ts.add(s4);
        ts.add(s5);

 

		//练习2:比较器排序
		//要求:存入四个字符串, “c”, “ab”, “df”, “qwer”
        //按照长度排序,如果一样长则按照首字母排序

        //创建对象的同时传递一个Comparator接口的实现类的对象
        //o1:表示当前要添加的元素
        //o2:表示已经在红黑树存在的元素
        TreeSet<String> ts = new TreeSet<>((o1, o2)->{
                // 按照长度排序
                int i = o1.length() - o2.length();
                //如果一样长则按照首字母排序。o1.compareTo(o2),o1调用Comparable接口中的compareTo方法来进行默认比较
                i = i == 0 ? o1.compareTo(o2) : i;
                return i;
        });
  • 40
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值