java学习笔记(十四) - 集合

一、概念

  • 可以动态保存任意多个对象,使用比较方便
  • 提供了一系列方便的操作对象方法:add、remove、set、get
  • 使用集合添加、删除新元素代码更加简洁

二、集合框架体系

1. 单列集合

1.1 使用

1
2
3
4
//单列集合 collection
ArrayList arrayList = new ArrayList();
arrayList.add( "jack");
arrayList.add( "tom");

1.2 Collection接口实现类的特点

1
public interface Collection<E> extends Iterable<E>
  • collection实现子类可以存放多个元素,每个元素可以是Object
  • 有些Collection的实现类,可以存放重复的元素,有些不可以
  • 有些Collection的实现类,有些是有序的(List),有些不是有序(Set)
  • Collection接口没有直接的实现子类,是通过它的子接口Set 和List来实现的

1.3 常用方法

1
2
3
4
5
6
7
8
9
10
1) add:添加单个元素
2) remove:删除指定元素
3) contains:查找元素是否存在
4) size:获取元素个数
5) isEmpty:判断是否为空
6) clear:清空
7) addAll:添加多个元素
8) containsAll:查找多个元素是否都存在
9) removeAll: 删除多个元素
10)说明:以ArrayList实现类来演示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class CollectionMethods {

public static void main(String[] args) {

List list = new ArrayList<>();

//add: 添加单个元素
list.add("jack");
list.add(10);//list.add(new Integer(10))
list.add(true);
System.out.println("list=" + list);

//remove: 删除指定元素
list.remove(0);//删除第一个元素
list.remove(true);//指定删除某个元素
System.out.println("list=" + list);

//contains: 查找元素是否存在
System.out.println(list.contains("jack"));

//size: 获取元素的个数
System.out.println(list.size());

//isEmpty: 判断是否为空
System.out.println(list.isEmpty());

//clear: 清空
list.clear();
System.out.println("list=" + list);

ArrayList list2 = new ArrayList<>();
//addAll: 添加多个元素
list2.add("西游记");
list2.add("三国演义");
list.addAll(list2);
System.out.println("list=" + list);

//containsAll: 查找多个元素是否都存在
System.out.println(list.containsAll(list2));

//removeAll: 删除多个元素
list.add("水浒传");
list.removeAll(list2);
System.out.println("list=" + list);
}
}

1. 4 遍历方式

1.4.1 Iterator(迭代器)
  • Iterator对象称为迭代器,主要用于遍历Collection集合中的元素。

  • 所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了lterator接口的对象,即可以返回一个迭代器。

  • Iterator仅用于遍历集合,Iterator 本身并不存放对象。

  • 执行原理:

    • ```java
      Iterator iterator = collection.iterator();//得到一个集合的迭代器
      //方法
      hasNext();//判断是否还有下一个元素
      while(iterator.hasNext()) {
      //next();//指针下移,将下移以后集合位置上的元素返回
      System.out.println(iterator.next());
      
      }
      //在调用iterator.next()方法之前必须要调用iterator.hasNext()进行检测是否有下一条,如果下一条记录无效,直接调用iterator.next()会抛出NoSuchElementException异常
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12

      ##### 1.4.2 增强for

      ```java
      //语法
      for(元素类型 元素名 : 集合名或者数组名){
      访问元素
      }
      //案例
      for(Object object : col) {
      System.out.println(object);
      }

2. 双列集合

1
2
3
HashMap hashMap = new HashMap();
hashMap.put("N01""北京");
hashMap.put( "NO2""上海");

三、List接口

1 概念

  • List 接口是 Collection 接口的子接口
  • List集合类中元素有序(即添加顺序和取出顺序一致)、且可重复
  • List集合中的每个元素都有其对应的顺序索引,即支持索引
  • List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素
  • JDK API中List接口的实现类有:ArrayList、LinkedList、Vector

1.1 常用方法

