Java基础(十六):集合

一、集合

(一)集合的框架
在这里插入图片描述
(二)集合

  1. 动态保存多个对象
  2. 提供一系列方便操作对象的方法
  3. 代码简洁

(三)集合分类
单列集合(List、Set)和双列集合(Map)

二、Collection

(一)Collection简介

1)单列集合框架图
在这里插入图片描述

2)Collection接口常用方法
add()
addAll(Collection c)
remove(下标/指定元素)
removeAll(Collection c)
contains(指定元素)
containsAll(Collection c)
size()
isEmpty()
clear()

3)Collection接口的遍历

方法一:使用iterator迭代器
1)Iterator对象称为迭代器,主要用于遍历Collection集合中的元素。
2)所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了lterator接口的对象,即可以返回一个迭代器。

Collection c = new ArrayList();
c.add(1);
c.add(2);
Iterator iterator = c.iterator();
while(iterator.hasNext()){
	Object next = iterator.next();
	System.out.println(next);
}

方法二:for循环增强

Collection c = new ArrayList();
c.add(1);
c.add(2);
for(Object num: c){
	System.out.println(num);
}

(二)List

1. List类的简介

1)List的性质
List集合元素有序且可重复,支持索引,允许添加null

2)List常用方法
增加
add(Object o)
add(int index, Object o)
addAll(int index, Collection c)
查找
get(int index)
indexOf(Object o)
lastIndexOf(Object o)
删除
remove(int index)
修改
set(int index, Object o)
取子串
subList(int index1, int index2)

3)List的遍历方式
方法一:使用iterator迭代器
方法二:for循环增强
方法三:普通for循环

2. ArrayList类

1)ArrayList是由(可变)数组实现数据存储,线程不安全,但是执行效率高,允许插入null
2)ArrayList的扩容机制:
当创建对象时,若使用无参构造,默认容量10,若再次扩容,扩容1.5倍。
若指定容量构造,初始化指定大小,若再次扩容,扩容1.5倍。

3. Vector类

1)Vector是由(可变)数组实现数据存储,线程安全
2)Vector的扩容机制:
当创建对象时,若使用无参构造,默认容量10,若再次扩容,扩容2倍。
若指定容量构造,初始化指定大小,若再次扩容,扩容2倍。

4. LinkedList类

1)LinkedList底层实现了双向链表和双端队列的特点,可添加任意元素,包括null
2)线程不安全,没有实现同步
3)添加和删除效率高

5. ArrayList、Vector、LinkedList类的使用场景

改查操作多,选择ArrayList
增删操作多,选择LinkedList
线程安全,选择Vector

(三)Set

1. Set类的简介

1)Set的性质
Set元素无序且不重复,没有索引,最多包含一个null,添加的顺序与取出的顺序不一致

注意:
set.add(new Dog(“小黑”)); // T
set.add(new Dog(“小黑”)); // T
这其实是不同的对象,不重复,可添加
set.add(new String(“hello”)); // T
set.add(new String(“hello”)); // F
而这种不可

2)Set的常用方法
Set和List一样,是Collection的子接口,因此,常用方法和Collection一样。

3)Set的遍历
方法一:使用iterator迭代器
方法二:for循环增强
(不能用索引遍历)

2. HashSet类

1)HashSet的底层其实是HashMap,HashMap的底层是数组+链表+红黑树

2)HashSet的扩容机制
<1> HashSet底层是HashMap,当添加key-value时,根据key的hash值得到在table的索引,没有元素直接添加,有则替换;容量不够就扩容;
<2> 第一次添加时,table 数组扩容到16,临界值(threshold)是16*加载因子(IoadFactor)是0.75=12;
<3> 如果容量加到了临界值12(注意:无论这个元素在table数组上,还是在链表上,都算作HashSet容量的增加),table数组就会扩容16*2=32,新的临界值就是32*0.75 =24,依次类推;
<4> 在Java8中,如果一条链表的元素个数到达TREEIFY_THRESHOLD(默认是8)。并且table的大小>=MIN TREEIFY CAPACITY(默认64),就会进行树化(红黑树),否则仍然采用数组扩容机制。(比如:如果链表元素达到8,但是table未达到64,会先对table扩容,一直扩容直到table为64)

