Java集合(单列集合)的使用以及集合底层原理

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

本文章是对单列集合(Collection)的使用方法和底层原理的简单解释。
其中包含如何使用List集合和Set集合,包括它们的实现类ArrayList、LinkedList、HashSet、LinkedHashSet、TreeSet的使用场景和底层原理


一、集合是什么?

java集合是为储存、操作和管理集合数据而设计的一组接口、实现类和算法。说白了就是一个装数据的容器。主要分为单列集合(Collection)和双列集合(Map)。

二、单列集合(Collection)

1.什么是单列集合

Collection代表单列集合,每个元素(数据)只包含一个值。
Collection是一个接口,下面有List和Set两个子接口,List接口由ArrayList和LinkedList两个类实现。Set接口由HashSet和TreeSet两个类实现。其中HashSet还有一个子类LinkedHashSet继承。
Collection是单列集合的祖宗,他的功能方法所用单列集合都会继承。

2.Collection集合特点

List系列集合:有序、可重复
ArrayList、LinkedList
Set系列集合:无序、不重复
HashSet
LinkedHashSet:存储有序
TreeSet:可排序

3.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() 返回集合中元素的个数
poblic Object[] toArray() 把集合中的元素存储到数组中
public boolean addAll() 把一个集合添加到另一个集合中

Collection的一些常用方法使用
// An highlighted block
   public static void main(String[] args) {
        //多态创建单列集合
        Collection<String> arrayList = new ArrayList<>();
        //boolean add(E e) 添加元素
        arrayList.add("关羽");
        arrayList.add("张飞");
        arrayList.add("赵云");
        arrayList.add("张飞");
        //boolean remove(E e) 删除指定的元素 (如有重复删除第一个)
        arrayList.remove("张飞");
        System.out.println(arrayList);
        //boolean contains(Object obj) 判断集合中是否包含指定元素
        boolean b = arrayList.contains("赵云");
        System.out.println(b);
        //int size() 返回集合中元素的个数
        int size = arrayList.size();
        System.out.println(size);
        //boolean isEmpty() 判断集合是否为空
        System.out.println(arrayList.isEmpty());
        //Object[] toArray() 将集合中元素存入一个对象数组并返回
        System.out.println(Arrays.toString(arrayList.toArray()));
        //T[] toArray(T[]a)  将集合中元素存入一个指定类型的数组并返回(指定数组长度)
        System.out.println(Arrays.toString(arrayList.toArray(new String [arrayList.size()]  )));
        //一个集合直接添加另一个集合的所有元素
        Collection<String> collection2 = new ArrayList<>();
        collection2.add("黄忠");
        collection2.add("孙策");
        collection2.add("大乔");
        arrayList.addAll(collection2);
        System.out.println(arrayList);
        //void clear() 清空集合
        arrayList.clear();
    }

4.Collection的迭代方式

有三种迭代方式
1.使用迭代器(Iterator)
2.使用增强for
3.使用Lambda

(1).使用迭代器遍历Collection集合

迭代器是专门用来遍历集合的。Java中的迭代器是Iterator
Collection集合获取迭代器的方法:

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

用集合对象调用iterator()方法获取iterator对象,这个对象默认指向当前集合的第一个元素。
Iterator的常用方法:

boolean hasNext() 询问当前位置是否有元素存在,存在返回true,不存在返回false
E next() 获取当前位置的元素,并同时将迭代器对象指向下一个元素处

代码如下:

Iterator<String> iterator = collection.iterator();
       while (iterator.hasNext()) {
           String next = iterator.next();
           System.out.println(next);
       }

(2).使用增强for遍历Collection集合

使用增强for可以遍历集合和数组
增强for遍历集合,本质就是迭代器遍历集合的简化写法
格式:
for(元素的数据类型 变量名:数组或者集合){
}
代码如下:

		//1. 准备一个集合
       Collection<String> collection = new ArrayList<>();
       collection.add("java");
       collection.add("python");
       collection.add("c++");
       collection.add("c#");
       //2. 使用增强for循环遍历
       for (String s : collection) {
           System.out.println(s);
       }

(3).使用Lambda遍历Collection集合