1
2
3
4
5
6
7
8
9
List 集合里添加了一些根据索引来操作集合元素的方法
1) void add(int index, Object ele)//在index位置插入ele元素
2) boolean addAll(int index, Collection eles)//从index位置开始将eles中的所有元素添加进来
3) Object get(int index)//获取指定index位置的元素
4) int indexOf(Object obj)//返回obj在集合中首次出现的位置
5) int lastIndexOf(Object obj)//返回obj在当前集合中末次出现的位置
6) Object remove(int index)//移除指定index位置的元素,并返回此元素
7) Object set(int index,Object ele)//设置指定index位置的元素为ele ,相当于是替换
8) List subList(int fromlndex, int tolndex)//返回从fromIndex到tolndex位置的子集合
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class ListExercise {
public static void main(String[] args) {
List list = new ArrayList();

list.add("hello");
list.add("hello");
list.add("hello");
list.add("hello");
list.add("hello");
list.add("hello");
list.add("hello");
list.add("hello");
list.add("hello");
list.add("hello");

//在指定位置增加 信息
list.add(2, "韩顺平教育");
//输出指定位置1的信息
System.out.println(list.get(5));

//删除指定位置的元素
list.remove(6);
System.out.println("list=" + list);

//替换指定位置的元素
list.set(7, "study java");

//获得迭代器
Iterator iterator = list.iterator();

//循环遍历
while (iterator.hasNext()) {
Object next = iterator.next();
System.out.println("next=" + next);
}
}
}

2. 遍历的三种方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//方式一:使用iterator
Iterator iterator = col.iterator();
while(iterator.hasNext()) {
Object o = iterator.next();
}

//方式二:使用增强for
for(Object o : col) {

}

//方式三:使用普通for
for(int i = 0; i < list.size(); i++) {
Object object = list.get(i);
System.out.println(object);
}

3. ArrayList

  • permits all elements, including null ,ArrayList可以加入null,并且多个
  • ArrayList是由数组来实现数据存储的门
  • ArrayList 基本等同于Vector,除了ArrayList是线程不安全(执行效率高),在多线程情况下,不建议使用

1. 底层分析

  • ArrayList中维护了一个Object类型的数组elementData;transient Object[] elementData;

    1
    transient Object[] elementData; // non-private to simplify nested class access
  • 当创建对象时,如果使用的是无参构造器,则初始elementData容量为0 (jdk7是10)

    1
    2
    3
    4
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
  • 当添加元素时:先判断是否需要扩容,如果需要扩容,则调用grow方法,否则直接添加元素到合适位置

  • 如果使用的是无参构造器,如果第一次添加,需要扩容的话,则扩容elementData为10,如果需要再次扩容的话,则扩容elementData为1.5倍。

  • 如果使用的是指定容量capacity的构造器,则初始elementData容量为capacity
  • 如果使用的是指定容量capacity的构造器,如果需要扩容,则直接扩容elementData为1.5倍。

4. Vector

4.1 概念

  • Vector底层也是一个对象数组

    • ```java
      protected Object[] elementData;
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10

      - Vector是线程同步的,即线程安全

      - ```java
      public synchronized E get(int index) {
      if (index >= elementCount) {
      throw new ArraylndexOutOfBoundsException(index);
      }
      return elementData(index);
      }

4.2 比较

  • Vector和ArrayList的比较
底层结构 版本 线程安全(同步)效率 扩容倍数
ArrayList 可变数组 jdk1.2 不安全,效率高 如果有参构造器1.5倍; 如果是无参构造器 :1、 第一次10;2、从第二次开始按1.5倍扩容
Vector 可变数组Object[] jdk1.0 安全,效率不高 如果是无参,默认为10,满后按2被进行扩容;如果指定大小,则每次按2倍进行扩容

5. LinkedList

  • LinkedList底层实现了双向链表和双端队列特点
  • 可以添加任意元素(元素可以重复),包括null
  • 线程不安全

