JAVA之集合

write:2022-3-14
update:2022-5-15

本文讲解Java SE的一个重要知识点:集合,学习集合的一个重要思路:两个接口四个分类

1. 集合

集合用于存储数量不等的对象(只能保存对象),集合类也叫容器类

数组与集合的区别

  1. 数组的长度是固定的(数组用new语句创建后,长度一般是固定的);集合中的元素的数目可以变化。
  2. 数组可以存放基本类型的数据,也可以存放引用类型的数据;集合中不能存放基本类型数据,而只能存放对象的引用。
  3. 数组本身没有操纵元素的方法(只有一个length属性);所有Java集合类都位于java.util包中。Java集合类具有各种操纵集合中元素的方法。

2. 集合的分类

2.1 四种集合

集合大致可以分为Set、List、Queue、Map四种体系:

  1. Set, 代表无序、不可重复的集合;
  2. List, 代表有序、可以重复的集合;
  3. Queue,用于模拟队列这种数据结构;
  4. Map, 代表着具有映射关系的集合;

(1)概念详解:
Set(集):集合中的对象不按特定方式排序,并且没有重复对象。它的有些实现类能对集合中对象按特定方式排序。(无序不重复)
List(列表):集合中的对象按照索引位置排序,可以有重复对象,允许按照对象在集合中的索引位置检索对象。List与数组有些相似。(有序可重复)
Queue(队列):集合中的对象按照先进先出的规则来排列。在队列的末尾添加元素,在队列的头部删除元素。可以有重复对象。双向队列则允许在队列的末尾和头部添加和删除元素。(用于模拟队列这种数据结构)
Map(映射):集合中的每一个元素包含一对键(Key)对象和值(Value)对象,集合中没有重复的键对象,值对象可以重复。它的有些实现类能对集合中的键对象进行排序。(代表具有映射关系的集合)

(2)概念图理解:
以上四类集合可用下图进行理解:
在这里插入图片描述
(3)主要的集合接口和类:
集合类都位于java.util包下,Queue接口,Set接口,List接口是collection接口的子类,其又各有子类
Map接口没有父类,有子类(集合类都位于java.util包下,这些类都是从Collection和Map接口派生而来!)

用以下类图来表示:
在这里插入图片描述

2.2 两种接口

2.2.1 Collection接口

Queue接口,,Set接口,List接口是collection接口的子类,其又各有子类或者子接口
在这里插入图片描述

2.2.1.1 集合操作方法(针对单个元素)

Collection接口中定义了这三种集合体系都具备的、通用的方法;

collection接口中声明了一些对集合的基本操作:
在这里插入图片描述

2.2.1.2 集合的批量操作

在Collection接口中定义了以下方法:
(1)boolean retainAll(Collection<?> c) 修改当前集合,在当前集合中保留那些同时位于参数c集合中的元素,删除其余的元素。如果当前集合最终做了改动,就返回true。 (2)boolean removeAll(Collection<?> c)
删除当前集合中的那些同时位于参数c集合中的元素。
(3)boolean addAll(Collection<? extends E> c) 把参数c集合中的元素加入到当前集合中。 (4)boolean containsAll(Collection<?> c)
判断当前集合中是否存在参数c集合中的元素。

2.2.1.3 遍历集合元素:Iterator

Collection接口的iterator() 方法用于获得集合中的所有元素,返回一个Iterator对象。

Iterator接口隐藏底层集合的数据结构,向客户程序提供了遍历各种类型的集合的统一接口。Iterator接口中声明了如下方法:
boolean hasNext():如果集合还没有迭代完,就返回true。
Object next():返回下一个元素。
void remove():从集合中删除由next()方法返回的当前元素。

遍历元素可以用foreach,也可以用Iterator,区别如下:
eg:

/**
 * Iterator
 */
public class CollectionDemo2 {