Lambda表达式提供了一种更简单、更直接的方式遍历集合。
需要使用forEach()这个方法来完成
代码如下:

  		//1. 准备一个集合
        Collection<String> collection = new ArrayList<>();
        collection.add("java");
        collection.add("python");
        collection.add("c++");
        collection.add("c#");
        //2. Lambda表达式方式遍历集合
        collection.forEach((String)->{
            System.out.println(String);
        });

5.Collection并发修改异常

集合的并发修改异常:
使用迭代器遍历集合时,又同时在删除集合中的数据,程序就会出现并发修改异常的错误。
由于增强for循环遍历就是迭代器循环遍历的一种简化写法,所以,在使用增强for遍历集合时,同时删除集合中的数据时,程序也会出现并发修改异常的错误。
那么如何保证遍历集合时删除数据不出现bug呢?
方法如下:
1.使用迭代器遍历集合时:使用迭代器自己的删除方法删除数据即可
iterator.remove();
代码如下:

 ArrayList<String> arrayList = new ArrayList<>();
       arrayList.add("Java入门");
       arrayList.add("宁夏枸杞");
       arrayList.add("黑枸杞");
       arrayList.add("人字拖");
       arrayList.add("特级枸杞");
       arrayList.add("枸杞子");
       //删除所有带枸杞的
       Iterator<String> iterator = arrayList.iterator();
       while (iterator.hasNext()){
           String next = iterator.next();
           if (next.contains("枸杞")){
           iterator.remove();
           }
       }
       System.out.println(arrayList);

2.使用增强for循环遍历集合:无法解决这个问题!!!
3.使用普通for循环遍历集合:可以倒着遍历并删除:或者从前往后遍历,但是需要删除元素后做 i - - 操作。(注意:这个普通for循环遍历不能遍历Collection集合。因为这个Collection没有索引,不能做i - -)

三、List集合(单列集合)

1.List集合的特有方法

List集合因为支持索引,所以多了很多索引相关的方法,当然,Collection的功能List也都继承了。
常用方法:

void add(int index,E element) 在此集合中的指定位置插入指定元素
E remove(int index) 删除指定索引处的元素,返回被删除的元素
E set(int index,E element) 修改指定索引处的元素,返回被修改的元素
E get(int index) 返回指定索引处的元素

list一些常用方法使用
List<String> list = new ArrayList<>();
list.add("张三");
list.add("李四");
list.add("王五");
list.add("王五");
//void add(int index,E e) 在此集合中的指定位置插入指定的元素
list.add(1,"小王");
//E remove(int index) 删除指定索引处的元素,返回被删除的元素 (一般不接收)
System.out.println(list.remove(1));
//E set(int index,E e) 修改指定索引处的元素,返回被修改的元素(一般不接收)
System.out.println(list.set(3, "王九"));
//E get(int index) 返回指定索引处的元素
System.out.println(list.get(3));
System.out.println(list);

2.List集合的遍历

List集合的遍历方式:
1.迭代器
2.增强for循环
3.Lambda表达式
4.普通for循环(因为List集合有索引)
代码实现如下:

      List<String> list = new ArrayList<>();
      list.add("张三");
      list.add("李四");
      list.add("王五");
      list.add("王五");
      //1. 迭代器
      Iterator<String> iterator = list.iterator();
      while (iterator.hasNext()){
          String next = iterator.next();
          System.out.println(next);
      }
      System.out.println("-----------------------");
      //2. 增强for循环
      for (String s : list) {
          System.out.println(s);
      }
      System.out.println("-----------------------");
      //3. Lambda表达式
      list.forEach((string)->{
          System.out.println(string);
      });
      System.out.println("-----------------------");
      //4. for循环(因为List集合有索引)
      for (int i = 0; i < list.size(); i++) {
          System.out.println(list.get(i));
      }

3.ArrayList

(1).ArrayList集合的底层原理

ArrayList底层是基于数组实现的。
特点:查询快;增、删慢。

查询速度快(注意:是根据索引查询数据快):查询数据通过地址值和索引定位,查询任意数据耗时相同。
删除效率低:可能需要把后面很多的数据进行前移。
添加效率极低:可能需要把后面的很多数据后移,在添加元素;或者也可能需要进行数组的扩容。

