你真的了解集合吗,来给我说一下集合的底层数据结构!(中)

简介: 数据结构就是计算机存储、组织数据的方式。 在计算机科学中,算法的时间复杂度是一个函数,它定性描述了该算法的运行时间,常用O符号来表述。 时间复杂度是同一问题可用不同算法解决,而一个算法的质量优劣将影响到算法乃至程序的效率。算法分析的目的在于选择合适算法和改进算法

四、ArrayList类


ArrayList类,基于数组算法的列表,通过查看源代码会发现底层其实就是一个Object数组


ArrayList是一个List接口的实现类,实现了可变数组,当添加一个元素时,如果容量足够,直接添加,如果容量不够,按照newCapacity = oldCapacity + oldCapacity/2 原则拓容


由于ArrayList底层数据结构是数组,决定了该实现类在查询的场景下比较适用,添加和删除的场景效率稍低


public class ArrayListDemo1 {
    /**
      ArrayList的常用API 
    **/
public static void main(String[] args) {
//创建一个默认长度的列表对象
List list = new ArrayList();
//打印集合中元素的个数
System.out.println("元素数量:"+list.size());//0
//添加操作:向列表中添加4个元素
list.add("Will");
list.add(100);
list.add(true);
list.add("Lucy");
//查询操作:
System.out.println("列表中所有元素:"+list);//输出:[Will, 100, true, Lucy]
System.out.println("元素数量:"+list.size());//4
System.out.println("第一个元素:"+list.get(0));//Will
//修改操作:把索引为2的元素,替换为wolfcode
list.set(2, "wolfcode");
System.out.println("修改后:"+list);//输出:[Will, 100, wolfcode, Lucy]
//删除操作:删除索引为1的元素
list.remove(1);
System.out.println("删除后:"+list);//输出:[Will, wolfcode, Lucy]
}
}
复制代码

我们画出ArrayList的内存图可以发现集合类中存储的对象,都存储的是对象的引用,而不是对象本身

26.JPG


五、LinkedList类


LinkedList由于底层数据结构是链表,在添加元素和删除元素时效率很高,查询元素效率低。 LinkedList类,底层采用链表算法,实现了链表,队列,栈的数据结构。无论是链表还是队列主要操作的都是头和尾的元素,因此在LinkedList类中除了List接口的方法,还有很多操作头尾的方法。常用的方法如下:


//将指定元素插入此列表的开头。
void addFirst(Object e);
//将指定元素添加到此列表的结尾。
void addLast(Object e); 
//返回此列表的第一个元素。
Object getFirst();
//返回此列表的最后一个元素。
Object getLast();
//移除并返回此列表的第一个元素。
Object removeFirst(); 
//移除并返回此列表的最后一个元素。
Object removeLast();
//在此列表的开头插入指定的元素。
boolean offerFirst(Object e);
//在此列表末尾插入指定的元素。
boolean offerLast(Object e);
////获取但不移除此列表的第一个元素;如果此列表为空,则返回 null。
Object peekFirst();
////获取但不移除此列表的最后一个元素;如果此列表为空,则返回 null。
Object peekLast();
////获取并移除此列表的第一个元素;如果此列表为空,则返回 null。
Object pollFirst();
//获取并移除此列表的最后一个元素;如果此列表为空,则返回 null。
Object pollLast();
//将元素推入此列表所表示的栈。
void push(Object e);
//从此列表所表示的栈处弹出一个元素。
Object pop();
//获取但不移除此列表的头(第一个元素)。
Object peek() 
复制代码
import java.util.LinkedList;
public class LinkedListDemo {
  public static void main(String[] args) {
    LinkedList list = new LinkedList();
//添加元素
    list.addFirst("A");
    list.addFirst("B");
    System.out.println(list);
    list.addFirst("C");
    System.out.println(list);
    list.addLast("D");
    System.out.println(list);
//获取元素
    System.out.println("获取第一个元素:" + list.getFirst());//C
    System.out.println("获取最后一个元素:" + list.getLast());//D
//删除元素
    list.removeFirst();
    System.out.println("删除第一个元素后:" + list);//[B, A, D]
    list.removeLast();
    System.out.println("删除最后一个元素后:" + list);//[B, A]
  }
}
复制代码