    public static void main(String[] args) {
        Collection c = new ArrayList();

        c.add("唐僧");
        c.add("悟空");
        c.add("八戒");
        c.add("沙僧");
        c.add("白龙");

        for (Object obj : c) {
            System.out.println(obj);
//            if (obj.equals("悟空")) {
//                c.remove(obj);    //遍历过程中删除元素会报错
//            }    
        }

        Iterator iterator = c.iterator();
        while (iterator.hasNext()) {
            Object obj = iterator.next();
            System.out.println(obj);
            if (obj.equals("悟空")) {
//                c.remove(obj);   //遍历过程中删除元素会报错
                iterator.remove();    //遍历过程中删除元素不报错
            }
        }

        System.out.println(c);
    }

}

因此Iterator 可以在数据遍历过程中加以干预,Iterator 的能力请于foreach

2.2.1.4 自动装箱与拆箱

JDK的高版本会有,省去基本类型与对象转换的操作,方便编程;

2.2.2 Map接口

在这里插入图片描述

3. 四种集合详讲

3.1 Set(集)

Set(集):集合中的对象不按特定方式排序,并且没有重复对象。它的有些实现类能对集合中对象按特定方式排序。

3.1.1 Set集合中存放的是对象的引用,并且没有重复对象。

eg:

Set<String> set = new HashSet<String>();   //<>是一个泛型标记,里面的是数据类型
String s1 = new String("Hello");
String s2 = s1;
String s3 = new String("world");
set.add(s1);
set.add(s2);
set.add(s3);

打印结果:2(因为s1和s2是同一个对象)

3.1.2 Set类的一个实现类:HashSet类

HashSet是Set接口的典型实现,它具有以下特点:

  1. HashSet不能保证元素的排列顺序;
  2. HashSet集合元素的值可以是null;
  3. HashSet是非线程安全的,多线程环境下须通过代码来保证其同步。

eg:

  HashSet students = new HashSet();
        students.add("唐僧");
        students.add("悟空");
        students.add("八戒");
        students.add("唐僧"); // false
        students.add(null);
        System.out.println(students);

打印:
在这里插入图片描述

1)HashSet类按照哈希算法来存取集合中的对象,具有很好的存取和查找性能(效率高)。当向集合中加入一个对象时,HashSet会调用对象的hashCode()方法获得哈希码,然后根据这个哈希码进一步计算出对象在集合中的存放位置。
2)在Object类中定义了hashCode()和equals()方法。为了保证HashSet能正常工作,要求当两个对象用equals()方法比较的结果为true时,它们的哈希码也相等。

例如,如果customer1.equals(customer2)为true,那么以下表达式的结果也应该是true:
customer1.hashCode()==customer2.hashCode()

3.1.3 Set类的一个实现类:TreeSet类

TreeSet类实现了SortedSet接口(SortedSet接口也是Set的子类),能够对集合中的对象进行排序

TreeSet可以保证元素的排列顺序,它比HashSet多了一些方法:

  1. 返回集合中的第一个(最后一个)元素;
  2. 返回集合中位于指定元素之前(之后)的元素;
  3. 返回集合中某个限定范围内的元素组成的子集;

eg1:

Set<Integer> set=new TreeSet<Integer>();
set.add(8);  //自动装箱,把8转换为相应的Integer对象,再加入到Set中
set.add(7);  
set.add(6);
set.add(9);
 
for(int i:set) //自动拆箱,把集合中的Integer对象转换为int基本类型数据
  System.out.print(i+" ");

以上程序的打印结果为:6 7 8 9

eg2:

TreeSet scores = new TreeSet();
        scores.add(83);
        scores.add(37);
        scores.add(92);
        scores.add(54);
        scores.add(75);
        scores.add(60);
        System.out.println(scores);

        System.out.println(scores.first() + " - " + scores.last());
        System.out.println(scores.lower(60) + " - " + scores.higher(60));  //低于60的一个元素   高于60的一个元素
        System.out.println(scores.headSet(60));//取低于60的所有元素成一个子集
        System.out.println(scores.tailSet(60));//取大于等于60的所有元素成一个子集

