Java基础学习之单列集合

Collection单列集合

Collection接口下面又有两个子接口List接口、Set接口,List和Set下面分别有不同的实现类

List系列集合:添加的元素是有序、可重复、有索引。
    ArrayList、LinekdList: 有序、可重复、有索引。

set系列集合:添加的元素是无序、不重复、无索引。
    HashSet: 无序、不重复、无索引;
    LinkedHashSet: 有序、不重复、无索引。
    TreeSet: 按照大小默认升序排序、不重复、无索引

Collection集合的常用方法

这些方法所有Collection实现类都可以使用。 这里我们以创建ArrayList为例

Collection c = new ArrayList<>();
//1.public boolean add(E e): 添加元素到集合
c.add("java1");
c.add("java1");
c.add("java2");
c.add("java2");
c.add("java3");
System.out.println(c); //打印: [java1, java1, java2, java2, java3]

//2.public int size(): 获取集合的大小
System.out.println(c.size()); //5

//3.public boolean contains(Object obj): 判断集合中是否包含某个元素
System.out.println(c.contains("java1")); //true
System.out.println(c.contains("Java1")); //false

//4.pubilc boolean remove(E e): 删除某个元素,如果有多个重复元素只能删除第一个
System.out.println(c.remove("java1")); //true
System.out.println(c); //打印: [java1,java2, java2, java3]

//5.public void clear(): 清空集合的元素
c.clear(); 
System.out.println(c); //打印:[]

//6.public boolean isEmpty(): 判断集合是否为空 是空返回true 反之返回false
System.out.println(c.isEmpty()); //true

//7.public Object[] toArray(): 把集合转换为数组
Object[] array = c.toArray();
System.out.println(Arrays.toString(array)); //[java1,java2, java2, java3]

//8.如果想把集合转换为指定类型的数组,可以使用下面的代码
String[] array1 = c.toArray(new String[c.size()]);
System.out.println(Arrays.toString(array1)); //[java1,java2, java2, java3]

//9.还可以把一个集合中的元素,添加到另一个集合中
Collection c1 = new ArrayList<>();
c1.add("java1");
c1.add("java2");
Collection c2 = new ArrayList<>();
c2.add("java3");
c2.add("java4");
c1.addAll(c2); //把c2集合中的全部元素,添加到c1集合中去
System.out.println(c1); //[java1, java2, java3, java4]

Collection遍历方式

迭代器遍历集合
Collection c = new ArrayList<>();
c.add("赵敏");
c.add("小昭");
c.add("素素");
c.add("灭绝");
System.out.println(c); //[赵敏, 小昭, 素素, 灭绝]

//第一步:先获取迭代器对象
//解释:Iterator就是迭代器对象,用于遍历集合的工具)
Iterator it = c.iterator();

//第二步:用于判断当前位置是否有元素可以获取
//解释:hasNext()方法返回true,说明有元素可以获取;反之没有
while(it.hasNext()){
    //第三步:next()获取当前位置的元素,然后自动指向下一个元素.
    String e = it.next();
    System.out.println(s);
}

迭代器代码的原理

  • 当调用iterator()方法获取迭代器时,当前指向第一个元素
  • hasNext()方法则判断这个位置是否有元素,如果有则返回true,进入循环
  • 调用next()方法获取元素,并将当月元素指向下一个位置,
  • 等下次循环时,则获取下一个元素,依此类推
增强for遍历集合

增强for不光可以遍历集合,还可以遍历数组。

Collection c = new ArrayList<>();
c.add("赵敏");
c.add("小昭");
c.add("素素");
c.add("灭绝");

//1.使用增强for遍历集合
for(String s: c){
    System.out.println(s); 
}

//2.再尝试使用增强for遍历数组
String[] arr = {"迪丽热巴", "古力娜扎", "稀奇哈哈"};
for(String name: arr){
    System.out.println(name);
}
forEach遍历集合

forEach方法的参数是一个Consumer接口,而Consumer是一个函数式接口,所以可以传递Lambda表达式

Collection c = new ArrayList<>();
c.add("赵敏");
c.add("小昭");
c.add("素素");
c.add("灭绝");

//调用forEach方法
//由于参数是一个Consumer接口,所以可以传递匿名内部类
c.forEach(new Consumer{
    @Override
    public void accept(String s){
        System.out.println(s);
    }
});


//也可以使用lambda表达式对匿名内部类进行简化
c.forEach(s->System.out.println(s)); //[赵敏, 小昭, 素素, 灭绝]
并发修改异常

迭代器遍历机制,规定迭代器遍历集合的同时,不允许集合自己去增删元素,否则就会出现ConcurrentModificationException异常。

要求不使用集合的删除方法,而是使用迭代器的删除方法