5.1 底层机制

  • LinkedList底层维护了一个双向链表
  • LinkedList中维护了两个属性first和last分别指向首节点和尾节点
  • 每个节点(Node对象),里面又维护了prev、next、item三个属性,其中通过prev指向前一个,通过next指向后一个节点。最终实现双向链表
  • 所以LinkedList的元素的添加和删除,不是通过数组完成的,相对来说效率较高。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
public class LinkedList01 {
public static void main(String[] args) {

Node jack = new Node("jack");
Node tom = new Node("Tom");
Node smith = new Node("smith");

// 链接三个结点,形成双向列表 jack > tom > smith
jack.next = tom;
tom.next = smith;

//smith > tom > jack
smith.pro = tom;
tom.pro = jack;

Node first = jack;//让first引用指向jack 就是双向列表的头结点
Node last = smith;//让last引用指向smith 就是双向列表的头结点

//从头到尾进行遍历
System.out.println("======");
while (true) {
if (first == null) {
break;
}
//输出first信息
System.out.println(first);
first = first.next;
}

//从尾到头遍历
System.out.println("======");
while (true) {
if (last == null) {
break;
}
//输出last信息
System.out.println(last);
last = last.pro;
}

//链表添加对象/数据
//在tom 和smith中插入一个对象 hi

//1、先创建一个 Node结点 name 就是hi
Node hi = new Node("hi");

hi.next = smith;
hi.pro = tom;
smith.pro = hi;
tom.next = hi;

//让first再次指向jack

first = jack;//让first引用指向jack,就是双向链表的头结点

//从头到尾进行遍历
System.out.println("======");
while (true) {
if (first == null) {
break;
}
//输出first信息
System.out.println(first);
first = first.next;
}

last = smith;//让last引用指向smith 就是双向列表的头结点
//从尾到头遍历
System.out.println("======");
while (true) {
if (last == null) {
break;
}
//输出last信息
System.out.println(last);
last = last.pro;
}
}
}

//定义一个Node 类,Node 对象 表示双向链表的一个结点
class Node {
public Object item;//真正存放数据
public Node next;//指向后一个结点
public Node pro;//指向前一个结点

public Node(Object name) {
this.item = name;
}

@Override
public String toString() {
return "Node name=" + item;
}
}

5.2 比较

  • ArrayList和LinkedList的比较
底层结构 增删的效率 改查的效率
ArrayList 可变数组 较低;数组扩容 较高
LinkedList 双向链表 较高,通过链表追加 较低

5.3 选择

  • 如何在ArrayList和LinkedList中选择
    • 如果改查的操作多,选择ArrayList
    • 如果增删的操作多,选择LinkedList

6. set接口

  • 无序(添加和取出的顺序不一致),没有索引
  • 不允许重复元素,最多包含一个null

  • 可以使用迭代器和增强for进行遍历,不能使用索引方式进行遍历

6.1 HashSet

  • HashSet实现了Set接口

  • HashSet实际上是HashMap

    • ```java
      public HashSet() {
      map = new HashMap<>();
      
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56

      - 不能有重复元素对象,可以存放null,但只有一个

      - HashSet不保证元素是有序的,取决于hash后,再确定索引的结果

      ```java
      public class HashSet01 {
      public static void main(String[] args) {
      HashSet set = new HashSet();

      //1. 在执行add方法后会返回一个boolean值
      //2. 如果添加成功,返回true,否则返回false
      //3. 可以通过remove指定删除某个对象
      System.out.println(set.add("john"));//T
      System.out.println(set.add("lucy"));//T
      System.out.println(set.add("john"));//F
      System.out.println(set.add("jack"));//T
      System.out.println(set.add("Rose"));//T

      set.remove("john");
      System.out.println("set=" + set);

      //
      set = new HashSet();//置空
      System.out.println("set=" + set);//0

      //4. HashSet 不能添加相同的元素/数据?
      set.add("lucy");//添加成功
      set.add("lucy");//添加失败
      set.add(new Dog("tom"));//OK 不同的对象只是名字相同
      set.add(new Dog("tom"));//OK
      System.out.println("set=" + set);

      //经典面试题
      set.add(new String("TP"));//ok
      set.add(new String("TP"));//添加失败

      System.out.println("set=" + set);
      }
      }

      //定义Dog类
      class Dog {
      private String name;

      public Dog(String name) {
      this.name = name;
      }

      @Override
      public String toString() {
      return "Dog{" +
      "name='" + name + '\'' +
      '}';
      }
      }