打印:
在这里插入图片描述
TreeSet采用红黑树的数据结构来存储元素,它支持两种排序方式:自然排序、定制排序。

1)自然排序:TreeSet能够对数字进行由小到大排序

  1. 添加时,调用元素的compareTo方法比较元素的大小,并按照升序排列元素;
  2. 添加到TreeSet中的对象必须实现Comparable接口,该接口定义了compareTo方法;
  3. Java提供的很多类型均已经实现了Comparable接口,如包装类、String、Date等。

compareTo方法:
obj1.compareTo(obj2):该方法返回一个int类型的整数,返回0代表对象相等,返回正数则代表obj1更大,返回负数则代表obj2更大。

eg:

 // 自然排序
        TreeSet names = new TreeSet();  //实现Comparable接口
        names.add("Lily");
        names.add("John");
        names.add("Tony");
        names.add("Lisa");
        names.add("Mary");
        System.out.println(names);

打印:
字符串是按照首字母顺序排序
在这里插入图片描述

        // 没有实现Comparable接口
//        TreeSet objs = new TreeSet();
//        objs.add(new Object());
//        objs.add(new Object());
//        objs.add(new Object());  //运行报错

上述代码运行报错;

     // 类型不一样,没有可比性.
//        TreeSet objs = new TreeSet();
//        objs.add("nowcoder");
//        objs.add(100);

上述代码运行报错;

2)定制排序:除了自然排序,TreeSet还支持客户化排序
java.util.Comparator接口提供具体的排序方式,指定被比较的对象的类型,Comparator有个compare(T x, T y)方法,用于比较两个对象的大小。

  1. 创建TreeSet时,传入Comparator接口的实例;
  2. Comparator接口定义了compare方法,用于比较两个对象的大小;
  3. TreeSet不再调用compareTo(),转而调用compare()比较大小。

obj1.compare(obj2):该方法返回一个int类型的整数,返回0代表对象相等,返回正数则代表obj1更大,返回负数则代表obj2更大。
当compare(x,y)的返回值大于0,表示x大于y;
当compare(x,y)的返回值小于0,表示x小于y;
当compare(x,y)的返回值等于0,表示x等于y。

eg:

// 定制排序
        TreeSet nums = new TreeSet(new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                Number n1 = (Number) o1;
                Number n2 = (Number) o2;
                if (n1 == null && n2 == null) {
                    return 0;
                } else if (n1 == null) {
                    return 1;
                } else if (n2 == null) {
                    return -1;
                } else if (n1.doubleValue() > n2.doubleValue()) {
                    return -1;
                } else if (n1.doubleValue() < n2.doubleValue()) {
                    return 1;
                } else {
                    return 0;
                }
            }
        });
        nums.add(null);
        nums.add(83.5);
        nums.add(37);
        nums.add(92.5);
        nums.add(54);
        nums.add(75);
        nums.add(60);
        System.out.println(nums);
    }

打印:
在这里插入图片描述

3.1.4 Set类的一个实现类:LinkedHashSet

LinkedHashSet是HashSet的子类,它具备HashSet的一切特点,同时LinkedHashSet采用链表结构维护了元素的插入顺序!
LinkedHashSet采用链表结构维护元素的顺序;

eg:

LinkedHashSet teachers = new LinkedHashSet();
        teachers.add("刘备");
        teachers.add("关羽");
        teachers.add("张飞");
        teachers.add("刘备"); // false
        teachers.add(null);
        System.out.println(teachers);

打印:
在这里插入图片描述

3.1.5 三者选择

  1. HashSet的性能总比TreeSet好,因为TreeSet需要通过红黑树算法来维护元素的顺序;
  2. 对于插入、删除操作,LinkedHashSet比HashSet略慢,这是由维护链表增加的开销,但因为有了链表,所以遍历时LinkedHashSet更快!

3.2 List(列表)