ArrayList底层的存放原理:

1.利用无参构造器创建的集合,会在底层创建一个默认长度为0的数组。
2.添加一个元素时,底层会创建一个新的长度为10的数组。
3.存满时,会扩容1.5倍
4.如果一次添加多个元素,1.5倍还放不下,则新创建新数组的长度以实际为准。

(2).ArrayList集合适合的应用场景

ArrryList适合:根据索引查询数据,比如根据随机索引取数据(高效)!或者数据量不是很大时!
ArrayList不适合:数据量大的同时,又频繁的进行增删操作。

4.LinkedList

(1).LinkedList集合的底层原理

LinkedList底层是基于双链表实现的
特点:查询慢;增、删相对较快。但是对首尾元素进行增删改查的速度是极快的。
LinkedList新增了很多首尾操作的特有方法:

public void addFirst(E e) 在该列表开头插入指定的元素
public void addLast(E e) 将指定的元素追加到此列表的末尾
public E getFirst() 返回此列表中的第一个元素
public E getLast() 返回此列表中的最后一个元素
public E removeFirst() 从此列表中删除并返回第一个元素
public E removeLast() 从此列表中删除并返回最后一个元素

(2).LinkedList集合适合的应用场景

可以用来设计队列(只在首尾增删元素,用LinkedList集合很合适)
也可以用来设计栈(数据进入栈模型称为进栈push;数据离开栈模型称为出栈pop)

四、Set集合(单列集合)

(注意:Set常用的方法基本上是Collection提供的,自己几乎没有额外的方法)
Set常见的实现类:
HashSet 无序、不重复
LinkedHashSet 有序、不重复
TreeSet 可排序、不重复

1.HashSet

(1).Hash值

哈希值就是一个int类型的数值,java中的每个对象都有一个哈希值。
java中所有对象都可以调用hashCode方法,返回自己的哈希值
对象哈希值的特点:

1.同一个对象多次调用hashCode()方法返回的哈希值是相同的
2.不同的对象,它们的哈希值一般不相同,但也有可能会相同(哈希碰撞)
3.Object的hashCode方法根据“对象地址值”计算哈希值,子类重写后的hashCode方法可以根据”对象属性值“计算哈希值

HashSet集合判定两个对象的标准就是两个对象的Hash值是否一致,因此我们经常重写hashCode实现集合中的对象去重。
代码如下:

  @Override
 public boolean equals(Object o) {
     if (this == o) return true;
     if (o == null || getClass() != o.getClass()) return false;
     Student student = (Student) o;
     return age == student.age && Objects.equals(name, student.name);
 }

 @Override
 public int hashCode() {
     return Objects.hash(name, age);
 }

这段代码一般用idea自动生成(快捷键 Alt+Ins 然后选择equals() and hashCode())

(2).HashSet底层原理

HashSet是基于哈希表实现的,哈希表是一种增删改查数据,性能都比较好的数据结构。
哈希表:
JDK8之前,哈希表 = 数组+链表
JDK8开始,哈希表 = 数组+链表+红黑树

1.创建一个默认长度为16的数组,默认加载因子为0.75,数组名table
2.使用元素的哈希值对数组的长度求余计算出应存入的位置
3.判断当前位置是否为null,如果是null直接存入
4.如果不为null,表示有元素,则调用equals方法比较相等,则不存;不相等,则存入
5.当数组存满到16*0.75=12时,就会自动扩容,每次扩容到原先的两倍

JDK8之前,新元素存入数组,占老元素位置,老元素挂下面
JDK8开始之后,新元素直接挂在老元素下面,但是当链表长度超过8,且数组长度>=64时,自动将链表转成红黑树。进一步提升了操作数据的性能。

2.LinkedHashSet

(1).LinkedHashSet底层原理

LinkedHashSet是不可重复的,存储有序的,底层是基于哈希表(数组+链表+红黑树)实现的。
(注意:它的每个元素都额外的多了一个双链表的机制记录它前后元素的位置)

3.TreeSet

(1).TreeSet的特点与底层原理

