常用集合认识

集合是一种容器,用来装数据的,类似于数组,但集合的大小可变。

集合体系结构

集合分为单列集合(Collection)和双列集合(Map)

Collection代表单列集合,每个元素(数据)只包含一个值

Map代表双列集合,每个元素包含两个值(键值对)

集合与数组的区别

数组集合
长度区别长度固定长度可变
内容区别基本类型、引用类型只能是引用类型
元素区别只能存储同一种类型

可以存储不同类型

一般存储的也是同一种类型


Collection集合体系

Collection集合特点

Collection是一个接口类,分为以下两个系列

List系列集合:

添加的元素是有序、可重复、有索引

Set系列集合:

添加的元素是无序、不重复、无索引

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 Object[] toArray()

把集合中的元素,存储到数组中

Collection的遍历方式

1、迭代器

迭代器是用来遍历集合的专用方式(数组没有迭代器),在Java中迭代器的代表是Iterator

Collection集合获取迭代器的方法

方法名称

说明

Iterator<E> iterator()

返回集合中的迭代器对象,该迭代器对象默认指向当前集合的第一个元素

Iterator迭代器中的常用方法

方法名称

说明

boolean hasNext()

询问当前位置是否有元素存在,存在返回true ,不存在返回false

E next()

获取当前位置的元素,并同时将迭代器对象指向下一个元素处。

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

注意:

集合的并发修改异常

使用迭代器遍历集合时,又同时在删除集合中的数据,程序就会出现并发修改异常的错误。

由于增强for循环遍历集合就是迭代器遍历集合的简化写法,因此,使用增强for循环遍历集合,又在同时删除集合中的数据时,程序也会出现并发修改异常的错误

怎么保证遍历集合同时删除数据时不出bug

使用迭代器遍历集合,但用迭代器自己的删除方法删除数据即可。

如果能用for循环遍历时:可以倒着遍历并删除或者从前往后遍历,但删除元素后做i --操作

2、增强for循环

  • 增强for可以用来遍历集合或者数组。
  • 增强for遍历集合,本质就是迭代器遍历集合的简化写法。

注意:修改增强for循环中的变量值不会影响到集合中的元素

格式:

for (元素的数据类型 变量名 : 数组或者集合) {

        

}

Collection<String> c = new ArrayList<>();

...
for(String s : c) {
   
System.out.println(s);
}

3、Lambda表达式

方法名称

                                    说明

default void forEach(Consumer<? super T> action)

 结合lambda遍历集合

Collection<String> lists = new ArrayList<>();
...

lists.forEach(new Consumer<String>() {
   
@Override
   
public void accept(String s) {
       
System.out.println(s);
    }
});

//Lambda写法

 lists.forEach(s -> {
           
System.out.println(s);
    });
//  lists.forEach(s -> System.out.println(s));

Collection补充

可变参数

就是一种特殊参数格式,格式是:数据类型... 参数名称

可变参数的特点和好处

特点:可以传数据给它;可以传一个或者同时传多个数据给它;也可以传一个数组给它。

好处:常常用来灵活的接收数据

可变参数注意事项

可变参数在方法内部就是一个数组。

一个形参列表中可变参数只能有一个

可变参数必须放在形参列表的最后面

Collections工具类

Collections 类在java.util包中,是一个操作集合的工具类。
Collections 类提供了许多操作集合的静态方法,可以实现集合元素的排序、批量添加,替换等操作。

常用方法

addAll(Collection<T> c, T... elements) 将所有指定的元素添加到指定的集合c中。
shuffle(List<?> list) 随机打乱list集合中元素的顺序。
sort(List<T> list) 根据自然顺序对list集合的元素进行升序排序。
sort(List<T> list, Comparator<T> c) 根据指定的比较器,对list集合元素进行自定义排序。

public class Test1 {
    public static void main(String[] args) {
        //addAll(Collection<T> c, T... elements)  将所有指定的元素添加到指定的集合c中。
        ArrayList<Integer> list = new ArrayList<>();
        //批量添加
        Collections.addAll(list,11,22,33,44);
        System.out.println(list);

        //shuffle(List<?> list)  随机打乱list集合中元素的顺序。
        Collections.shuffle(list);
        System.out.println(list);

        //sort(List<T> list) 根据自然顺序对list集合的元素进行升序排序(从小到大)。
        Collections.sort(list);
        System.out.println(list);
    }
}
 