演示:HashSet添加一个元素的底层实现
<1> 添加一个元素时(可能会对数组进行扩容),先得到Hash值,然后转成索引值;
<2> 看存储表中这个索引位置,若无值,则加入;
<3> 若有值,调用equals方法(比较hashcode),若相等,则放弃添加;
<4> 若不等,添加到最后;
<5> 在Java8中,链表元素到TREEIFY_THRESHOLD(默认为8),且table数组大小超过MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树),否则仍采用对table数组扩容机制。

4)HashSet添加一个元素时,在添加对象所属的类中重写hashCode和equals方法来去重。注意:和HashMap对比理解

/*
定义一个Employee类,该类包含: private成员属性name,age 要求:
1.创建3个Employee 对象放入 HashSet中
2.当 name和age的值相同时,认为是相同员工,不能添加到HashSet集合中
*/
import java.util.HashSet;
import java.util.Objects;
public class Exercise1 {
    public static void main(String[] args){
        HashSet hashSet = new HashSet();
        hashSet.add(new people("li",18));//ok
        hashSet.add(new people("wang",18));//ok
        hashSet.add(new people("li",18));//no
    }
}
class people{
    private String name;
    private  int age;
    public people(String name, int age) {
        this.name = name;
        this.age = age;
    }
    //重写equals方法,当name和age相同时,不插入新值
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        people people = (people) o;
        return age == people.age && Objects.equals(name, people.name);
    }
    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

3. LinkedHashSet类

1)LinkedHashSet 是 HashSet 的子类
2)LinkedHashSet 底层是一个 LinkedHashMap,底层维护了一个 数组+ 双向链表
3)LinkedHashSet 根据元素的 hashCode 值来决定元素的存储位置,同时使用双向链表维护元素的次序,这使得元素看起来是以插入顺序保存的,添加与取出的顺序一致
4)LinkedHashSet 不允许添重复元素

4. TreeSet类

1)TreeSet的底层是TreeMap
2)TreeSet使用无参构造时,无序(不按照输入顺序),可以使用构造器的匿名内部类的比较器制定排序规则,常用构造顺序
3)在添加一个元素时根据TreeSet的Comparator匿名内部类的比较规则来去重。假设compateTo方法的比较规则是长度递增,那么遇到相同长度的元素时,元素无法添加进去 。重要,注意理解!!!

import java.util.Comparator;
import java.util.TreeSet;
public class TreeSetExercise {
    public static void main(String[] args) {
        TreeSet treeSet = new TreeSet(new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                //按照字母顺序递增
                return ((String) o1).compareTo((String) o2);
                //按照长度递增
                //return ((String) o1).length() - ((String) o2).length();
            }
        });
        treeSet.add("lis");
        treeSet.add("wi");
        treeSet.add("ai");
        treeSet.add("diswe");
        System.out.println(treeSet);
    }
}

三、Map

双列集合框架图:
在这里插入图片描述

  1. Map接口实现类的特点
    1)Map与Collection并列存在。用于保存具有映射关系的数据:Key-Value
    2)Map 中的 key 和 value 可以是任何引用类型的数据,会封装到HashMap$Node对象中
    3)Map 中的 key 不允许重复,原因和HashSet 一样,当插入一个新元素,有相同的key,会替换前面的key-value
    4)Map 中的 value 可以重复
    5)Map 的key 可以为 null, value 也可以为null ,注意: key 为null,只能有一个;value 为null ,可以多个
    6)常用String类作为Map的 key
    7)key 和 value 之间存在单向一对一关系,即通过指定的 key 总能找到对应的 value
    8)添加与取出的顺序不一致

  2. Map常用方法
    1)put:添加
    2)remove:根据key删除key-value
    3)get:根据key获取value
    4)size:元素个数
    5)isEmpty:判断元素个数是否为0
    6)clear:清除
    7)contiansKey:查找key知否存在