List(列表):集合中的对象按照索引位置排序,可以有重复对象,允许按照对象在集合中的索引位置检索对象。List与数组有些相似。(看情况二选一选取使用)

3.2.1 根据索引来访问集合的方法

List代表有序集合,它提供了根据索引来访问集合的方法:

  1. 将元素插入到集合中指定的索引处;
  2. 将指定索引处的元素从集合中删除;
  3. 从集合中返回指定索引处的元素;
  4. 返回某个元素在集合中的索引值;
  5. 从集合中,返回起始索引和结束索引之间的元素所组成的子集。

3.2.2 ListIterator接口

List提供了listIterator()方法,以返回一个ListIterator对象;
ListIterator接口继承于Iterator接口,该接口增加了如下的方法:

  1. boolean hasPrevious() 判断迭代器指向的元素是否有上一个元素;
  2. Object previous() 返回该迭代器所指向的元素的上一个元素;
  3. void add(Object o) 在迭代器所指向的元素位置插入一个元素。

eg:

List names = new ArrayList();
        names.add("Lily");
        names.add("Mary");
        names.add("John");
        names.add("Tony");
        names.add("Lisa");
        names.add("Mary");
System.out.println(names);

names.add(1, "Lucy");//在索引为1的位置插入
System.out.println(names);

names.remove(1);//在索引为1的位置删除
System.out.println(names);

打印结果:
在这里插入图片描述

 for (int i = 0; i < names.size(); i++) {
            System.out.println((i + 1) + ": " + names.get(i));
        }

打印结果:
在这里插入图片描述

 System.out.println(names.subList(1, 4));

打印结果:
在这里插入图片描述

 System.out.println(names.indexOf("Mary") + ", " + names.lastIndexOf("Mary")

打印结果:
在这里插入图片描述

 ListIterator iterator = names.listIterator();
        while (iterator.hasNext()) {   //前到后
            System.out.print(iterator.next() + " ");
        }
        System.out.println();

打印结果:
在这里插入图片描述

while (iterator.hasPrevious()) {   //后往前
            System.out.print(iterator.previous() + " ");
        }
        System.out.println();

打印结果:
在这里插入图片描述

// 排序
        names.sort(new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                String s1 = (String) o1;
                String s2 = (String) o2;
                if (s1 == null && s2 == null) {
                    return 0;
                } else if (s1 == null) {
                    return -1;
                } else if (s2 == null) {
                    return 1;
                } else {
                    return s1.compareTo(s2);
                }
            }
        });
        System.out.println(names);
    }

打印结果:
在这里插入图片描述

3.2.3 List类的主要实现类之一:ArrayList类

ArrayList:ArrayList代表长度可变的数组。允许对元素进行快速地随机访问(给定索引 进行访问),但是向ArrayList中插入与删除元素的速度较慢。

  1. ArrayList是基于数组实现的List接口;
  2. ArrayList内部封装了一个长度可变的Object[]数组;
  3. 默认该数组的初始长度为10,也可以通过构造器参数显示指定其初始长度;
  4. 每当添加的元素个数超出数组的长度,ArrayList会自动对长度进行扩展。

3.2.4 List类的主要实现类之一:Vector类

  1. Vector也是基于数组的实现,与ArrayList用法相同;
  2. Vector是线程安全的,ArrayList则是非线程安全的;
  3. Vector有保证线程安全的开销,所以性能不如ArrayList;
  4. Vector的子类Stack,用于模拟栈这种数据结构(LIFO)。

Vector、Stack是古老的集合类,性能很差,不建议使用!
1.Collections工具类可以将ArrayList变成线程安全的类;
2.当程序中需要使用栈的数据结构时,推荐使用ArrayDeque。

3.2.5 List类的主要实现类之一:LinkedList类

LinkedList:在实现中采用链表数据结构。对顺序访问进行了优化,向List中插入和删除元素的速度较快,随机访问则相对较慢。随机访问是指检索位于特定索引位置的元素。LinkedList单独具有addFirst()、addLast()、getFirst()、getLast()、removeFirst()和removeLast()方法,这些方法使得LinkedList可以作为堆栈、队列和双向队列使用。