List集合

List是一个接口类

List系列集合特点:有序、可重复、有索引

List<String> list = new ArrayList<>();

当里面没有定义泛型时<String>,会默认使用Object父类,当使用时,必须强转才能调用该类的特有方法。

List接口特有方法

方法名称

说明

void add(int index,E element)

在此集合中的指定位置插入指定的元素

E remove(int index)

删除指定索引处的元素,返回被删除的元素

E set(int index,E element)

修改指定索引处的元素,返回被修改的元素

E get(int index)

返回指定索引处的元素

addAll(另一个集合)集合的拷贝
...其他方法Collection接口方法

List使用remove方法问题

因为集合的大小可变,使用remove方法移除一个元素时,索引和长度发生变化,

——会出现删除不干净的问题

public class Demo1 {
    public static void main(String[] args) {

        ArrayList<String> list = new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add("b");
        list.add("c");
        list.add("d");
        list.add("b");

        //删除集合的所有b元素
        //有问题的代码:删除时索引会改变
        /*for (int i = 0; i < list.size(); i++) {
            String s = list.get(i);
            if(s.equals("b")){
                list.remove(i);
            }
        }*/

        //正确的方式1:删除成功后,索引减1
        /*for (int i = 0; i < list.size(); i++) {
            String s = list.get(i);
            if(s.equals("b")){
                list.remove(i);
                i--;
            }
        }*/

        //正确方式2:倒序遍历(推荐)
        for(int i=list.size()-1; i>=0; i--){
            String s = list.get(i);
            if(s.equals("b")){
                list.remove(i);
            }
        }

        //一直删除,直到返回false,说明已经删完了
        /*while (true){
            boolean b = list.remove("b");
            if(b==false){
                break;
            }
        }*/

        //简化写法
        /*while ( list.remove("b") ){

        }*/

        System.out.println(list);
    }
}
 

List集合遍历方式

1、for循环

List系列集合——ArrayList

 面试热点问题:

    1.ArrayList底层是什么数据结构?
         Object类型的数组
         Object[] elementData;

    2.底层的数组何时创建,初始化长度是多少?
        使用无参构造方法创建集合,默认把elementData数组初始化为{}数组(长度为0)
            private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
            public ArrayList() {
                this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
            }
       使用带参构造方法创建集合,数组的长度就是参数的值。

    3.首次调用add方法添加元素的时候,数组的长度变为多少?
        首次添加的时候,长度会由0进行扩容。首次扩容后,长度变成10
            if (s == elementData.length){
                elementData = grow();
            }

    4.如果添加的元素超过数组长度,怎么办?
        如果元素超过长度,会再次扩容,扩容后长度是多少?
        长度会在原来基础上增加一半  :新的长度 = 旧的长度 + 旧的长度/2;   10 —> 15 ->22
        int newCapacity = oldCapacity + (oldCapacity >> 1); 相当于 int newCapacity = oldCapacity + (oldCapacity /2);

       

       >> 右移符号(2进制的计算)

       5. 如果一次添加多个元素,1.5倍还放不下,则新创建数组的长度以实际为准

ArrayList特点:

ArrayList:底层数据结构是数组,查询快,增删慢,线程不安全,效率高,可以存储重复元素

查询速度快注意:是根据索引查询数据快):查询数据通过地址值和索引定位,查询任意数据耗时相同

删除效率低:可能需要把后面很多的数据进行前移。

添加效率极低:可能需要把后面很多的数据后移,再添加元素;或者也可能需要进行数组的扩容。

应用场景:

 

List系列集合——LinkedList

LinkedList 底层数据结构是链表,查询慢,增删快,线程不安全,效率高,可以存储重复元素

LinkedList的底层是一个双向链表,增删快,查询慢。

 双向链表可以快速对首尾元素进行操作,所以LinkedList中有很多进行首尾操作的API。

链表中的结点是独立的对象,在内存中是不连续的,每个结点包含数据值和下一个结点的地址。

链表的特点:

1、查询慢,无论查询哪个数据都要从头开始找。

2、链表增删相对快

Snipaste_2022-05-05_18-25-18.png

LinkedList常用方法

方法名称

说明

public void addFirst​(E e)

在该列表开头插入指定的元素

public void addLast​(E e)

将指定的元素追加到此列表的末尾

public E getFirst​()

返回此列表中的第一个元素

public E getLast​()

