Collection集合与Map集合的知识点梳理

    在Java中集合分为单列集合(Collection)与双列集合(Map),本文主要对两种集合的常用实现类作一介绍,如果有不足,恳请指教更正。

1.前言

说到集合肯定要介绍下集合的概念与特点:集合是一个容器,能够用来存储引用数据类型,长度是可变的。说到存储数据的容器大家脑海里想到的肯定还有数组,数组也是一个容器,可以用来存储任意类型的数据,但是长度是不可变的。因此数组有很大的局限性,在开发中存储数据我们一般都使用集合。

 

                                (图是拷贝的)

2.Collection单列集合

Collection是单列集合的顶层父接口,其下分为List集合与Set集合两大类。List集合与Set集合各有其特点:

List集合:1.元素存取有序   2.有索引  3.元素可以重复

Set集合:1.元素存取无序   2.无索引  3.元素不能重复

(元素存取有序是指按什么顺序存的就按什么顺序取出)

2.1List集合

List集合下常用的实现类有ArrayList、LinkedList、Vector。

2.1.1ArrayList

ArrayList集合在我们平时开发中使用的频率最高,其底层其实是Object数组,所以查询快,增删慢。为什么这么说呢?因为在数组结构中数组在内存中是连续的,数组的地址值其实是连续空间的第一个空间的地址,由于连续性,数组就可以通过直接计算出对应空间的地址值从而实现快速查询,但是数组长度不可变,如果我们要在数组中进行增删操作,需要创建一个新的数组,还要把原数组中的元素复制过去,这就导致了增删慢。

2.1.2LinkedList

LinkedList集合的底层是一个双向链表,所以查询慢,增删快。在这里要说明一下链表结构,链表结构其实是有多个节点连接起来,节点最少由数据部分、地址部分2部分组成。数据部分:存数据。地址部分:存下一个节点的地址。多个节点之间通过地址进行连接。例如:多个人手拉手,每个人使用自己的右手拉住下一个人的左手,一次类推,这样多个人就在一起了。这种数据接口查找慢的原因是:链表中的数据是离散的,没有任何规律,必须从第一个节点开始一个一个向后查询,而增删快的原因是:节点它可以记录下一个节点的地址值,链表中增删数据时,不用修改原本数据的内存地址值,增加元素只需要修改连接下个元素的地址即可,同理,删除元素也只需要修改下个元素的地址即可。

2.1.3Vector

Vector集合底层是扩展的对象数组,功能与ArrayList集合相似,但是Vector是线程安全的,而ArrayList是线程不安全的,线程安全带来的弊端就是效率比较低,所以Vector集合一般很少使用。

2.2单列集合的遍历

集合数据的添加可以通过集合中的add方法实现,那么要怎么获取集合中的元素呢?

集合中元素的获取可以通过一般for循环、增强(高级)for循环、迭代器实现,这里主要说一下高级for循环和迭代器。

高级for循环也叫foreach循环,是从JDK1.5之后出现的,专门用来遍历数组和集合。其使用格式为:for(数据类型 变量名 :容器){ 循环体  }。注意:数组的增强for循环底层是普通for循环;集合的增强for循环底层原理是迭代器。

 ArrayList<String> list = new ArrayList<>();
        list.add("小明");
        list.add("小红");
        list.add("小黑");
        list.add("小白");
        for ( String s : list ) {
            System.out.println(s);
        }
控制台打印:

小明
小红
小黑
小白

 

迭代器(Iterator)可以简单的看作是一个具有光标(指针)的遍历集合的工具。是Collection集合元素的通用获取方式。一开始指向集合元素的第一个位置的值,在取元素之前先要判断集合中有没有元素,如果有,就把这个元素取出来,继续在判断,如果还有就再取出出来,一直把集合中的所有元素全部取出,这种取出方式专业术语称为迭代。有人可能会问有了这么简单的foreach循环为什么还要使用迭代器?

foreach循环遍历与迭代器遍历比较:

如果对集合中的元素随机访问,foreach循环中的get()方法采用的就是随机访问的形式,因此在ArrayList集合中,foreach遍历较快。

如果对集合中的元素顺序访问,迭代器中的next()方法,采用的就是顺序访问的形式,因此在LinkedList集合中,使用迭代器较快。

从数据结构角度分析,for循环适合访问顺序结构,可以根据下标快速获取指定元素.而Iterator 适合访问链式结构,因为迭代器是通过next()和Pre()来定位的.可以访问没有顺序的集合.

 foreach只是让代码更加简洁了,但是他有一些缺点,就是遍历过程中不能操作数据集合(删除等),所以有些场合不使用,而迭代器可以在遍历过程中操作集合,但值得注意的是必须用迭代器对象操作集合,而不是集合对象操作集合!比如你要遍历一个集合,当满足某条件时候,删除一个元素,  如果用的是foreach循环,就用集合自带的remove(),而这样就改变了集合的Size()循环的时候会出错但如果把集合放入迭代器,既iterator迭代可以遍历并选择集合中的每个对象而不改变集合的结构,而把集合放入迭代器,用迭代器的remove()就不会出现问题。