六、Stack和Vector类


Vector类:基于数组算法实现的列表,其实就是ArrayList类的前身。和ArrayList的区别在于方法使用synchronized修饰,所以相对于ArrayList来说,线程安全,但是效率就低了点。 Stack类:表示栈,是Vector类的子类,具有后进先出(LIFO)的特点,拥有push(入栈),pop(出栈)方法。


七、集合的遍历


List<String> list = new ArrayList<>();
  list.add("西施");
  list.add("王昭君");
  list.add("貂蝉");
  list.add("杨玉环");
复制代码


7.1、for循环


for (int index = 0; index < list.size(); index++) {
  String ele = list.get(index);
  System.out.println(ele);
}
复制代码


7.2、使用迭代器


在程序开发中,经常需要遍历集合中的所有元素。针对这种需求,JDK专门提供了一个接口java.util.Iterator


想要遍历Collection集合,那么就要获取该集合迭代器完成迭代操作,下面介绍一下获取迭代器的方法:


  • public Iterator iterator(): 获取集合对应的迭代器,用来遍历集合中的元素的。


Iterator表示迭代器对象,迭代器中拥有一个指针,默认指向第一个元素之前,有如下几个方法:


  1. public E next():判断指针后是否存在下一个元素
  2. public boolean hasNext():获取指针位置下一个元素,获取后指针向后移动一位,如果仍有元素可以迭代,则返回 true


Iterator iterator = list.iterator();
    while (iterator.hasNext()){
      System.out.println(iterator.next());
    }
复制代码


27.JPG


注意:


  1. 在进行集合元素获取时,如果集合中已经没有元素了,还继续使用迭代器的next方法,将会抛出java.util.NoSuchElementException没有集合元素异常。
  2. 在进行集合元素获取时,如果添加或移除集合中的元素 , 将无法继续迭代 , 将会抛出ConcurrentModificationException并发修改异常。


7.3、for-each遍历(增强for循环)


语法:


for(数据类型 迭代变量: 实例对象){
  //TODO
}
复制代码
for (String ele : list) {
  System.out.println(ele);
}
复制代码


7.4、forEach的函数式编程遍历


list.forEach(object -> System.out.println(object));
复制代码


7.5、并发修改异常


在迭代集合时删除集合元素,比如删除王昭君


List<String> list = new ArrayList<>();
    list.add("西施");
    list.add("王昭君");
    list.add("貂蝉");
    list.add("杨玉环");
    for (String ele : list){
      if ("王昭君".equals(ele)){
        list.remove(ele);
      }
      System.out.println(ele);
    }
复制代码

执行完会发现报错了:


28.JPG

造成该错误的原因是,不允许在迭代过程中改变集合的长度(不能删除和增加)


如果要在迭代过程中删除元素,就不能使用集合的remove方法,只能使用迭代器的remove方法,此时只能使用迭代器来操作,不能使用foreach。


List<String> list = new ArrayList<>();
    list.add("西施");
    list.add("王昭君");
    list.add("貂蝉");
    list.add("杨玉环");
  Iterator<String> iterator = list.iterator();
    while (iterator.hasNext()){
      if ("王昭君".equals(iterator.next())){
        iterator.remove();
      }
    }
    System.out.println(list);
复制代码


八、Set接口


Set是Collection的子接口,Set接口定义了一种规范,也就是说明该容器不记录元素的添加顺序,也不允许元素重复。


8.1、Set集合的特点


  1. 不允许元素重复
  2. 不会记录元素添加的先后顺序


set只包含从Collection继承的方法,不过Set无法记住添加的顺序,也不允许元素重复,当将两个相同的元素添加进Set集合的时候,添加操作失败,添加方法(add())返回false