返回此列表中的最后一个元素

public E removeFirst​()

从此列表中删除并返回第一个元素

public E removeLast​()

从此列表中删除并返回最后一个元素

...其他方法

Collection中方法

Set集合

Set集合特点:

无序:添加数据的顺序和获取出的数据顺序不一致;  不重复无索引;【统称】

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

LinkedHashSet:有序、不重复、无索引。

TreeSet:排序、不重复、无索引。

常用方法

Snipaste_2022-05-06_18-10-15.png

注意:

Set要用到的常用方法,基本上就是Collection提供的!!

自己几乎没有额外新增一些常用功能!

Set遍历方式

Collection的遍历方式

使用Set集合时的情况

存储自定义类型的数据如对象等时,即使对象内的属性值相等,也是会存储到集合中,因为集合存储的是对象的地址值。如果想要去重,要重写hashCode()和equals()方法。

哈希值

就是一个int类型的数值,Java中每个对象都有一个哈希值。

Java中的所有对象,都可以调用Obejct类提供的hashCode方法,返回该对象自己的哈希值。

public int hashCode();  返回对象自己的哈希值

对象哈希值的特点:

同一个对象多次调用hashCode()方法返回的哈希值是相同的。

不同的对象,它们的哈希值一般不相同,但也有可能会相同(哈希碰撞)

Set系列集合——HashSet

HashSet底层数据结构采用哈希表实现,元素无序且唯一,线程不安全,效率高,可以存储null元素,元素的唯一性是靠所存储元素类型是否重写hashCode()和equals()方法来保证的,如果没有重写这两个方法,则无法保证元素的唯一性

特点:

存储的元素是无序不重复无索引的。
底层数据结构是哈希表,具有良好的存储和查找性能。
HashSet集合没有索引,只能通过迭代器或增强for循环或者forEach遍历集合。

常用方法:

boolean add(E e)

将指定的元素添加到此集合(如果尚未存在)。

boolean remove(Object o)

如果存在,则从该集合中删除指定的元素。

int size()

返回此集合中的元素数。

boolean contains(Object o)

如果此集合包含指定的元素,则返回 true 。

Iterator<E> iterator()

返回此集合中元素的迭代器

HashSet底层结构——哈希表

哈希表:JDK8之前,哈希表 = 数组 + 链表

JAK8开始, 哈希表 = 数组 + 链表 + 红黑树

哈希表存储元素的过程:

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

根据元素的哈希值跟数组的长度计算出应存入的位置 (存入位置= 哈希值 % 数组的长度)

判断当前位置是否为null,如果是null直接存入,如果位置不为null,表示有元素,  则调用equals方法比较属性值,如果一样,则不存,如果不一样,则存入数组。

当数组存满到16*0.75=12时,就自动扩容,每次扩容原先的两倍

JDK 8之前,新元素存入数组,占老元素位置,老元素挂下面
JDK 8开始之后,新元素直接挂在老元素下面

JDK8开始当链表长度超过8,且数组长度>=64自动将链表转成红黑树

Set系列集合——LinkedHashSet

LinkedHashSet底层数据结构采用链表和哈希表共同实现,链表保证了元素的顺序与存储顺序一致,哈希表保证了元素的唯一性。线程不安全,效率高

特点:有序(根据链表记录存入的顺序)、不重复、无索引

底层结构——哈希表 + 双链表

Linkedlist里面是一个双向链表,每个空间存有了前和后对象的地址。使得,每次调用数据时,可以有顺序存入!遍历时只找头节点即可按顺序进行。

⚫ LinkedHashSet继承了HashSet,底层在哈希表的基础上,又维护了一个双向链表。
⚫ LinkedHashSet通过双向链表实现了元素的存取有序性。

Set系列集合——TreeSet

