java中集合框架学习

目录

1. Collection接口

1.1 Set接口

1.1.1 HashSet

1.1.2 TreeSet    

1.2 List接口

1.2.1 ArrayList类

1.2.2 LinkedList类 

1.2.3 ArrayList和LinkedList的区别

1.3 Vector接口


       在java日常编码中集合框架数据类型被极其广泛的使用,用它们来处理数据非常方便,其重要性不言而喻。但是对于常用的像List、Set、Queue、Map等容器类自身的理解和实现原理,很多小伙伴包括博主自己都一知半解,仅仅局限于工具类的使用。想要用好这些强大的容器类,必须对其自身有足够的了解,弄懂底层的数据结构类型,才能在合适的场景用合适的容器,才能使我们的程序效率最大化,下面我逐步介绍几个常用的集合类:

先看第一张图片:

1. Collection接口

Collection位于Java.util包下,是一个比较上层的接口。该接口实现的子接口很多,但主要用到的有以下几个: List、Set和Queue。其中作为容器的话又以List和Set使用最多,也是我们学习的重点。一般来说,在工作当中不会直接使用Collection进行数据的存储,但是作为父接口它仍然支持这些操作。所以我们还是有必要进行学习。因为Collection是接口不能被实例化,所以我们可以利用它的一个子类来演示它的方法,具体如代码如下所示:

package com.example.demo;

import java.util.ArrayList;
import java.util.Collection;

public class CollectionDemo {
    public static void main(String[] args) {
        Collection<String> cl = new ArrayList<>();
        cl.add("土豆");
        cl.add("菜花");
        cl.add("黄瓜");
        System.out.println(cl);
    }
}

以上代码演示了集合类添加元素的方法

输出结果:  [土豆, 菜花, 黄瓜]

1.1 Set接口

       Set接口扩展自Collection接口,所以Collection提供的方法Set都是支持的。但与此同时,Set有着自身的特性,这些特性是基于数据结构的,也是为了让我们适应更多的存储场景。首先Set接口不允许重复元素,也不区分先后顺序,但它允许元素是nu11值.
Set接口的具体实现包括HashSet和TreeSet。一般情况下,我们只要学会了这两个实现类,并且做到可以区分两者的不同,就可以算是学会Set集合了。从方法树来分析Collection和Set的方法是一样的,没有额外多出来的方法,这说明两者是同一容器只不过特性不一样。

1.1.1 HashSet

       HashSet是基于Hash算法实现的,性能比TreeSet好,特点是增加删除比较块,代码示例如下:

package com.example.demo;

import java.util.HashSet;

public class CollectionDemo {
    public static void main(String[] args) {
        HashSet<String> hashSet = new HashSet<>();
        hashSet.add("格瑞");
        hashSet.add("格瑞");
        hashSet.add("夏尔");
        hashSet.add("夏尔");
        hashSet.add("动漫");
        hashSet.add("动漫");
        System.out.println(hashSet);
    }
}

以上代码可以看到Set集合中的元素自动去重了,HashSet不是线程安全的,HashSet本质还是在内部维护一个HashMap对象,将所有的数据都交给HashMap进行处理,这一点可以从其源码看出:

// 内部初始化HashMap
public HashSet() {
    map = new HashMap<>();
}
//将一个集合添加到HashMap中
public HashSet(Collection<? extends E> c) {
    map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
    addAll(c);
}
// 初始化一个HashMap,指定容量和加载因子
public HashSet(int initialCapacity, float loadFactor) {
    map = new HashMap<>(initialCapacity, loadFactor);
}
// 初始化一个HashMap,指定容量
public HashSet(int initialCapacity) {
    map = new HashMap<>(initialCapacity);
}

 HashSet的特点主要如下:

  • 无序的
  • 不允许元素重复
  • 最多只有一个null值(允许null元素,但由于去重,所以只能保留一个null值)
  • 不是同步的,不安全
1.1.2 TreeSet    

        TreeSet实际上是TreeMap实现的,底层用到的数据结果是红黑树。当我们构造TreeSet时;若使用不带参数的构造函数,则TreeSet的使用自然比较器;若用户需要使用自定义的比较器,则需要使用带比较器的参数。

与HashSet相比,TreeSet的性能稍低些。add、remove、search等操作时间复杂度为O(log n),按照存储顺序打印n个元素则耗时为O(n)。

如果我们想要按序保存条目,并且按照升序或者降序对集合进行访问和遍历,那么TreeSet就应该作为首选集合。升序方式的操作与视图性能要强于降序方式。

        TreeSet 采用无参构造方法时创建TreeSet<T> treeSet = new TreeSet<>(),要求放入集合中的元素必须实现Comparable接口,重写compareTo()方法自定义比较规则,适合规则比较固定的情况,代码如下所示:

package com.example.test;

import lombok.Builder;
import lombok.Data;

/**
 * @Title: PersonTest
 * @Description: TODO
 * @author: hulei
 * @date: 2023/7/22 9:03
 * @Version: 1.0
 */
@Data
@Builder
public class Person implements  Comparable<Person> {
    private String name;

    private int age;

    private long height;
    /**
     * 重写比较方法(按照年龄大小正序排列)
     * @param o the object to be compared.
     * @return 相减结果小于0或者等于0不交换位置,大于0左右交换
     */
    @Override
    public int compareTo(Person o) {
        return this.age-o.age;
    }
}

以下为测试代码 

package com.example.test;

import java.util.HashMap;
import java.util.Map;
import java.util.TreeSet;

/**
 * @Title: Client
 * @Description: TODO
 * @author: hulei
 * @date: 2023/7/22 9:02
 * @Version: 1.0
 */