迭代器遍历过程:

1.有一个单列集合对象

2.调用集合对象的iterator获取迭代器

3.调用迭代器的hasNext方法判断是否还有数据

4.如果有调用迭代器的next方法来获取

ArrayList<String> list = new ArrayList<>();
list.add("小明");
list.add("小红");
list.add("小黑");
list.add("小白");
Iterator<String> it = list.iterator();//获取迭代器对象
while (it.hasNext()) {
String s = it.next();
System.out.println(s);
}

LinkedList<Integer> linkedList = new LinkedList<>();
linkedList.add(1);
linkedList.add(2);
linkedList.add(3);
linkedList.add(4);
Iterator<Integer> iterator = linkedList.iterator();
while (iterator.hasNext()) {
Integer next = iterator.next();
System.out.println(next);
}
控制台输出:

小明
小红
小黑
小白
1
2
3
4

2.2 Set集合

Set集合下常用的实现类有HashSet、LinkedHashSet、TreeSet。

2.2.1 HashSet

HashSet集合的底层是哈希表,哈希表在JDK1.8之后新增了红黑树,即哈希表=数组+链表+红黑树。当向哈希表中存储数据时,先判断哈希值(hashcode),再判断内容(equals),这也是哈希表的去重方式。哈希表的存储方式是数组存放的是哈希值不同的元素,链表是用来存放哈希值相同但内容不同的元素,当链表的长度超过8时会自动变成红黑树,提高效率。  在向HashSet集合中存储自定义对象时,一定要重写hashcode()和equals()方法,以实现对自定义对象的去重。

(哈希值:是一个十进制的int值,它表示一个对象的特征码(可能相同),重写后是以对象的属性值按照一定的计算方式获取的,所以如果属性内容相同,2个不同地址值的对象的哈希值有可能一样。)

扩展:在哈希表中地址不同的2个对象,内容相同,哈希值是可能一样的;地址不同,内容也不同,哈希值也有可能一样。

public class Person {
    private String name;
    private int age;

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
  
  //重写equals方法:对对象的内容进行比较判断 @Override
public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Person person = (Person) o; return age == person.age && Objects.equals(name, person.name); }
  //重写hashCode方法:对对象的属性值进行运算,
//底层算法:
result = 31 * result + (element == null ? 0 : element.hashCode());

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

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; }

public Person(String name, int age) { this.name = name; this.age = age; }

public Person() { } }

2.2.2 LinkedHashSet

LinkedHashSet集合的底层是双向链表+哈希表,而链表是用来保存顺序的,所以LinkedHashSet就实现了存取有序了,是Set集合中唯一一个能保证怎么存怎么取的集合。LinkedHashSet集合同样有去重的作用,不能存储重复数据,其功能除了有序外,与HashSet集合差不多。

2.2.3 TreeSet

TreeSet底层是二叉树结构, 实现了SortedSet接口 ,能够对集合中对象进行排序(升序),同样具有去重,不重复的特点。

针对自定义对象的排序可以分为两种方式:

方式1:实现Comparable接口,实现compareTo方法,这种方式也称为元素的自然排序。口诀:我减它>0是升序

public class Student implements Comparable<Student> {
    private String name;
    private int age;
    private int score;

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", 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;
    }

    public int getScore() {
        return score;
    }

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

    public Student() {

    }

    public Student(String name, int age, int score) {

        this.name = name;
        this.age = age;
        this.score = score;
    }

    @Override
    public int compareTo(Student o) {
        //分数做降序
        if (this.score > o.score) {
            return -1;
        } else if (this.score < o.score) {
            return 1;
        } else {
            //年龄升序
            if (this.age > o.age) {
                return 1;
            } else if (this.age < o.age) {
                return -1;
            } else {
                return 0;
            }
        }

    }
}

方式2:Comparator比较器,这种方式常用于匿名内部类,不用修改源码,所以比较常用。口诀:我减它>0是升序

(使用比较器就不用实现Comparable接口了)

//分别用Comparable和Comparator两个接口对下列四位同学的成绩做降序排序,
// 如果成绩一样,那在成绩排序的基础上按照年龄由小到大排序
public class Test13 {
    public static void main(String[] args) {
        Student liusan = new Student("liusan", 20, 90);
        Student lisi = new Student("lisi", 22, 90);
        Student wangwu = new Student("wangwu", 20, 99);
        Student sunliu = new Student("sunliu", 22, 100);

        comparable(liusan, lisi, wangwu, sunliu);
        comparator(liusan, lisi, wangwu, sunliu);
    }