特点:不重复、无索引、可排序(默认升序排序 ,按照元素的大小,由小到大排序

底层结构——红黑树

注意:

对于数值类型:Integer , Double,默认按照数值本身的大小进行升序排序。

对于字符串类型:默认按照首字符的编号升序排序。

对于自定义类型如Student对象,TreeSet默认是无法直接排序的,需要指定排序规则。

TreeSet自然排序

TreeSet集合可以对实现Comparable接口的元素自动排序。
步骤:
1. 元素类需要实现Comparable接口,重写 compareTo() 方法。
2. 使用无参构造方法创建TreeSet集合,集合会对添加元素的实现排序。

3.注意当比较元素不int,返回不是int时。 可以通过判断,直接return 1 、0、-1。

CompareTo 只看的是,大于0、小于0 或者是等于0 不看具体值。

//让学生类实现Comparable接口,具备排序能力
public class Student implements Comparable<Student>{
    private String name;
    private int age;

    /*
        this :准备存入集合的元素
        Student s  :已经存在集合中的元素
        返回值大于0,认为要存入的元素比较大,存到右边
        返回值小于0,认为要存入的元素比较小,存到左边
        返回值等于0,认为要存入的元素已存在,不会存入
     */
    @Override
    public int compareTo(Student s) {
        //return this.age - s.age; //根据年龄升序排序
        return s.age - this.age; //根据年龄降序排序

        //如果年龄一样,比较姓名,如果姓名也一样不存入
        /*int result = this.age - s.age;
        if(result==0){
            if(this.name.equals(s.name)){
                return 0;
            }else {
                return 1; //返回不是0的数字就可以
            }
        }else {
            return result;
        }*/
    }
//Set ,get 方法

compareTo方法排序原理
⚫ 如果返回值为负数,认为当前存入的元素是较小值,存左边。
⚫ 如果返回值为正数,认为当前存入的元素是较大值,存右边。
⚫ 如果返回值为0,认为两个元素一样,不存入集合。

TreeSet比较器排序

没有实现Comparable接口的元素,无法实现自动排序。
此时可以在TreeSet的构造方法中传入Comparator比较器,实现排序。

当既有Comparable接口接口排序和Comaparator比较器时,以Comparator比较器为主。

Comparator比较器排序:

⚫ 使用TreeSet的带参构造方法创建集合对象。
⚫ TreeSet的构造方法必须接收Comparator的实现类对象,并重写compare(T o1, T o2)方法。
⚫ compare方法的参数o1表示要添加的元素, o2表示集合中的元素,返回值规则如下。

速记: 1到2 升序。
o1 – o2 升序排序
o2 – o1 降序排序

如果返回值为负数,认为当前存入的元素是较小值,存左边。
如果返回值为正数,认为当前存入的元素是较大值,存右边。
如果返回值为0,认为两个元素一样,不存入集合。

实现方式:匿名内部类:直接在方法内new Comparator

/*
    使用TreeSet,要求元素是具备排序的能力的,即要求元素实现Comparable接口

    或者要求TreeSet的构造方法接收一个比较器对象,即Comparator接口的实现类对象
 */
public class TreeSetDemo2 {
    public static void main(String[] args) {
        Student s1 = new Student("张三",18);
        Student s2 = new Student("李四",15);
        Student s3 = new Student("王五",21);
        Student s4 = new Student("赵六",21);
        Student s5 = new Student("赵六",21);

        //比较器排序
        //如果元素实现Comparable接口,而同时又有比较器Comparator,此时比较器优先
        TreeSet<Student> ts = new TreeSet<>(new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                return o1.getAge() - o2.getAge(); //年龄升序
                //return o2.getAge() - o1.getAge(); //年龄降序
            }
        });

        ts.add(s1);
        ts.add(s2);
        ts.add(s3);
        ts.add(s4);
        ts.add(s5);

        for(Student s : ts){
            System.out.println(s);
        }
    }
}
 

两种比较方式小结


⚫ 自然排序:自定义类实现Comparable接口,重写compareTo方法。
⚫ 比较器排序: TreeSet带参构造方法传入Comparator的实现类对象,重写compare方法。
⚫ 当两种排序规则同时出现时, TreeSet集合会使用比较器进行排序。

Map集合

Map集合称为双列集合,格式:{key1=value1 , key2=value2 , key3=value3 , ...} 一次需要存一对数据做为一个元素.

Map集合的每个元素“key=value”称为一个键值对/键值对对象/一个Entry对象,Map集合也被叫做“键值对集合

Map集合的所有键是不允许重复的,但值可以重复,键和值是一一对应的,每一个键只能找到自己对应的值

Map集合体系的特点

HashMap(由键决定特点): 无序、不重复、无索引;  (用的最多)

LinkedHashMap (由键决定特点):由键决定的特点:有序、不重复、无索引。

TreeMap (由键决定特点):按照大小默认升序排序不重复、无索引。

使用Map集合时的情况