public class MapExercise {
    public static void main(String[] args){
        Map map = new HashMap();
        map.put("li","12");
        map.put("wang","11");
        map.put("li","13");//替换
        map.put(null,null);
        map.put(null,"12");//替换

        map.remove(null);
        map.remove("li");

        Object val = map.get("wang");
        System.out.println("val = " + val);

        System.out.println("size = " + map.size());
        System.out.println(map.containsKey("li"));
        System.out.println(map.isEmpty());
        map.clear();
        System.out.println("map=" + map);
    }
}
  1. Map的六大遍历方式
import java.sql.SQLOutput;
import java.util.*;
public class MapExercise {
    public static void main(String[] args){
        Map map = new HashMap();
        map.put("wang","11");
        map.put("li","13");
        map.put(null,"12");

        //第一组:先取出所有key,在取出对应value
        Set keyset = map.keySet();
        //(1)增强for
        for(Object key:keyset){
            System.out.println(key + ":" + map.get(key));
        }
        //(2)迭代器
        Iterator iterator1 = keyset.iterator();
        while(iterator1.hasNext()){
            Object key = iterator1.next();
            System.out.println(key + ":" + map.get(key));
        }

        //第二组:把所有value取出
        Collection values = map.values();
        //(1)增强for
        for(Object value:values){
            System.out.println(value);
        }
        //(2)迭代器
        Iterator iterator2 = values.iterator();
        while(iterator2.hasNext()){
            Object value = iterator2.next();
            System.out.println(value);
        }

        //第三组:通过EntrySet取出k-y
        Set entrySet = map.entrySet();
        //(1)增强for
        for(Object entry:entrySet){
            //将entry转成map.entry
            Map.Entry m = (Map.Entry)entry;
            System.out.println(m.getKey() + "=" + m.getValue());
        }
        //(2)迭代器
        Iterator iterator3 = entrySet.iterator();
        while(iterator3.hasNext()){
            Object entry = iterator3.next();
            Map.Entry m = (Map.Entry)entry;
            System.out.println(m.getKey() + "=" + m.getValue());
        }
    }
}

(一)HashMap类

  1. 不保证映射顺序,底层是以hash表实现的,底层:数组+链表+红黑树
  2. HashMap没有实现同步互斥,因此线程不安全

(二)Hashtable类

  1. Hashtable的key和value不能为null,出现的话会抛出异常
  2. Hashtable实现了同步互斥,因此是线程安全
  3. Hashtable的效率较低,HashMap效率较高

(三)Properties类

  1. Properties类继承了Hashtable类,使用方法与Hashtable相似
  2. 通常作为配置文件,可从xxx.properties文件中加载数据到Properties类对象中,进行读取修改

(四)LinkedHashMap类

(五)TreeMap类

TreeMap使用无参构造时,无序(不按照输入顺序),可以使用构造器的匿名内部类的比较器制定排序规则,常用构造顺序。使用方法类似TreeSet

四、Collecions

Collections工具类介绍
1) Collections 是一个操作 SetListMap 等集合的工具类
2) Collections 中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作