3.2.6 Collections类的sort()排序方法

List只能对集合中的对象按索引位置排序,如果希望对List中的对象按其他特定方式排序,可以借助Comparator接口和Collections类。
Collections类是Java集合类库中的辅助类,它提供了操纵集合的各种静态方法,其中sort()方法用于对List中的对象进行排序:
sort(List list):对List中的对象进行自然排序

以下程序对List中的Integer对象进行自然排序:

List<Integer> list=new ArrayList<Integer>();
list.add(new Integer(3));
list.add(new Integer(4));
list.add(new Integer(3));
list.add(new Integer(2));
 
Collections.sort(list);  //为列表中的元素进行排序
for(Integer i:list)
  System.out.print(i+" ");

以上程序的打印结果为:2 3 3 4
sort(List list,Comparator comparator):对List中的对象进行客户化排序,comparator参数指定排序方式。

3.2.7 把数组包装成List对象

List Arrays.asList(Object… o)

  1. 该方法可以将多个对象或一个对象数组转换成一个List集合;
  2. 实际返回的类型是Arrays的内部类,名字也叫ArrayList;
  3. Arrays.ArrayList是一个固定长度的集合,可以遍历,但不能增加、删除!

java.util.Arrays类的asList()方法能够把一个Java数组包装为一个List对象,这个List对象代表固定长度的数组。
所有对List对象的操作都会被作用到底层的Java数组。由于数组的长度不能改变,因此不能调用这种List对象的add()和remove()方法,否则会抛出java.lang.UnsupportedOperationException运行时异常。

String[] ss={"Tom","Mike","Jack"};
List<String> list=Arrays.asList(ss);
list.set(0,"Jane");  //合法,可以修改某个位置的元素
System.out.println(Arrays.toString(ss));  //打印[Jane, Mike, Jack]
 
//list.remove("Mike");  运行时会抛出java.lang.UnsupportedOperationException

//list.add("Mary");  运行时会抛出java.lang.UnsupportedOperationException

3.3 Queue(队列)

Queue用于模拟队列,它是一种先进先出(FIFO)的容器。
Queue(队列):集合中的对象按照先进先出的规则来排列。在队列的末尾添加元素,在队列的头部删除元素。可以有重复对象。双向队列则允许在队列的末尾和头部添加和删除元素。
多数人都有过在火车站售票大厅排队等待购票的经历。后加入的人排在队列的末尾,排在队列头部的人优先购票后离开队列。
从JDK5开始,用java.util.Queue接口来表示队列。队列的特点是向末尾添加元素,从队列头部删除元素,队列中允许有重复元素。

3.3.1 普通队列

(1)Queue接口具有以下加入元素的方法:
boolean add(E element)
boolean offer(E element)
以上两个方法都向队列的末尾添加元素,如果操作成功就返回true。参数的类型“E”为泛型类型。这两个方法的区别在于,如果队列已满,add()方法会抛出IllegalStateException,而offer()方法返回false。
(2)Queue接口具有以下删除元素的方法:
E remove()
E poll()
以上两个方法都会删除队列头部的元素。这两个方法的区别在于,如果队列为空,remove()方法抛出NoSuchElementException,而poll()方法返回null。
(3)Queue接口具有以下获取元素的方法:
E element()
E peek()
以上两个方法都会返回队列头部的元素,但不删除它。这两个方法的区别在于,如果队列为空,element()方法抛出NoSuchElementException,而peek()方法返回null。

方法表:
在这里插入图片描述
eg:

/**
 * Queue
 */
public class QueueDemo1 {