8.2、Set接口常用的实现类


  1. HashSet类:底层采用哈希表实现
  2. TreeSet类:底层采用红黑书实现,可以对集合中的元素排序


8.3、HashSet


8.3.1、HashSet原理


29.JPG

HashSet底层采用哈希表实现,元素对象的HashCode值决定了在哈希表中存储的位置,当往HashSet中添加新元素的时候,先会判断该位置是否有元素:


  1. 如果没有,直接把该新的对象存储到hashCode指定的位置
  2. 如果有,再继续判断新对象和集合对象的equals作比较:
  • 若equals为true,则视为同一个对象,不保存,add()方法返回false
  • 如果equals为false,则存储在之前的对象的同一个位置上开辟一个链表进行存储


每一个存储到哈希表中的对象,都得重写hashCode和equals方法来判断是否是同一个对象,在一般情况下,equals为true的时候,hashCode应该也相等,Idea可以自动生成这些方法


8.3.2、HashSet常用方法


package day14_ArrayList2.classing;
    import java.util.HashSet;
    import java.util.Iterator;
    import java.util.Set;
/**
 * @author Xiao_Lin
 * @version 1.0
 * @date 2020/12/17 19:38
 */
public class SetApi {
  public static void main(String[] args) {
    Set<String> set = new HashSet<>();
    //添加操作,向列表中添加元素
    set.add("will");
    set.add("wolf");
    set.add("code");
    set.add("lucy");
    //查询操作
    System.out.println("集合中的所有元素为:"+set);
    System.out.println("元素数量:"+set.size());
    System.out.println("是否存在某个元素:"+set.contains("111"));
    System.out.println("是否存在某个元素:"+set.contains("code"));
    //删除元素
    set.remove("code");
    System.out.println("删除后的集合为:"+set);
    //增强for循环遍历
    for (String ele : set){
      System.out.println(ele);
    }
    //迭代器遍历
    Iterator<String> it = set.iterator();
    while (it.hasNext()){
      System.out.println( it.next());
    }
  }
}
复制代码
package day14_ArrayList2.classing;
/**
  重写equals和hashCode方法
 * @author Xiao_Lin
 * @version 1.0
 * @date 2020/12/17 11:34
 */
public class Student {
  private String name;
  private Integer sn;
  private String classname;
  @Override
  public boolean equals(Object o) {
    if (this == o) {
      return true;
    }
    if (o == null || getClass() != o.getClass()) {
      return false;
    }
    Student student = (Student) o;
    if (name != null ? !name.equals(student.name) : student.name != null) {
      return false;
    }
    if (sn != null ? !sn.equals(student.sn) : student.sn != null) {
      return false;
    }
    return classname != null ? classname.equals(student.classname) : student.classname == null;
  }
  @Override
  public int hashCode() {
    int result = name != null ? name.hashCode() : 0;
    result = 31 * result + (sn != null ? sn.hashCode() : 0);
    result = 31 * result + (classname != null ? classname.hashCode() : 0);
    return result;
  }
  public Student(String name, Integer sn, String classname) {
    this.name = name;
    this.sn = sn;
    this.classname = classname;
  }
  public Student(){
  }
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
  public Integer getSn() {
    return sn;
  }
  public void setSn(Integer sn) {
    this.sn = sn;
  }
  public String getClassname() {
    return classname;
  }
  public void setClassname(String classname) {
    this.classname = classname;
  }
  @Override
  public String toString() {
    return "Student{" +
        "name='" + name + '\'' +
        ", sn=" + sn +
        ", classname='" + classname + '\'' +
        '}';
  }
}
复制代码


8.4、TreeSet


TreeSet底层采用了红黑树算法,会对存储的元素对象默认使用自然排序(升序)


数据类型 排序规则
BigDecimal、BigInteger、Byte、Double、Float、Integer、Long、Short 按照数字大小排序
Character 按照字符的Unicode值的数字大小排序
String 按照字符串中字符的Unicode值排序