public class CollectionsExercise {
    public static void main(String[] args) {
        List arrayList = new ArrayList();
        //1) reverse(List): 反转 List 中元素的顺序
        Collections.reverse(arrayList);
        //2) shuffle(List):对 List 集合元素进行随机排序
        Collections.shuffle(arrayList);
        //3) sort(List): 根据元素的自然顺序对指定 List 集合元素按升序排序
        Collections.sort(arrayList);
        //4) sort(List,Comparator): 根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
        Collections.sort(arrayList, new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                return ((String)o1).length() - ((String)o2).length();
            }
        });
        //5) swap(List,int,int): 将指定 ist 集合中的i 处元素和j处元素进行交换
        Collections.swap(arrayList, 0 ,1);
        //6) Object max(Collection): 根据元素的自然顺序,返回给定集合中的最大元素
        Collections.max(arrayList);
        //7) Object max(Collection,Comparator): 根据 Comparator 指定的顺序返回给定集合中的最大元素
        Collections.max(arrayList, new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                return ((String)o1).length() - ((String)o2).length();
            }
        });
        //8) Object min(Collection)
        Collections.min(arrayList);
        //10) int frequency(Collection,Object): 返回指定集合中指定元素的出现次数
        Collections.frequency(arrayList,"tom");
        //11) void copy(List dest,List src): 将src中的内容复制到dest中,注意:需要给dest赋值使得大小与list一致
        List dest = new ArrayList();
        for (int i = 0; i <arrayList.size(); i++) {
            dest.add("");
        }
        Collections.copy(dest, arrayList);
        //12) boolean replaceAll(List list,Object oldVal,object newVal): 使用新值替换 List 对象的所有旧值
        Collections.replaceAll(arrayList, "tom", "汤姆")
    }
}

五、开发过程如何选择类

1. 先判断存储的类型(一组对象[单列]或一组键值对[双列]
2. 一组对象[单列]: Collection接口
	允许重复: List
		增删多: LinkedList [底层维护了一个双向链表]
		改查多: ArrayList [底层维护 object类型的可变数组]
	不允许重复: Set
		无序: HashSet[底层是HashMap ,维护了一个哈希表 即(数组+链表+红黑树)]
		排序: TreeSet
		插入和取出顺序一致: LinkedHashSet,维护数组+双向链表
3. 一组键值对[双列]: Map
	键无序: HashMap[底层是: 哈希表 jdk7: 数组+链表,jdk8: 数组+链表+红黑树]
	键排序: TreeMap
	键插入和取出顺序一致: LinkedHashMap
	读取文件 Properties

六、习题

例题1:分析如下代码的输出:

public class HashSetExercise {
    public static void main(String[] args){
        HashSet hashSet = new HashSet();
        People p1 = new People("li",18);
        People p2 = new People("wang",18);
        hashSet.add(p1);//ok
        hashSet.add(p2);//ok
        hashSet.add(p1);//no
        p1.name = "la";
        hashSet.remove(p1);//未删除
        hashSet.add(new People("la",18));//加入到新的位置
        hashSet.add(new People("li",18));//挂在p1的table表后面
        System.out.println(hashSet);
        /*
        分析:
        因为重写了hashCode和equals方法,所以对象的位置根据name和age计算
        假设
        原来的p1的hashCode为001
        原来的p2的hashCode为003
        修改p1的name导致,新的p1的hashcode与原来不同,所以无法删除
        新加一个la 18计算的hashCode006,虽然与p1的name和age相同,但是p1用的是原来的hashCode
        由于p1已经被修改,而li 18因为与p1的name和age不同被挂在了001后面的链表中
        所以最后table的变换如下
        001 li 18    la 18      la 18    la 18->li 18
        003 wang 18  wang 18    wang 18  wang 18
        006                     la 18    la 18
        007                             
         */
    }
}

class People{
    public String name;
    public  int age;
    public People(String name, int age) {
        this.name = name;
        this.age = age;
    }
    //重写equals方法,当name和age相同时,不插入新值
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        People people = (People) o;
        return age == people.age &&
                Objects.equals(name, people.name);
    }
    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

特别说明
本文章是个人整理的学习笔记,参考b站韩顺平老师的课程(【零基础 快速学Java】韩顺平 零基础30天学会Java)。老师讲的非常好,有兴趣的可以去看一下。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值