    public static void main(String[] args) {
        Queue queue = new ArrayDeque();
        queue.offer("Mary");
        queue.offer("Lily");
        queue.offer("Tony");
        queue.offer("John");
        queue.offer("Lucy");
        System.out.println(queue);

        // 返回头部(不删),队列为空时返回null.
        System.out.println(queue.peek());
        System.out.println(queue.peek());
        System.out.println(queue);

        // 返回头部(删除),队列为空时返回null.
        while (queue.size() > 0) {
            System.out.println(queue.poll());
        }
        System.out.println(queue);
    }

}

打印结果:
在这里插入图片描述
在这里插入图片描述

3.3.2 Queue 实现类

  1. PriorityQueue是一种不标准的队列实现,它不是按照加入的顺序来保存元素,而是按照元素的大小排序来保存元素;
  2. Deque接口代表双端队列,它允许你从队列头/尾的任何一端,来进行入队/出队操作,甚至还支持入栈/出栈的操作;
  3. ArrayDeque、LinkedList是Deque接口的实现类,前者采用数组实现双端队列,而后者采用链表结构实现双端队列。

3.3.3 双向/端队列 Deque

Queue接口是单向队列,它有一个子接口Deque,表示双向队列。双向队列的特点是在队列的头部和末尾都可以添加或删除元素。
Deque代表双端队列,它允许你从两端来操作队列中的元素,并支持入栈及出栈操作。

Deque在Queue的基础上,增加了两类方法:

◼ 双端队列方法
(1)Deque接口具有以下向队列头部或末尾添加元素的方法:
void addFirst(E element)
void addLast(E element)
boolean offerFirst(E element)
boolean offerLast(E element)
如果队列已满,前两个方法抛出IllegalStateException,而后两个方法返回false。
(2)Deque接口具有以下从队列头部或末尾删除元素的方法:
E removeFirst()
E removeLast()
E pollFirst()
E pollLast()
如果队列为空,前两个方法抛出NoSuchElementException,而后两个方法返回null。
(3)Deque接口具有以下从队列头部或末尾获取元素(不会删除该元素)的方法:
E getFirst()
E getLast()
E peekFirst()
E peekLast()
如果队列为空,前两个方法抛出NoSuchElementException,而后两个方法返回null。

◼ 栈方法
void push(Object e) //入栈
Object pop() //出栈

eg:

import java.util.*;
public class DequeTester{
  public static void main(String args[]){
    Deque<String> queue=new ArrayDeque<String>();
    queue.add("老二"); //向队列末尾添加元素
    queue.addFirst("老大"); //向队列头部添加元素
    queue.addLast("老三"); //向队列末尾添加元素
    queue.add("老四");  //向队列末尾添加元素
 
    System.out.print("遍历双向队列:");
    for(String e:queue )
      System.out.print(e+" ");
 
    System.out.println("\n删除双向队列的最后一个元素:"+queue.removeLast());  
  }
}

打印结果如下:

遍历双向队列:老大 老二 老三 老四
删除双向队列的最后一个元素:老四

3.4 Map(映射)

◼ Map用于保存具有映射关系的数据(key-value/键-值);
◼ key和value之间存在单向一对一关系,通过指定的key,总能找到确定的value;
◼ Map的key不允许重复,同一个Map的任何两个key通过equals比较总返回false。

3.4.1 向Map集合中加入元素时,必须提供一对键对象和值对象

从Map集合中检索元素时,只要给出键对象,就会返回对应的值对象。
eg:以下程序通过Map的put(Object key,Object value)方法向集合中加入元素,通过Map的get(Object key)方法来检索与键对象对应的值对象:

Map<String,String> map=new HashMap<String,String>();
map.put("1","Monday");
map.put("2","Tuesday");
map.put("3","Wendsday");
map.put("4","Thursday");
 
String day=map.get("2");  //day的值为“Tuesday”

3.4.2 遍历映射

Map<String,String> map=new HashMap<String,String>();
map.put("1","Mon.");
map.put("1","Monday");
map.put("one","Monday");
 