public class Text {
    public static void main(String[] args) {
        ArrayList list = new ArrayList<>();
        list.add("王麻子");
        list.add("小李子");
        list.add("李爱花");
        list.add("张全蛋");
        list.add("晓李");
        list.add("李玉刚");
        remove1(list);
        remove2(list);
        remove3(list);
    }
    //普通for循环删除集合中的元素(正序遍历)
    public static void remove1(ArrayList list){
        for (int i = 0; i < list.size(); i++) {
            String s = list.get(i);
            if (s.contains("李")){
                list.remove(i);
                i--;
            }
        }
        System.out.println(list);
    }
    //普通for循环删除集合中的元素(倒序遍历)
    public static void remove2(ArrayList list){
        for (int i = list.size()-1; i >=0 ; i--) {
            String s = list.get(i);
            if (s.contains("李")){
                list.remove(i);
            }
        }
        System.out.println(list);
    }
    //迭代器删除集合中的元素
    public static void remove3(ArrayList list){
        Iterator iterator = list.iterator();
        while (iterator.hasNext()){
            String next = iterator.next();
            if (next.contains("李")){
                iterator.remove();
            }
        }
        System.out.println(list);
    }
}

List系列集合

List集合的常用方法

List集合是有索引的,所以多了一些有索引操作的方法

//1.创建一个ArrayList集合对象(有序、有索引、可以重复)
List list = new ArrayList<>();
list.add("蜘蛛精");
list.add("至尊宝");
list.add("至尊宝");
list.add("牛夫人"); 
System.out.println(list); //[蜘蛛精, 至尊宝, 至尊宝, 牛夫人]

//2.public void add(int index, E element): 在某个索引位置插入元素
list.add(2, "紫霞仙子");
System.out.println(list); //[蜘蛛精, 至尊宝, 紫霞仙子, 至尊宝, 牛夫人]

//3.public E remove(int index): 根据索引删除元素, 返回被删除的元素
System.out.println(list.remove(2)); //紫霞仙子
System.out.println(list);//[蜘蛛精, 至尊宝, 至尊宝, 牛夫人]

//4.public E get(int index): 返回集合中指定位置的元素
System.out.println(list.get(3));

//5.public E set(int index, E e): 修改索引位置处的元素,修改后,会返回原数据
System.out.println(list.set(3,"牛魔王")); //牛夫人
System.out.println(list); //[蜘蛛精, 至尊宝, 至尊宝, 牛魔王]
List集合的遍历方式

List集合相比于前面的Collection多了一种可以通过索引遍历的方式,所以List集合遍历方式一共有四种:

  • 普通for循环(只因为List有索引)
  • 迭代器
  • 增强for
  • Lambda表达式
List list = new ArrayList<>();
list.add("蜘蛛精");
list.add("至尊宝");
list.add("糖宝宝");

//1.普通for循环
for(int i = 0; i< list.size(); i++){
    //i = 0, 1, 2
    String e = list.get(i);
    System.out.println(e);
}

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

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

//4.lambda表达式遍历
list.forEach(s->System.out.println(s));
ArrayList底层原理

ArrayList集合底层是基于数组结构实现的,也就是说当你往集合容器中存储元素时,底层本质上是往数组中存储元素。

特点是

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

数组扩容原理

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

注意

  • 数组扩容,并不是在原数组上扩容(原数组是不可以扩容的),底层是创建一个新数组,然后把原数组中的元素全部复制到新数组中去。
LinkedList
LinkedList底层原理

LinkedList底层是双向链表结构,链表结构是由一个一个的节点组成,一个节点由数据值、下一个元素的地址组成。

特点

  • 查询慢,无论查询哪个数据都要从头开始找
  • 链表增删相对快
  • 链表中的结点是独立的对象,在内存中是不连续的,每个结点包含数据值和下一个结点的地址。

双向链表表结构

  • 对首尾进行增删改查的速度是极快的
LinkedList方法

LinkedList集合是基于双向链表实现了,所以相对于ArrayList新增了一些可以针对头尾进行操作的方法

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

LinkedList集合可以用来设计栈结构、队列结构。

队列结构

队列结构你可以认为是一个上端开口,下端也开口的管子的形状。元素从上端入队列,从下端出队列。

入队列可以调用LinkedList集合的addLast方法,出队列可以调用removeFirst()方法.

//1.创建一个队列:先进先出、后进后出
LinkedList queue = new LinkedList<>();
//入对列
queue.addLast("第1号人");
queue.addLast("第2号人");
queue.addLast("第3号人");
queue.addLast("第4号人");
System.out.println(queue);

//出队列
System.out.println(queue.removeFirst());    //第4号人
System.out.println(queue.removeFirst());    //第3号人
System.out.println(queue.removeFirst());    //第2号人
System.out.println(queue.removeFirst());    //第1号人

栈结构

栈结构可以看做是一个上端开头,下端闭口的水杯的形状。