6.1.1 底层机制
  • HashSet底层是HashMap,HashMap底层是(数组+链表+红黑树)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class HashSetStructure {
public static void main(String[] args) {

//模拟一个HashSet 的底层 (HashMap的底层结构)

//1. 创建一个数组,数组的类型是Node[]
//2. Node[] 数组称为表
Node[] table = new Node[16];
System.out.println("table= " + table);
//3. 创建结点
Node john = new Node("jhon", null);

table[2] = john;
Node jack = new Node("jack", null);
john.next = jack;//将john的next指向jack形成链表

Node rose = new Node("Rose", null);
jack.next = rose;//将rose结点挂载到jack

Node lucy = new Node("Lucy", null);
table[3] = lucy;

}
}

class Node {//结点类
Object item;//存放数据
Node next;//指向下个结点

public Node(Object item, Node next) {
this.item = item;
this.next = next;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
public class HashSetExercise02 {
public static void main(String[] args) {
/*
定义一个Employee类,该类包含:private成员属性name,sal,birthday(MyDate类.·型),
其中 birthday 为 MyDate类型(属性包括:year, month, day),要求:
1.创建3个Employee 放入 HashSet中
2.当name和birthday的值相同时,认为是相同员工,不能添加到HashSe集合中
*/

HashSet hashSet = new HashSet();
hashSet.add(new Employee2("tom", 8000,2022, 3, 13));
hashSet.add(new Employee2("jack", 12000,2021, 3, 13));
hashSet.add(new Employee2("tom", 3000,2022, 3, 13));

//遍历
for (Object o : hashSet) {
System.out.println("员工信息:" + hashSet);
}
}
}

//定义一个Employee类
class Employee2 extends MyDate {
private String name;
private double sal;


public Employee2(String name, double sal, int years, int month, int day) {
super(years, month, day);
this.name = name;
this.sal = sal;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
Employee2 employee2 = (Employee2) o;
return super.equals(employee2) && Objects.equals(name, employee2.name);
}

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

@Override
public String toString() {
return "员工姓名:" + name + "薪水:" + sal + "出生年月日:" + getYears() + "-" + getMonth()
+ "-" + getDay();
}
}

class MyDate {//MtDate类
private int years;
private int month;
private int day;

public MyDate() {
}

public MyDate(int years, int month, int day) {
this.years = years;
this.month = month;
this.day = day;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MyDate myDate = (MyDate) o;
return years == myDate.years && month == myDate.month && day == myDate.day;
}

@Override
public int hashCode() {
return Objects.hash(years, month, day);
}

public int getYears() {
return years;
}

public void setYears(int years) {
this.years = years;
}

public int getMonth() {
return month;
}

public void setMonth(int month) {
this.month = month;
}

public int getDay() {
return day;
}

public void setDay(int day) {
this.day = day;
}
}

6.2 LinkedHashSet

  • LinkedHashedSet是HashSet的子类
  • LinkedHashSet 底层是一个 LinkedHashMap,底层维护了一个数组+双向链表
  • LinkedHashSet根据元素的hashCode值米决互元系时仔4直,问时使用链表维护元素的次序(图),这使得元素看起来是以插入顺序保存的
  • LinkedHashSet 不允许添重复元素
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class LinkedHashSetExercise {
public static void main(String[] args) {
/*
Car 类(属性:name,price),如果name和price一样,。则认为是相同元素,就不能添加。
*/

LinkedHashSet linkedHashSet = new LinkedHashSet();
linkedHashSet.add(new Car("奥拓", 1000));
linkedHashSet.add(new Car("奥迪", 300000));
linkedHashSet.add(new Car("法拉利", 10000000));
linkedHashSet.add(new Car("奥迪", 300000));
linkedHashSet.add(new Car("保时捷", 70000000));
linkedHashSet.add(new Car("奥迪", 300000));

System.out.println("linkedHashSet= " +linkedHashSet);

}
}

class Car {
private String name;
private double price;

public Car(String name, double price) {
this.name = name;
this.price = price;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Car car = (Car) o;
return Double.compare(car.price, price) == 0 && Objects.equals(name, car.name);
}

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

@Override
public String toString() {
return name + price;
}
}
  • 在LinkedHashSet中维护了一个hash表和双向链表(LinkedHashSet有head和 tail )
  • 每一个节点有before和after属性,这样可以形成双向链表
  • 在添加一个元素时,先求hash值,在求索引.,确定该元素在table的位置,然后将添加的元素加入到双向链表(如果已经存在,不添加[原则和hashset一样])tail.next = newElement;newElement.pre = tail;tail = newEelment;
  • 这样的话,我们遍历LinkedHashSet也能确保插入顺序和遍历顺序一致

7. Map接口

7.1 实现类的特点(jdk8)

  • Map与Collection并列存在。用于保存具有映射关系的数据:Key-Value
  • Map 中的key和 value可以是任何引用类型的数据,会封装到HashMap$Node对象中
  • Map 中的key 不允许重复,原因和HashSet一样(存放时会比较哈希值)
  • Map中的value可以重复
  • Map的key可以为null, value也可以为null,注意key 为null, 只能有一个,value为null ,可以多个
  • 常用String类作为Map的key
    7) key 和 value之间存在单向一对一关系,即通过指定的key 总能找到对应的value
    7) 一组k-v是存放在一个Node结点中,因为Node实现了Entry接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class Map_ {
public static void main(String[] args) {
//1. Map与Collection并列存在, 用于保存具有映射关系的数据:Key-Value(双列元素)

//2. Map 中的 key 和 value 可以是任何引用类型的数据, 会封装到HashMap$Node 对象中
//3. Map中的 Key不允许重复,原因和HashSet 一样
//4. Map 中的 value是可以重复的
//5. Map 中的 key 可以为null value也可以为null 但key为null,只能有一个;value为null,可以为多个
//6. 常用String 作为key,其他的也可以
//7. key 和 value 之间存在单向一对一关系 即通过指定的key 总能找到对应的value
Map map = new HashMap();
map.put("no1", "张三丰");//k-v
map.put("no2", "张无极");//k-v
map.put("no1", "张无忌");//k-v//当有相同的k 就等价替换
map.put("no3", "张无极");//k-v
map.put(null, null);//k-v
map.put(null, "123");//k-v等价替换
map.put("no4", null);//k-v
map.put("no5", null);//k-v
map.put(1, "赵明");
map.put(new Object(), "林白");

//通过get方法 传入一个key 会返回对应的value
System.out.println(map.get("no2"));

System.out.println("map=" + map);
}
}