Set<Map.Entry<String,String>> set=map.entrySet();    //将Map用entrySet方法转换成Set,entrySet取出的是每一个元素即键值对
for(Map.Entry entry : set)  //entry表示Map中的一对键与值
  System.out.println(entry.getKey()+":"+entry.getValue());

由于第一次和第二次加入Map中的键对象都为“1”,因此第一次加入的值对象将被覆盖,Map集合中最后只有两个元素,分别为:
“1”对应“Monday”
“one”对应“Monday”

3.4.3 Map实现类

◼ HashMap是Map接口的典型实现;
◼ LinkedHashMap采用链表维护键值对的顺序(为key的顺序为主);
◼ Hashtable是古老的Map实现类(线程安全);
◼ Properties常被用于处理属性文件;
◼ SortedMap是Map的子接口(两种排序方式:自然排序和定制排序);
◼ TreeMap采用红黑树结构实现了SortedMap;
其他不常用类:
◼ EnumMap专门用于处理枚举类型的映射关系;
◼ IdentityHashMap与HashMap类似,但该类以严格相等的方式(key1==key2)认定key的相等;
◼ WeakHashMap与HashMap类似,但该类的key采用的是弱引用,而HashMap的key采用的是强引用。

eg:Map的一些常见操作

/**
 * Map
 */
public class MapDemo1 {

    public static void main(String[] args) {
        Map map = new HashMap();
        map.put("John", 70);
        map.put("Lily", 80);
        map.put("Tony", 90);
        System.out.println(map);

        // value可以重复
        map.put("Mary", 80);
        System.out.println(map);

        // key重复会导致覆盖
        map.put("Lily", 85);
        System.out.println(map);  //Lily打印的还是80

        map.remove("Mary");    //删除Mary
        System.out.println(map);

        System.out.println(map.get("Tony"));    //打印90
        System.out.println(map.containsKey("Lily"));   //打印true
        System.out.println(map.containsValue(85));     //打印true

        // entrySet()得到所有的键值对
        for (Object entry : map.entrySet()) {
            Map.Entry e = (Map.Entry) entry;
            System.out.println(e.getKey() + ": " + e.getValue());   //打印出键值对
        }

        // keySet()得到所有的键对
        for (Object key : map.keySet()) {
            System.out.println(key + ": ");     //打印出键
            System.out.println(key + ": " + map.get(key));     //打印出键值对
        }

        // values()得到所有的值对
        for (Object value : map.values()) {
            System.out.println(value);      //打印出值
        }
    }

}

3.4.4 Map类的主要实现类之一:HashMap

◼ HashMap

  1. HashMap是非线程安全的,其性能高于Hashtable;
  2. HashMap允许使用null作为key/value,而Hashtable不允许存入null。
  3. HashMap按照哈希算法来存取键对象,有很好的存取性能,为了保证HashMap能正常工作,和HashSet一样,要求当两个键对象通过equals()方法比较为true时,这两个键对象的hashCode()方法返回的哈希码也一样。

3.4.5 Map类的主要实现类之一:TreeMap

TreeMap实现了SortedMap接口,能对键对象进行排序。和TreeSet一样,TreeMap也支持自然排序和客户化排序两种方式。
TreeMap是一个红黑树的数据结构,在存储键值对时,它按照key对键值对排序。
◼ 自然排序

  1. 对key进行比较,并根据key按照由小到大的顺序排列键值对;
  2. 所有的key应该是同一个类型,且必须实现Comparable接口;
    ◼ 定制排序
  3. 创建TreeMap时,传入一个Comparator类型的对象;
  4. 该对象负责对所有的key进行比较,此时不要求key实现Comparable接口。

eg:

/**
 * TreeMap
 */
public class MapDemo4 {