存储自定义类型的数据如对象等时,即使对象内的属性值相等,也是会存储到集合中,因为集合存储的是对象的地址值。如果想要去重,要重写hashCode()和equals()方法。

Map集合常用方法

方法名称

说明

public V put(K key,V value)

添加元素

public int size()

获取集合的大小

public void clear()

清空集合

public boolean isEmpty()

判断集合是否为空,为空返回true , 反之

public V get(Object key)

根据键获取对应值

public V remove(Object key)

根据键删除整个元素

public  boolean containsKey(Object key)

判断是否包含某个键

public boolean containsValue(Object value)

判断是否包含某个值

public Set<K> keySet()

获取全部键的集合

public Collection<V> values()

获取Map集合的全部值


留意一个方法:
put():当key值相同时候,value值会替换,新的值会放存在,Map集合里面,但返回的时旧值:


 

 遍历方式

1、键找值

先获取Map集合全部的键,再通过遍历键找值

方法名称

说明

public Set<K> keySet()

获取所有键的集合

public V get(Object key)

根据键获取其对应的值

1.获取Map中所有的键,方法提示: keySet()
2. 遍历键的Set集合,得到每一个键
3. 根据键,获取键所对应的值。方法提示: get(K key)

Set<String> keySet = map.keySet();
for (String key : keySet) {
    System.out.println(key + "===>" + map.get(key));
}

2、键值对

把“键值对“看成一个整体进行遍历(难度较大

Map提供的方法

说明

Set<Map.Entry<K, V>> entrySet()

获取所有“键值对”的集合

Map.Entry提供的方法

说明

getKey()

获取键

getValue()

获取值

把每一个key和value值封装成为一个对象。

Map.Entry<K,V>接口

Map中将每个键和值封装成一个个的Entry<K, V>对象,
并提供 getKey() 和 getValue() 方法,用于获取Entry中封装的键和值。

Set<Map.Entry<String, Double>> entries = map.entrySet();
for (Map.Entry<String, Double> entry : entries) {
    System.out.println(entry.getKey() + "===>" + entry.getValue());
}

3、Lambda

//forEach方法
map.forEach(new BiConsumer<String, Double>() {
    @Override
    public void accept(String key, Double value) {
        System.out.println(key + "===>" + value);
    }
});

System.out.println("-----------------------------------------");
map.forEach((key,value) -> {
    System.out.println(key + "===>" + value);
});

Map系列集合——HashMap

HashMap特点

无序、不重复、无索引

HashMap是非线程安全的

实际上:原来学的Set系列集合的底层就是基于Map实现的,只是Set集合中的元素只要键数据,不要值数据而已。

⚫ HashMap底层是哈希表结构+链表/红黑树。
⚫ 依赖hashCode方法和equals方法保证的唯一。
⚫ 如果要存储的是自定义对象,需要重写hashCode和equals方法。 在key的类里面重写了HashCode和equals,自己定义的书籍类型才能保证基本数据类的唯一性。

map.put(s1, "广州");

map.put(s2, "深圳");

map.put(s3, "杭州");

//s4和s3两个键一样,所以s4没有存入,但是覆盖了s3的值

map.put(s4, "北京");

即通过重写了hashCode和equals方法(依据时他们的年龄和名字):判断了S3 和 S4是否一样。

Integer、String内部已经实现了,出现相同的key值时,会直接覆盖掉原来的数值。(Java内部类已经实现重写)。

Map系列集合——LinkedHashMap

特点:

(由键决定)有序、不重复、无索引

底层数据结构依然是基于哈希表实现的,只是每个键值对元素又额外的多了一个双链表的机制记录元素顺序(保证有序)

实际上:原来学习的LinkedHashSet集合的底层原理就是LinkedHashMap

Map系列集合——TreeMap

TreeMap的特点

(由键决定特点)按照键的大小默认升序排序不重复、无索引  

自动去重

◼ TreeMap是Map里面的一个实现类。
◼ TreeMap底层是红黑树结构;可以对元素的进行排序。(TreeSet底层就是使用TreeMap实现存储的)
◼ 排序方式有两种:自然排序,比较器排序。

TreeMap的排序

自然排序: 使用无参构造器创建TreeMap集合,存入集合的键需要实现Comparable<T>接口。
比较器排序: 使用带参构造器创建TreeMap集合,传入Comparator<T>接口的实现类。

 TreeMap<Integer, String> tm = new TreeMap<>(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2 - o1;
            }
        });

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值