7.2 体系图

7.3 常用方法

1
2
3
4
5
6
7
1) put//添加
2) remove//根据键删除映射关系
3) get//根据键获取值
4) size//获取元素个数
5) isEmpty//判断个数是否为
6) clear//清除
7) containsKey//查找键是否存在

7.4 遍历方法

1
2
3
4
1) containsKey//查找键是否存在
2) keySet//获取所有的键
3) entrySet//获取所有关系
4) values//获取所有的值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
public class MapFor {
public static void main(String[] args) {

Map map = new HashMap();
map.put("邓超","孙俪");
map.put("王宝强","马蓉");
map.put("宋喆","马蓉");
map.put("刘令博",null);
map.put(null,"刘亦菲");
map.put("鹿晗","关晓彤");


//第一组 :先取出 所有的key 通过key 取出对应的value
Set keySet = map.keySet();
//(1) 增强for
System.out.println("------第一种方法------");
for (Object key :keySet) {
System.out.println(key + "-" + map.get(key));
}
//(2) 迭代器
System.out.println("------第二种方式-----");
Iterator iterator = keySet.iterator();
while (iterator.hasNext()) {
Object key = iterator.next();
System.out.println(key + "-" + map.get(key));

}

//第二组:把所有的Valus 取出
Collection values = map.values();
//(1) 增强for
System.out.println("-----取出所有的valuse 增强for----");
for (Object key : values) {
System.out.println("value " + values);
}

//(2) 迭代器
System.out.println("-----取出所有的valuse 迭代器----");
Iterator iterator1 = values.iterator();
while (iterator1.hasNext()) {
Object key = iterator1.next();
System.out.println("values " + values);
}

//第三组:通过EntrySet 来获取k-v
Set entrySet = map.entrySet();//EntrySet<Map.Entry<k,v>>
//(1) 增强for
System.out.println("-------使用EntrySet 的 for增强(第3种)---");
for (Object entry : entrySet) {
//将entry转成map.entry
Map.Entry m = (Map.Entry) entry;
System.out.println(m.getKey() + "-" + m.getValue());
}

//(2) 迭代器
System.out.println("-------使用EntrySet 的 迭代器(第4种)---");
Iterator iterator2 = entrySet.iterator();
while (iterator2.hasNext()) {
Object entry = iterator2.next();
//System.out.println(entry.getClass());//HashMap$Node-实现-> Map.Entry(getKey, getValues)
//向下转型
Map.Entry m1 = (Map.Entry) entry;
System.out.println(m1.getKey() + "-" + m1.getValue());
}

}
}