    public static void main(String[] args) {
        TreeMap map = null;

        // 自然排序
        map = new TreeMap();
        map.put("Lily", 90);
        map.put("Mary", 80);
        map.put("John", 70);
        map.put("Lucy", 60);
        map.put("Tony", 50);
        map.put("Lisa", 40);
        System.out.println(map);

        System.out.println(map.firstKey() + ", " + map.lastKey());
        System.out.println(map.lowerKey("Lucy") + ", " + map.higherKey("Lucy"));  //Lucy前面和后面的key
        System.out.println(map.headMap("Lucy"));//Lucy前面的Map(不包括Lucy)
        System.out.println(map.tailMap("Lucy"));//Lucy后面的Map(包括Lucy)
        System.out.println(map.subMap("Lily", "Mary"));//左闭右开

        // 定制排序
        map = new TreeMap(new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                String s1 = (String) o1;
                String s2 = (String) o2;
                if (s1 == null && s2 == null) {
                    return 0;
                } else if (s1 == null) {
                    return 1;
                } else if (s2 == null) {
                    return -1;
                } else {
//                    return s1.compareTo(s2) * (-1);
                    return s2.compareTo(s1);
                }
            }
        });

        map.put("Lily", 90);
        map.put("Mary", 80);
        map.put("John", 70);
        map.put("Lucy", 60);
        map.put("Tony", 50);
        map.put("Lisa", 40);
        System.out.println(map);
    }

}

3.4.6 Map类的主要实现类之一:LinkedHashMap

◼ LinkedHashMap

  1. LinkedHashMap采用双向链表维护键值对的顺序;
  2. 相对于HashMap,LinkedHashMap在迭代时性能略高,在插入时性能略低。

3.4.7 Map类的主要实现类之一:Properties

properties文件一般位于resource文件下:
◼ 属性文件
存储属性名与属性值的文件,如“.ini”、“.properties”文件;
◼ Properties
擅长处理属性文件,可以很方便地实现对属性文件的读写操作;
提供load()方法加载属性文件,store()方法保存属性文件;
提供getProperty()方法读取某个属性,setProperty()方法修改某个属性。

eg:

/**
 * Properties
 */
public class MapDemo3 {

    public static void main(String[] args) {
        Properties properties = new Properties();
        try {
            // 相对路径: 以编译后的项目的根目录为起点.
            properties.load(MapDemo3.class.getClassLoader()
                    .getResourceAsStream("resources/db.properties"));
            String s1 = properties.getProperty("username");
            String s2 = properties.getProperty("password");
            System.out.println(s1 + ", " + s2);
        } catch (IOException e) {
            throw new RuntimeException("加载资源文件失败!", e);
        }
    }

}

4. 线程安全的集合

在多线程环境中,可能会有多个线程同时操纵同一个集合,比如一个线程在为集合排序,而另一个线程在不断向集合中加入新的元素。
为了避免并发问题,可以采取以下几种解决措施:
(1)在程序中对可能导致并发问题的代码块进行同步。
(2)利用Collections的synchronizedXXX()方法获得原始集合的同步版本:
Collection synchronizedCollection=
Collections.synchronizedCollection(originalCollection);
程序中确保所有线程只对这个采取了同步措施的集合对象操作。
(3)如果集合只包含单个元素并且不允许被修改,可以用Collections的singletonXXX()方法来构造这样的集合,这可以避免集合被线程错误地修改,而且由于不必采取同步措施,可以提高并发性能。
(4)如果集合的元素不允许被修改,可以用Collections的unmodifiableXXX()方法来生成原始的集合视图,让线程只访问这个集合视图,这可以避免集合被线程错误地修改,而且由于不必采取同步措施,可以提高并发性能。
(5)利用Collections的synchronizedXXX()方法获得原始集合的同步版本后,如果一个线程操纵集合的同步版本,而另一个线程操纵原始的集合,那么仍然会导致并发问题。

为了避免这种情况,可以直接采用java.util.concurrent并发包提供的线程安全的集合,例如:ConcurrentHashMap、ConcurrentSkipListMap、ConcurrentSkipListSet和ConcurrentLinkedQueue。这些集合的底层实现采用了复杂的算法,保证多线程访问集合时,既能保证线程之间的同步,又具有高效的并发性能。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值