必须保证TreeSet集合中的元素对象是相同的数据类型,否则报错


import java.util.Set;
import java.util.TreeSet;
public class TreeSetDemo{
  public static void main(String[] args) {
    Set<String> set = new TreeSet<>();
    set.add("wolf");
    set.add("will");
    set.add("sfef");
    set.add("allen");
    System.out.println(set);//[allen, sfef, will, wolf]
  }
}
复制代码


8.4.1、TreeSet底层数据结构详解


第一步

插入第一个节点,无需比较,直接作为根节点

31.JPG


第二步

插入第二个节点,和wolf根节点比较,如果小于根节点就作为左子树,如果大于根节点就放在右边,作为右子树


32.JPG


第三步

插入第三个节点,先和wolf根节点比较,较小,左移动作为左子树,因为左边已经有了一颗左子树,所以再和will节点进行比较,较小,继续放在左边作为左子树


33.JPG


第四步

由于TreeSet是平衡二叉树,如果树不平衡(左右子树差值大于1),会对节点进行调整

34.JPG


第五步

插入第四个节点,先和根节点will进行比较,较小,左移,再和sfef节点作比较,依然较小,左移


35.JPG


8.4.2、Comparable接口


在缺省的情况下,TreeSet中的元素会采用自然排序(从小到大),此时要求元素对象必须实现java.util.Comparable接口,大多数的JDK自带的类都实现了该接口,比如说最常见的八大包装类和String TreeSet会调用元素的comparaTo()方法来比较元素的大小关系,然后将集合元素按照升序排列


public interface Comparable<T> {
  public int compareTo(T o);
}
复制代码


比较规则


  • 当前元素大于传进来的元素,返回正整数1,优先级较高
  • 当前元素小于传进来的元素,返回负整数-1,优先级较低
  • 当前元素等于传进来的元素,返回0,此时认为两个对象是同一个对象


    如果我们自定义一个类,且需要存储到TreeSet中,此时我们需要让该类实现Comparable接口,并且覆盖compareTo()方法,在该方法中编写比较规则


package day14_ArrayList2.classing;
/**
 * @author Xiao_Lin
 * @version 1.0
 * @date 2020/12/17 11:34
 */
public class Student implements Comparable<Student>{
  private String name;
  private Integer sn;
  private String classname;
  //比较规则
  @Override
  public int compareTo(Student student) {
    //当前对象大于传进来的对象
    if (this.sn > student.sn){
      return 1;
    }else if (this.sn < student.sn){
      return -1;
    }
    return 0;
    //可以简化为;
    //return this.sn - student.sn;
  }
  @Override
  public boolean equals(Object o) {
    if (this == o) {
      return true;
    }
    if (o == null || getClass() != o.getClass()) {
      return false;
    }
    Student student = (Student) o;
    if (name != null ? !name.equals(student.name) : student.name != null) {
      return false;
    }
    if (sn != null ? !sn.equals(student.sn) : student.sn != null) {
      return false;
    }
    return classname != null ? classname.equals(student.classname) : student.classname == null;
  }
  @Override
  public int hashCode() {
    int result = name != null ? name.hashCode() : 0;
    result = 31 * result + (sn != null ? sn.hashCode() : 0);
    result = 31 * result + (classname != null ? classname.hashCode() : 0);
    return result;
  }
  public Student(String name, Integer sn, String classname) {
    this.name = name;
    this.sn = sn;
    this.classname = classname;
  }
}
复制代码


8.4.3、Comparator接口


TreeSet除了支持默认的自然排序外,还支持自定义排序,此时需要在构建TreeSet对象时传递java.util.Comparator接口的实现类对象,Comparator表示比较器,里面封装了比较规则。


此时compare方法返回0,则认为两个对象是同一个对象,返回正数排前面,返回负数排后面。


public interface Comparator<T> {
  int compare(T o1, T o2);
}
复制代码