public class Client {
    public static void main(String[] args) {
        Person personOne = Person.builder()
                .name("张三")
                .age(15)
                .height(170)
                .build();
        Person personTwo = Person.builder()
                .name("李四")
                .age(17)
                .height(175)
                .build();
        Person personThree = Person.builder()
                .name("王五")
                .age(18)
                .height(180)
                .build();
        TreeSet<Person> personTreeSet = new TreeSet<>();
        personTreeSet.add(personThree);
        personTreeSet.add(personOne);
        personTreeSet.add(personTwo);
        System.out.println(personTreeSet);
    }
}

测试结果如下:

 可以看到Peson类中重写了比较方法,即使添加顺序错乱,也能自动按照年龄大小正向排序。

下面看第二种排序,在构造器TreeSet的时候给它传一个比较器对象,TreeSet<T> wuGui = new TreeSet<>(new Comparator()),适合规则经常变换的场景,比较器对象内定义了排序方式,代码如下:

package com.example.test;

import java.util.Comparator;

/**
 * @Title: AgeComparator(自定义年龄比较器)
 * @Description: TODO
 * @author: hulei
 * @date: 2023/7/22 13:35
 * @Version: 1.0
 */
public class AgeComparator implements Comparator<Person> {

    /**
     * 倒序(正序为o1.getAge()-o2.getAge())
     * @param o1 the first object to be compared.
     * @param o2 the second object to be compared.
     * @return 比较结果
     */
    @Override
    public int compare(Person o1, Person o2) {
        return o2.getAge()-o1.getAge();
    }
}

Peson类去除实现Comparable接口 

调用示例改写:

执行结果如下所示,可以看到年龄按照自定义比较器规则降序排序 :

TreeSet主要特点如下:

  1. 无序:放入集合中的顺序和从集合中取出的顺序不相同

  2.传入的值不能重复:放入集合中的值不能重复,重复则覆盖。

  3.传入的值不能为null:不可往TreeSet集合中传入null,否则编译抛出NullPointerException异常。

  4.排序:从集合中取出时必须按照其自然顺序排序,要求使用该集合的对象的类必须实Compare或者在创建TreeSet时  在构造方法中传入 一个比较器接口(Comparator)的子类对象或者 匿名内部类。

1.2 List接口

      List接口继承自Collection接口,所以也拥有Collection接口定义的功能,这里不再过多赘述,下面详细介绍常用的实现类。

1. public boolean add(E e);  //添加元素到集合
2. public boolean addAll(Collection<? extends E> c); //存放一个集合
3. public boolean contains(Object o);  //查找集合中的元素
4. public boolean isEmpty();  //判断一个集合是否为空
5. public boolean remove(Object 0);//删除一个集合中的元素
6. public int size();//返回集合的长度
1.2.1 ArrayList类

        ArrayList一般用来查询速度较快,数组在内存空间上是连续的。 ArrayList是实现List接口的动态数组,大小是可变的,可在运行时根据需要动态扩容,允许null元素,默认初始容量是10,在每次添加元素时,ArrayList都会检查是否需要进行扩容操作,每次扩容1.5倍(太大造成内存浪费,太小内存又不合理需要多次扩容影响效率),扩容操作带来数据向新数组的重新拷贝,所以如果我们知道具体业务数据量,在构造ArrayList时,可以给ArrayList 指定一个初始容量,这样就会减少扩容时的拷贝问题。当然在添加大量元素前,应用程序也可以使用ensureCapacity操作来增加ArrayList实例的容量,这可以减少递增式再分配的数量,如果已经预知容器可能会装多少元素,最好显示的调用ensureCapacity这个方法一次性扩容到位。示例如下:

List list = new ArrayList<>();

list.ensureCapacity(2000);

ArrayList 的实现不是同步的如果多个线程同时访问一个ArrayList实例,而其中至少一个线程从结构上修改了列表,那么它必须保持外部同步。所以为了保证同步,最好的办法是在创建时完成,以防止意外对列表进行不同步的访问:

List list = Collections.synchronizedList(new ArrayList(...));

1.2.2 LinkedList类 

        LinkedList集合数据存储的结构是双向链表结构。方便元素添加,删除,直接找到要插入的前置节点和后置节点来进行插入和删除;查找慢,因为底层采用二分法的查找算法。

1.2.3 ArrayList和LinkedList的区别

        ArrayList有一些特征:增加数据,删除数据 效率慢:
       1.可以会涉及到数组的扩容,牵涉到数组的复制问题
       2.增加数据或者删除数据的时候,可能牵涉到数组的数据整体右移或者左移
       查询快:底层是数组,一般使用数组名字 + 索引进行直接定位 

        LinkedList 底层是双向链表,并且实现了一个接口队列(先进先出)Deque
        查找慢:因为底层是采用的二分法查找的算法 时间复杂度是n/2   不是一下就找到的
        增删快:直接找到要插入的前置节点和后置节点,来进行插入或删除,其他元素不用。

1.3 Vector接口

       Vector 的思路和 ArrayList 基本是相同的,底层是数组保存元素,Vector 默认的容量是10,有一个增量系数,如果指定,那么每次都会增加一个系数的大小,否则就扩大一倍。

Vector 扩容的时候,其实就是数组的复制,其实还是比较耗时间的,所以,我们使用的时候应该尽量避免比较消耗时间的扩容操作。

Vector 和 ArrayList 最大的不同,是它是线程安全的,几乎每一个方法都加上了 Synchronize 关键字,所以它的效率相对也比较低一点。ArrayList 如果需要线程安全,可以使用 Collections.synchronizedList(new ArrayList(...)); 方法,获取一个线程安全的 List。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值