7.5 HashMap

  • Map接口的常用实现类:HashMap、Hashtable和Properties
  • HashMap是 Map 接口使用频率最高的实现类
  • HashMap 是以 key-val对的方式来存储数据(HashMap$Node类型)
  • key 不能重复,但是值可以重复,允许使用null键和null值
  • 如果添加相同的key,则会覆盖原来的key-val,等同于修改。(key不会替换,val会替换)
  • 与HashSet一样,不保证映射的顺序,因为底层是以hash表的方式来存储的(jdk8的hashMap底层数组+链表+红黑树)
  • HashMap没有实现同步,因此是线程不安全的,方法没有做同步互斥的操作,没有synchronized
7.5.1 底层机制
7.5.2 扩容机制
  • HashMap底层维护了Node类型的数组table,默认为null
  • 当创建对象时,将加载因子(loadfactor)初始化为0.75
  • 当添加key-val时,通过key的哈希值得到在table的索引。然后判断该索引处是否有元素,如果没有元素直接添加。如果该索引处有元素,继续判断该元素的key是否和准备加入的key相等,如果相等,则直接替换val;如果不相等需要判断是树结构还是链表结构,做出相应处理。如果添加时发现容量不够,则需要扩容
  • 第1次添加,则需要扩容table容量为16,临界值(threshold)为12
  • 以后再扩容,则需要扩容table容量为原来的2倍,临界值为原来的2倍,即24,依次类推
  • 在Java8中,如果一条链表的元素个数超过TREEIFY_THRESHOLD(默认是8),并且table的大小 >= MIN TREEIFY_CAPACITY(默认64),就会进行树化(红黑树)

7.6 HashTable

  • 存放的元素是键值对:即K-V
  • hashTable的键和值都不能为null
  • hashTable使用方法基本上和HashMap一样
  • hashTable是线程安全的,hashMap是线程不安全的

7.7 Hashtable和HashMap对比

版本 线程安全(同步) 效率 允许null键null值
HashMap 1.2 不安全 可以
Hashtable 1.0 安全 较低 不可以