示范

class User {
  private String name;
    import java.util.TreeSet;
private int age;
public User(String name, int age) {
    this.name = name;
    this.age = age;
    }
public int getAge() {
    return age;
    }
public String getName() {
    return name;
    }
public String toString() {
    return "User [name=" + name + ", age=" + age + "]";
    }
    }
public class App {
  public static void main(String[] args) {
    Set<User> set = new TreeSet<>(new NameLengthComparator());
    set.add(new User("James", 30));
    set.add(new User("Bryant", 22));
    set.add(new User("Allen", 28));
    set.add(new User("Will", 17));
    System.out.println(set);//
  }
}
//封装了比较规则
class NameLengthComparator implements java.util.Comparator<User> {
  public int compare(User o1, User o2) {
    int ret = o1.getName().length() - o2.getName().length();
    if (ret > 0) {
      return 1;
    } else if (ret < 0) {
      return -1;
    } else {
      if (o1.getAge() > o2.getAge()) {
        return 1;
      } else if (o1.getAge() < o2.getAge()) {
        return -1;
      } else {
        return 0;
      }
    }
  }
}



相关文章
|
3月前
|
Java
【Java集合类面试二十六】、介绍一下ArrayList的数据结构?
ArrayList是基于可动态扩展的数组实现的,支持快速随机访问,但在插入和删除操作时可能需要数组复制而性能较差。
|
1月前
|
算法 安全 Java
【用Java学习数据结构系列】探索Java集合框架的无尽秘密pro
【用Java学习数据结构系列】探索Java集合框架的无尽秘密pro
19 1
|
6月前
|
算法 Java 数据库连接
Spring+MySQL+数据结构+集合,Alibaba珍藏版mybatis手写文档
Spring+MySQL+数据结构+集合,Alibaba珍藏版mybatis手写文档
|
2月前
|
存储 索引 Python
Python常用数据结构——集合
Python常用数据结构——集合
|
2月前
|
存储 Java 程序员
【数据结构】初识集合&深入剖析顺序表(Arraylist)
Java集合框架主要由接口、实现类及迭代器组成,包括Collection和Map两大类。Collection涵盖List(有序、可重复)、Set(无序、不可重复),Map则由键值对构成。集合通过接口定义基本操作,具体实现由各类如ArrayList、HashSet等提供。迭代器允许遍历集合而不暴露其实现细节。List系列集合元素有序且可重复,Set系列元素无序且不可重复。集合遍历可通过迭代器、增强for循环、普通for循环及Lambda表达式实现,各有适用场景。其中ArrayList实现了动态数组功能,可根据需求自动调整大小。
40 11
|
3月前
|
存储 安全
集合的特点和数据结构总结
集合的特点和数据结构总结
29 1
|
5月前
|
存储 Python 容器
Python零基础入门-5 数据结构(集合和字典)
Python零基础入门-5 数据结构(集合和字典)
|
5月前
|
算法 Java
Java数据结构与算法:用于处理不相交集合的合并和查找问题
Java数据结构与算法:用于处理不相交集合的合并和查找问题
|
5月前
|
存储 算法
数据结构学习记录——集合及运算(集合的表示、并查集、树结构表示集合、集合运算、查找函数、并运算)
数据结构学习记录——集合及运算(集合的表示、并查集、树结构表示集合、集合运算、查找函数、并运算)
33 0
|
6月前
|
存储 程序员 索引
数据结构深度剖析:列表、元组、字典和集合
【4月更文挑战第8天】Python的四种基础数据结构——列表、元组、字典和集合,各自拥有独特的特性和应用场景。列表是可变序列,方便增删改元素;元组不可变,常用于保证数据不变性;字典是键值对容器,快速访问通过键;集合是无序不重复元素集,适合成员测试和去重。理解并灵活运用这些数据结构,能提升代码效率,有效处理和分析数据。
82 1

热门文章

最新文章

下一篇
无影云桌面