特点:不重复、无索引、可排序(默认升序排序,按照元素的大小,由小到大排序)
底层原理:基于红黑树实现的排序
注意:

1.对于数值类型:Integer,Double,默认按数值本身大小进行排序
2.对于字符串类型:默认按照首字母的编号升序排序
3.对于自定义类型如:Teacher对象,TreeSet默认是无法直接排序的

对于自定义的对象TreeSet无法自动排序,但是可以通过自定义排序规则进行排序,方式如下:
方式一:自然排序

让自定义的类(如教师类)实现Comparable接口,重写里面的comparaTo方法来指定比较规则。

代码如下:

public class Demo4 {

  public static void main(String[] args) {
      //创建TreeSet
      TreeSet<Teacher> treeSet = new TreeSet<>();
      //添加学生
      treeSet.add(new Teacher("张三", 19,23));
      treeSet.add(new Teacher("李四", 18,34));
      treeSet.add(new Teacher("王五", 20,56));
      treeSet.add(new Teacher("赵六", 17,67));
      treeSet.add(new Teacher("赵六", 17,78));

      //打印
      for (Teacher teacher : treeSet) {
          System.out.println(teacher.getName()+teacher.getAge());
      }
  }
}
class Teacher implements Comparable<Teacher>{

  private String name;
  private int age;

  private double score;

  public double getScore() {
      return score;
  }

  public void setScore(double score) {
      this.score = score;
  }

  public Teacher() {
  }

  public Teacher(String name, int age, double score) {
      this.name = name;
      this.age = age;
      this.score = score;
  }

  public String getName() {
      return name;
  }

  public void setName(String name) {
      this.name = name;
  }

  public int getAge() {
      return age;
  }

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

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


  @Override
  public int compareTo(Teacher o) {
      return this.getAge() > o.getAge() ? 1:-1;
  }
}

方式二:比较器排序

通过调用TreeSet集合有参数构造器,可以设置Comparator对象(比较器对象,用于指定比较规则)

代码如下:

public class Demo4 {

 public static void main(String[] args) {
     //创建TreeSet
     TreeSet<Teacher> treeSet = new TreeSet<>(new Comparator<Teacher>() {
         @Override
         public int compare(Teacher o1, Teacher o2) {
             return o1.getAge()>o2.getAge()?1:-1;
         }
     });
     //添加学生
     treeSet.add(new Teacher("张三", 19,23));
     treeSet.add(new Teacher("李四", 18,34));
     treeSet.add(new Teacher("王五", 20,56));
     treeSet.add(new Teacher("赵六", 17,67));
     treeSet.add(new Teacher("赵六", 17,78));

     //打印
     for (Teacher teacher : treeSet) {
         System.out.println(teacher.getName()+teacher.getAge());
     }
 }
}

class Teacher{

 private String name;
 private int age;

 private double score;

 public double getScore() {
     return score;
 }

 public void setScore(double score) {
     this.score = score;
 }

 public Teacher() {
 }

 public Teacher(String name, int age, double score) {
     this.name = name;
     this.age = age;
     this.score = score;
 }

 public String getName() {
     return name;
 }

 public void setName(String name) {
     this.name = name;
 }

 public int getAge() {
     return age;
 }

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

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

总结

最后总结一下各个集合使用场景:
1、如果希望记住元素的添加顺序,需要存储重复的元素,又要频繁的根据索引查询数据?
用ArrayList集合(有序、可重复、有索引),底层基于数组的。(常用)
2、如果希望记住元素的添加顺序,且增删首尾数据的情况较多?
用LinkedList集合(有序、可重复、有索引),底层基于双链表实现的。
3、如果不在意元素顺序,也没有重复元素需要存储,只希望增删改查都快?
用HashSet集合(无序、不重复、无索引),底层基于哈希实现的。(常用)
4.如果希望记住元素的添加顺序,也没有重复元素需要存储,且希望增删改查都快?
用LinkedHashSet集合(有序、不重复、无索引),底层基于哈希表和双链表。
5.如果要对元素进行排序,也没有重复元素需要存储?且希望增删改查都快?
用TreeSet集合,底层基于红黑树实现。

  • 23
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值