8. Properties

  • 通常作为配置文件
  • Properties类继承自Hashtable类并且实现了Map接口,也是使用一种键值对的形式来保存数据
  • 使用特点和Hashtable类似
  • Properties 还可以用于从 xxx.properties文件中,加载数据到Properties类对象,并进行读取和修改
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Properties properties = new Properties();
properties.put("john",100);
properties.put(null, 100);
properties.put("john", null);
properties.put("lucy",100);
properties.put("lic",100);
properties.put("lic", 88);
System.out.println(properties);
//删除
properties.remove("lic");
//改
properties.put("john""北京大学");
System.out.println(properties);
//查
System.out.println(properties.get("john"));
System.out.println(properties.getProperty("john"));

9. 小结

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

三、Collection工具类

  • Collections是一个操作 Set、List和Map等集合的工具类
  • Collections 中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作

1. 排序操作(均为static方法)

1
2
3
4
5
1) reverse(List)//反转 List 中元素的顺序
2) shuffle(List)//对 List集合元素进行随机排序
3) sort(List)//根据元素的自然顺序对指定List 集合元素按升序排序
4) sort(List,Comparator)//根据指定的Comparator产生的顺序对List集合元素进行排序
5) swap(List,int,int)//将指定list集合中的i处元素和j处元素进行交换
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
public class Collections_ {
public static void main(String[] args) {
//1. 创建ArrayList集合 用于测试
List list = new ArrayList();
list.add("tom");
list.add("smith");
list.add("king");
list.add("milan");
list.add("tom");

// 1) reverse(List):反转List中元素的顺序
Collections.reverse(list);
System.out.println("反转List中元素的顺序 " + list);
// 2) shuffle(List):对List集合元素进行随机排序
Collections.shuffle(list);
System.out.println("随机排序 " + list);
// 3) sort(List):根据元素的自然顺序对指定List集合元素按升序排序
Collections.sort(list);
System.out.println("元素的自然顺序 " + list);
// 4) sort(List, Comparator):根据指定的Comparator 产生的顺序对List集合元素进行排序
Collections.sort(list, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
return ((String)o1).length() - ((String)o2).length();
}
});
System.out.println("指定排序后的情况 " + list);
// 5) swap(List, int, int):将指定 list集合中的i处元素和j处元素进行交换

Collections.swap(list, 1, 2);
System.out.println("list集合交换后的 " + list);

// 1) Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
System.out.println("自然顺序最大元素=" + Collections.max(list));
// 2) Object max(Collection, Comparator):根据Comparator 指定的顺序.返回给定集合中的最大元素
System.out.println("Comparator 指定的顺序= " + Collections.max(list, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
return ((String)o1).length() - ((String)o2).length();
}
}));
// 3) Object min(Collection)
System.out.println("自然顺序最小元素=" + Collections.min(list));

// 4) Object min(Collection,Comparator)
// 5) int frequency(Collection, Object):返回指定集合中指定元素的出现次数
System.out.println("tom出现的次数=" + Collections.frequency(list, "tom"));

// 6) void copy(List dest,List src):将src中的内容复制到dest中
ArrayList dest = new ArrayList();

for (int i = 0; i < list.size(); i++) {
dest.add("");
}
//拷贝
Collections.copy(dest,list);
System.out.println("dest= " + dest);

// 7) boolean replaceAll(List list,Object oldVal, Object newVal):使用新值替换 List 对象的所有旧值

Collections.replaceAll(list,"tom", "hy");
System.out.println("替换后的list=" + list);

}
}

2. 查找、替换

1
2
3
4
5
6
7
1) Object max(Collection)//根据元素的自然顺序,返回给定集合中的最大元素
2) Object max(Collection, Comparator)//根据Comparator 指定的顺序返回给定集合中的最大元素
3) Object min(Collection)
4) Object min(Collection,Comparator)
5) int frequency(Collection,Object)//返回指定集合中指定元素的出现次数
6) void copy(List dest,List src)//将src中的内容复制到dest中
7) boolean replaceAll(List list,Object oldVal, Object newVal)//使用新值替换 List 对象的所有旧值