元素永远是上端进,也从上端出,先进入的元素会压在最底下,所以栈结构的特点是先进后出,后进先出

//1.创建一个栈对象
LinkedList stack = new ArrayList<>();
//压栈(push) 等价于 addFirst()
stack.push("第1颗子弹");
stack.push("第2颗子弹");
stack.push("第3颗子弹");
stack.push("第4颗子弹");
System.out.println(stack); //[第4颗子弹, 第3颗子弹, 第2颗子弹,第1颗子弹]

//弹栈(pop) 等价于 removeFirst()
System.out.println(statck.pop()); //第4颗子弹
System.out.println(statck.pop()); //第3颗子弹
System.out.println(statck.pop()); //第2颗子弹
System.out.println(statck.pop()); //第1颗子弹

//弹栈完了,集合中就没有元素了
System.out.println(list); //[]

Set系列集合

Set集合是属于Collection体系下的另一个分支

Set系列集合特点:无序:添加数据的顺序和获取出的数据顺序不一致; 不重复, 无索引;

  • HashSet:无序、不重复、无索引。
  • LinkedHashSet: 有序、不重复、无索引
  • TreeSet: 排序、不重复、无索引
HashSet
HashSet集合底层原理

HashSet集合底层是基于哈希表实现的,哈希表根据JDK版本的不同,也是有点区别的

  • JDK8以前:哈希表 = 数组+链表
  • JDK8以后:哈希表 = 数组+链表+红黑树

数据存储过程

  • 创建一个默认长度16的数组,默认加载因子为0.75,数组名table
  • 使用元素的哈希值对数组的长度求余计算出应存入的位置
  • 判断当前位置是否为null,如果是null直接存入
  • 如果不为null,表示有元素,则调用equals方法比较相等,则不存;不相等,则存入数组
  • JDK 8之前,新元素存入数组,占老元素位置,老元素挂下面
  • JDK8开始之后,新元素直接挂在老元素下面
HashSet去重原理

往HashSet集合中存储元素时,底层调用了元素的两个方法:一个是hashCode方法获取元素的hashCode值(哈希值);另一个是调用了元素的equals方法,用来比较新添加的元素和集合中已有的元素是否相同。

  • 只有新添加元素的hashCode值和集合中以后元素的hashCode值相同、新添加的元素调用equals方法和集合中已有元素比较结果为true, 才认为元素重复。
  • 如果hashCode值相同,equals比较不同,则以链表的形式连接在数组的同一个索引为位置

要想保证在HashSet集合中没有重复元素,我们需要重写元素类的hashCode和equals方法。

public class Student{
    private String name; //姓名
    private int age; //年龄
    private double height; //身高
 
    //无参数构造方法
    public Student(){}
    //全参数构造方法
    public Student(String name, int age, double height){
        this.name=name;
        this.age=age;
        this.height=height;
    }
    //...get、set、toString()方法自己补上..
    
    //按快捷键生成hashCode和equals方法
    //alt+insert 选择 hashCode and equals
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Student student = (Student) o;

        if (age != student.age) return false;
        if (Double.compare(student.height, height) != 0) return false;
        return name != null ? name.equals(student.name) : student.name == null;
    }

    @Override
    public int hashCode() {
        int result;
        long temp;
        result = name != null ? name.hashCode() : 0;
        result = 31 * result + age;
        temp = Double.doubleToLongBits(height);
        result = 31 * result + (int) (temp ^ (temp >>> 32));
        return result;
    }
}
LinkedHashSet

LinkedHashSet类是HashSet的子类。LinkedHashSet它底层采用的是也是哈希表结构,只不过额外新增了一个双向链表来维护元素的存取顺序。

每次添加元素,就和上一个元素用双向链表连接一下。第一个添加的元素是双向链表的头节点,最后一个添加的元素是双向链表的尾节点。

TreeSet集合

TreeSet集合的特点是可以对元素进行排序,但是必须指定元素的排序规则。

如果往集合中存储String类型的元素,或者Integer类型的元素,它们本身就具备排序规则,所以直接就可以排序。

如果往TreeSet集合中存储自定义类型的元素,比如说Student类型,则需要我们自己指定排序规则,否则会出现异常。

需要告诉TreeSet集合按照指定的规则排序,有两种办法:

  • 让元素的类实现Comparable接口,重写compareTo方法

    原理

    在往TreeSet集合中添加元素时,add方法底层会调用compareTo方法,根据该方法的 结果是正数、负数、还是零,决定元素放在后面、前面还是不存。

  • 在创建TreeSet集合时,通过构造方法传递Compartor比较器对象

    原理

    当调用add方法时,底层会先用比较器,根据Comparator的compare方是正数、负数、还是零,决定谁在后,谁在前,谁不存。

你可能感兴趣的:(java集合)