    public static void comparator(Student liusan, Student lisi, Student wangwu, Student sunliu) {
        ArrayList<Student> list = new ArrayList<>();
        list.add(liusan);
        list.add(lisi);
        list.add(wangwu);
        list.add(sunliu);
        //比较器
        Collections.sort(list, new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                if (o1.getScore() > o2.getScore()) {
                    return -1;
                } else if (o1.getScore() < o2.getScore()) {
                    return 1;
                } else {
                    if (o1.getAge() > o2.getAge()) {
                        return 1;
                    } else if (o1.getAge() < o2.getAge()) {
                        return -1;
                    } else {
                        return 0;
                    }
                }
            }
        });
        for ( Student student : list ) {
            System.out.println("student = " + student);
        }
    }

    public static void comparable(Student liusan, Student lisi, Student wangwu, Student sunliu) {
        ArrayList<Student> list = new ArrayList<>();
        list.add(liusan);
        list.add(lisi);
        list.add(wangwu);
        list.add(sunliu);

        //Student类中实现了comparable接口并且重写了compareTo方法,Collections.sort()方法会调用重写的compareTo方法
        Collections.sort(list);
        for ( Student student : list ) {
            System.out.println("student = " + student);
        }
    }
}

3 Map集合

Map 集合是双列集合的顶层父接口,该集合中每个元素都是键值对<key,value>,成对出现。双列集合中,一个键一定只找到对应的一个值,键不能重复,但是值可以重复。

Map集合常用的实现类有HashMap、LinkedHashMap、HashTable,TreeMap。

3.1 HashMap

HashMap的底层是哈希表,与HashSet相似,只是数据的存储形式不同,HashMap可以使用null作为key或value,是线程不安全的,但是效率相对较高。当给HashMap中存放自定义对象时,如果自定义对象作为key存在,这时要保证对象唯一,必须复写对象的hashCode和equals方法。

3.2 HashTable

HashTable与HashMap之间的关系完全类似于Vector和Arraylist的关系。HashTable是线程安全的,但是效率相对较低,Hashtable不允许使用null作为key和value。

3.3 LinkedHashMap

LinkedHashMap是HashMap的子类,其底层是链表+哈希表结构,其关系与HashSet和LinkedHashSet类似,使用链表来维护key-value的次序,可以记住键值对的插入顺序。

3.4 TreeMap

TreeMap存储key-value键值对时,需要根据key对节点进行排序。TreeMap可以保证所有的key-value对处于有序状态。也有两种排序方式:

  1)  自然排序:TreeMap的所有key必须实现Comparable接口,而且所有的key应该是同一个类的对象,否则抛出ClassCastException异常。

  2)  定制排序:创建TreeMap时,传入一个Comparator对象,该对象负责对TreeMap中的所有key进行排序。不需要Map的key实现Comparable接口。

Map集合的遍历:

map集合采用put(key,value)的形式保存键值对数据,对于双列集合Map中键值对形式的数据如何遍历呢?

Map接口是没有Iterator方法,所以不能直接获取迭代器进行遍历;双列集合没有实现Iterable接口,因此也不能直接通过增强for(foreach)循环遍历。

因此我们只能间接的对Map集合进行遍历,方式有2种。

第一种:键找值方式

     1.调用keySet()方法获取到双列集合的所有key,存放到set单列集合中

     2.迭代器遍历或者增强for循环遍历单列set集合中的key,通过双列集合的get(key)方法获取value

/*
 * 有2个数组,第一个数组内容为:[黑龙江省,浙江省,江西省,广东省,福建省],第二个数组为:[哈尔滨,杭州,南昌,广州,福州],
 * 将第一个数组元素作为key,第二个数组元素作为value存储到Map集合中。如{黑龙江省=哈尔滨, 浙江省=杭州, …}
 * */
public class Test07 {
    public static void main(String[] args) {
        String[] str1 = {"黑龙江省", "浙江省", "江西省", "广东省", "福建省"};
        String[] str2 = {"哈尔滨", "杭州", "南昌", "广州", "福州"};
        Map<String, String> map = new HashMap<>();
        for ( int i = 0; i < str1.length; i++ ) {
            map.put(str1[i], str2[i]);
        }

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

第二种:键值对方式

  1.调用entrySet()方法获取到双列集合的所有键值对对象,存放到set集合中
  2.迭代器遍历或者增强for循环遍历单列set集合获取到键值对对象,通过getKey()和getValue()方法获得键和值。

/*
 * 有2个数组,第一个数组内容为:[黑龙江省,浙江省,江西省,广东省,福建省],第二个数组为:[哈尔滨,杭州,南昌,广州,福州],
 * 将第一个数组元素作为key,第二个数组元素作为value存储到Map集合中。如{黑龙江省=哈尔滨, 浙江省=杭州, …}
 * */
public class Test07 {
    public static void main(String[] args) {
        String[] str1 = {"黑龙江省", "浙江省", "江西省", "广东省", "福建省"};
        String[] str2 = {"哈尔滨", "杭州", "南昌", "广州", "福州"};
        Map<String, String> map = new HashMap<>();
        for ( int i = 0; i < str1.length; i++ ) {
            map.put(str1[i],str2[i]);
        }

        Set<Map.Entry<String, String>> entrySet = map.entrySet();
        Iterator<Map.Entry<String, String>> iterator = entrySet.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }
}

猜你喜欢

转载自www.cnblogs.com/JohnsonLiu/p/9857585.html