目录

1、集合概述

2、集合存储的数据类型

3、不同的集合,底层都会对应不同的数据结构

4、集合继承结构图(部分接口和类)

5、Collection接口中常用的方法

6、Collection 集合迭代(遍历)

7、Collection的contains( )方法理解

8、集合中元素的删除

9、List接口特有的方法

10、ArrayList集合初始化容量及扩容

11、LinkedList双向列表源码分析

12、Vector安全数组

13、HashSet集合特点

14、TreeSet集合特点

15、Map接口常用方法

16、HashMap集合

17、HashMap和HashTable的区别

18、属性类Properties

19、TreeSet可排序集合


1、集合概述

什么是集合,有什么用?

数组其实就是一个集合,集合实际上就是一个容器,集合可以容纳引用类型的数据

为什么集合在开发中使用较多?

集合是一个容器,一次可以容纳多个对象(集合的大小和机器的性能有关,理论无限大)

在实际开发中,假设网页内容连接的是数据库,数据库中有10条记录

假设需要把这10条记录查询出来,在java程序中会将这10条数据封装

成10个java对象,然后将10个java对象放到一个集合中,将集合传递

给前端,然后遍历集合,将一个数据一个数据展现出来

2、集合存储的数据类型

集合中存储的都是java对象的内存地址,或者说集合中存储的是引用,不能直接存储java对象

疑问?为什么list.add(100); 这个集合里面的100是什么数据类型

答案:100,是自动装箱的100,将int型自动装箱成为Integer型的包装型

底层原理:Integer x = new Integer(100);  然后 list.add(x);  x 表示的是内存地址

集合之间通过内存地址的存储可以嵌套

3、不同的集合,底层都会对应不同的数据结构

什么是数据结构:

数据结构就是数据进行存储时,存储的方式,例如:数组、二叉树、链表、哈希表

使用不同的集合等同于使用了不同的数据结构

java已经将数据结构实现了,已经写好了这些常用的集合类,只需要掌握怎么使用集合,什么情况用哪种类型的集合

new ArrayList(); 创建一个集合,底层是数组

new LinkedList(); 创建一个集合,底层是链表

new TreeSet();创建一个集合对象,底层是二叉树

集合在哪个包下:

java.util.*

4、集合继承结构图(部分接口和类)

在java中集合分成两大类

第一种类型:单个方式存储元素(超级父接口:java.util.Collection)

集合的接口之间的继承结构图:

Collection接口调用父类接口Iterable的iterator()方法,拿到集合依赖的迭代器对象

Itertor itertor = "Collection 对象".iterator();       itertor 是迭代器对象

总结:

  • ArrayList:底层是数组
  • LinkedList:底层是双向链表
  • Vector:底层是线程安全的数组,效率低
  • HashSet:底层是HashMap,放到HashSet集合中的元素等同于放到HashMap集合中的key部分
  • TreeSet:底层是TreeMap,放到TreeSet集合中的元素等同于放到TreeMap集合中的Key部分

可以多次看加强记忆

补充:

第二种类型:键值对方式存储元素(超级父接口:java.util.Map)

总结:

  • HashMap:底层是哈希表
  • Hashtable:底层是线程安全的哈希表,效率低
  • Properties:线程安全的属性类,并且key和value只能存储字符串String
  • TreeMap:底层是二叉树,TreeMap 集合的Key 可以按照大小顺序排序

总结:

  • List集合存储元素的特点:

元素有序可重复:存进去和取出来的顺序相同,元素有下标,存进去1,还可以存1

  • Set(对应的是Map)集合存储元素的特点:

元素无序不可重复:存进去和取出来的顺序不一定相同,元素无下标,存进去1,不可继续存1

  • SortSet(对应的是SortedMap)集合存储元素的特点:

元素无序不可重复可排序:01同上,可排序是指可以按照大小进行排序

关键点:

Map集合的key,就是一个Set集合

往Set集合中放数据,实际上放到了Map集合的Key部分

理解点:

怎么往集合这个容器里面放东西,怎么取出来的这个过程需要理解

5、Collection接口中常用的方法

提问:Collection中能存放什么元素?

  • 在没有使用“泛型”之前,Collection中可以存储Object的所有子类型
  • 使用了“泛型”之后,Collection中只能存储某个具体的类型
  • 集合后期我们会学习“泛型”,目前先不用管,我们只需要知道Collection中什么都能存,只要是Object的子类型就行

注意:集合中不能直接存储基本数据类型,也不能存储java对象,只能存储java对象的内存地址

Collection中常用的方法

1、boolean add(Object e) 向集合尾部中添加元素,把元素的内存地址添加进集合

代码演示:

package com.lbj.javase.collection;import java.util.ArrayList;
import java.util.Collection;public class CollectionTest01 {public static void main(String[] args) {//创建一个集合对象,由于接口是抽象的,无法实例化//多态 父类对象的引用指向子类对象Collection collection=new ArrayList();//测试集合中可以放置的是不是内存地址//自动装箱,Integer x = new Integer(100); collection.add(x),x是内存地址collection.add(100);collection.add(3.14);collection.add(new Object());collection.add(new Student());}
}
class Student{}

2、int size( )   获取集合中元素的个数,!注意,并不是获取集合中的容量

代码演示:

3、void  clear( ) 清空集合中的元素

代码演示:

4、boolean remove(Object o) 删除集合中指定元素

代码演示:

package com.lbj.javase.collection;import java.util.ArrayList;
import java.util.Collection;public class CollectionTest01 {public static void main(String[] args) {//创建一个集合对象,由于接口是抽象的,无法实例化//多态 父类对象的引用指向子类对象Collection collection=new ArrayList();//测试集合中可以放置的是不是内存地址//自动装箱,Integer x = new Integer(100); collection.add(x),x是内存地址collection.add(100);collection.add(3.14);collection.add(new Object());collection.add(new Student());collection.remove(100);System.out.println(collection.size());//3}
}
class Student{}

5、boolean contains(Object o)  判断集合中是否包含元素o

代码演示:

6、boolean isEmpty( ) 判断该集合中元素个数是否为0

代码演示:

7、Object[ ] toArray( )   调用这个方法可以把集合转换成数组

代码演示:

package com.lbj.javase.collection;import java.util.ArrayList;
import java.util.Collection;public class CollectionTest01 {public static void main(String[] args) {//创建一个集合对象,由于接口是抽象的,无法实例化//多态 父类对象的引用指向子类对象Collection collection=new ArrayList();//测试集合中可以放置的是不是内存地址//自动装箱,Integer x = new Integer(100); collection.add(x),x是内存地址collection.add(100);collection.add(3.14);collection.add(new Object());collection.add(new Student());Object[] objects=collection.toArray();//遍历数组for (int i = 0; i <objects.length; i++) {System.out.println(objects[i]);}
//结果:
//100
//3.14
//java.lang.Object@1540e19d
//com.lbj.javase.collection.Student@677327b6}
}
class Student{}

6、Collection 集合迭代(遍历)

迭代:就是遍历,不要将迭代两个字想的太难,因为集合不是数组,没有固定长度,无法通过For循环遍历集合

底层:需要用到Collection继承父类Iterable接口的 iterator( ) 方法,然后再在Collection中调用 iterator( ) 方法返回一个Iterator迭代器对象。

此迭代器对象可以使用其中的两个方法,

一个方法是boolean hasNext() ,表示如果下一个元素存在,则返回true,有点类似指针;

一方法是Object next( ) 返回迭代的下一个元素,且取出来的元素类型都是Object类。

代码演示:

package com.lbj.javase.collection;import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;/*** @author LBJ* @version V1.0* @Package com.lbj.javase.collection* @date 2021/3/28 18:40* @Copyright 公司*/
public class CollectionTest02 {public static void main(String[] args) {//创建集合对象Collection collection=new ArrayList();//添加集合元素collection.add("abc");collection.add(100);collection.add(new Object());//集合遍历//第一步:获取集合对象的迭代器对象Iterator iterator=collection.iterator();//第二步:开始遍历while(iterator.hasNext()){//不管存进去的是什么,拿出来的时候一律都是Object类Object o=iterator.next();System.out.println(o);}}}//结果:
//abc
//100
//java.lang.Object@1540e19d

图示:

图示2:

注意:以上的遍历方式,是所有Collection通用的一种方式(除了Map集合不可以用!!!

代码演示2:

ArrayList集合,有序可重复

HashSet集合,无序不可重复

package com.lbj.javase.collection;import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;/*** @author LBJ* @version V1.0* @Package com.lbj.javase.collection* @date 2021/3/28 19:51* @Copyright 公司*/
public class CollectionTest03 {public static void main(String[] args) {//创建ArrayList集合空间:有序可重复Collection collection = new ArrayList();//集合中添加元素collection.add(1);collection.add(2);collection.add(1);collection.add(3);//调用迭代器Iterator iterator=collection.iterator();//测试ArrayList集合元素中是否有序可重复while (iterator.hasNext()){Object o=iterator.next();if(o instanceof Integer){System.out.println("o是属于整数类型");}//输出的时候都会转换成字符串,因为这里println默认调用toString方法System.out.println(o.toString());}System.out.println("分割线---------------------------------------------");//创建HashSet集合空间Collection collection1=new HashSet();//添加元素入集合collection1.add(1);collection1.add(1);collection1.add(3);collection1.add(3);collection1.add(2);collection1.add(5);collection1.add(5);//调用迭代器Iterator iterator1=collection1.iterator();//测试HashSet是否无序不可重复while(iterator1.hasNext()){Object o=iterator1.next();System.out.println(o);}}
}

测试结果:

o是属于整数类型
1
o是属于整数类型
2
o是属于整数类型
1
o是属于整数类型
3
分割线---------------------------------------------
1
2
3
5

迭代器容易犯的错误:

代码演示3(迭代器不能自动刷新):

package com.lbj.javase.collection;import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;public class CollectionTest06 {public static void main(String[] args) {//创建集合对象Collection collection=new ArrayList();//集合中添加元素collection.add("100");collection.add(100);collection.add(new Object());//创建迭代器Iterator iterator=collection.iterator();//遍历集合元素while (iterator.hasNext()){Object o=iterator.next();System.out.println(o);}}}

代码演示4(改变迭代器的位置后):

package com.lbj.javase.collection;import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;public class CollectionTest06 {public static void main(String[] args) {//创建集合对象Collection collection=new ArrayList();//创建迭代器//此时获取的迭代器,指向的是那集合中没有元素状态下的迭代器//一定要注意:集合结构一旦发生改变,迭代器必须重新获取Iterator iterator=collection.iterator();//集合中添加元素collection.add("100");collection.add(100);collection.add(new Object());//遍历集合元素while (iterator.hasNext()){Object o=iterator.next();System.out.println(o);}}}

结果:
Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
    at java.util.ArrayList$Itr.next(ArrayList.java:859)
    at com.lbj.javase.collection.CollectionTest06.main(CollectionTest06.java:30)

7、Collection的contains( )方法理解

语法:bollean contains(Object o) 判断集合中是否存在某个元素,底层调用了equals方法比较

结论:存放一个集合中的类型,一定要重写equals方法

代码演示:

package com.lbj.javase.collection;import java.util.ArrayList;
import java.util.Collection;public class CollectionTest04 {public static void main(String[] args) {//创建集合对象Collection collection=new ArrayList();//向集合中添加元素String s=new String("abc");collection.add(s);String s1=new String("def");collection.add(s1);System.out.println("集合中元素个数为"+collection.size());//2//新建的对象StringString x=new String("abc");//判断:集合内有没有元素x的内容的存在System.out.println(collection.contains(x));//true//底层System.out.println(s.equals(x));//true}
}

JVM示意图:

代码演示2(当类中没有重写equals方法时):

package com.lbj.javase.collection;import java.util.ArrayList;
import java.util.Collection;/*** @author LBJ* @version V1.0* @Package com.lbj.javase.collection* @date 2021/3/31 20:46* @Copyright 公司*/
public class CollectionTest05 {public static void main(String[] args) {//创建集合对象Collection collection=new ArrayList();//创建用户对象User user=new User("aaa");User user1=new User("aaa");//加入集合collection.add(user);//问:此时集合中有没有包含user1的内容//原因:User类中没有重写equals方法,因此contains调用的是Object中的equals方法System.out.println(collection.contains(user1));//false//底层System.out.println(user.equals(user1));//false}
}
class User{public String name;public User() {}public User(String name) {this.name = name;}
}

同理可得:

boolean remove(Object o) 从集合中移除某个元素的内容,底层调用equals方法

8、集合中元素的删除

引言:

集合中的元素直接删除意味着集合的结构发生改变

package com.lbj.javase.collection;import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;public class CollectionTest07 {public static void main(String[] args) {//创建集合对象Collection collection=new ArrayList();//添加元素进入集合collection.add("aaa");collection.add(100);collection.add(new Object());//获取迭代器Iterator iterator=collection.iterator();//遍历输出集合while(iterator.hasNext()){Object o=iterator.next();//删除元素//删除元素后,集合的结构发生变化,应该重新去获取迭代器//但是,循环下一次的时候并没有重新获取迭代器,所以会出现异常:Exception in thread "main" java.util.ConcurrentModificationExceptioncollection.remove(o);//输出System.out.println(o);}}
}

规律:

Iterator it=c.iterator(); 获取迭代器对象,迭代器用来遍历集合,此时相当于对当前集合的状态拍了一个快照,迭代器迭代的时候会不断比对快照和原来集合是否相等,如果不相等,则报异常

迭代器的remove()方法(用迭代器的快照进行元素的删除,则不会出现异常):

package com.lbj.javase.collection;import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;public class CollectionTest07 {public static void main(String[] args) {//创建集合对象Collection collection=new ArrayList();//添加元素进入集合collection.add("aaa");collection.add(100);collection.add(new Object());//获取迭代器Iterator iterator=collection.iterator();//遍历输出集合while(iterator.hasNext()){Object o=iterator.next();//迭代器的快照删除iterator.remove();//输出System.out.println(o);}System.out.println(collection.size());}
}aaa
100
java.lang.Object@1540e19d
0

JVM示意图:

结论:

一:用“集合对象.remove(obj)”直接删除元素的时候,没有通知迭代器(导致迭代器的快照和原集合状态不同),系统报异常

二:用“迭代器对象.remove()”迭代器删除元素的时候,告诉迭代器从快照删除元素,会自动更新迭代器,自动更新集合,系统不报错

三:集合状态发生改变时,要重新获取迭代器

四:在迭代元素的过程中,一定要使用迭代器Iterator的remove方法,删除元素

9、List接口特有的方法

引言:List集合存储元素的特点:

有序(List集合元素有下标)

可重复(可以重复存储相同数据)

1、void add(int index,Object element)  在列表的指定位置插入指定元素,使用不多,效率低

代码演示:

package com.lbj.javase.collection;import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;public class ListTest01 {public static void main(String[] args) {//创建集合对象List list=new ArrayList();//添加元素list.add("a");//默认都是向集合的末尾添加元素list.add("b");list.add("c");//添加元素:List接口独有的//发现添加的元素在下标为1的位置上,且原来的元素需要向后list.add(1,"e");//引入迭代器iIterator iterator=list.iterator();//遍历while (iterator.hasNext()){Object o =iterator.next();System.out.println(o);}}
}
结果:a
e
b
c

2、Object set(int index,Object element) 修改指定下标的元素

代码演示:

3、Object get(int index)  根据下标获取元素,因此有特有的遍历方式

代码演示:

package com.lbj.javase.collection;import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;public class ListTest01 {public static void main(String[] args) {//创建集合对象List list=new ArrayList();//添加元素list.add("a");//默认都是向集合的末尾添加元素list.add("b");list.add("c");//根据下标获取元素Object o1=list.get(0);System.out.println(o1);//根据list集合的特性//可以用数组进行遍历for (int i = 0; i <list.size(); i++) {Object o2 =list.get(i);System.out.println(o2);}}
}
a
a
b
c

4、int indexOf(Object o)  获取元素的下标  int lastIndexOf(Object o) 获取最后出现此元素的下标【因为List集合中的元素可以重复】

代码演示:

5、Object remove(int index)  删除指定下标的元素

代码演示:

10、ArrayList集合初始化容量及扩容

引言:

1、ArrayList集合初始化容量是10【底层先创建了一个长度为0的数组,当添加第一个元素的时候,初始化容量10】

2、ArrayList集合底层是Object类型的数组 Object[ ]

3、构造方法 new ArrayList();   new ArrayList(初始化集合容量大小的数);

4、ArrayList集合的扩容,是原容量的1.5倍,即1*(1+1/2)。底层是数组,应该尽可能少扩容,使用ArrayList集合先预估一个初始化容量

5、面试:ArrayList集合用的是最多的集合,因为它是数组结构,每个元素占用空间大小相同,内存地址连续,可以知道首元素内存地址,检索效率高,随机增删效率低,末尾插入元素效率高,线程不安全

代码演示:

package com.lbj.javase.collection;import java.util.ArrayList;
import java.util.List;public class ArrayListTest01 {public static void main(String[] args) {//创建集合对象List list=new ArrayList();//集合size() 方法是获取当前集合中“元素”的个数,不是获取集合的容量//System.out.println(list.size());//0//创建的集合容量为20List list1=new ArrayList<>(20);}
}

构造方法 new ArrayList(初始化集合容量大小的数),参数里面还可以把另外一个数组传递进去

package com.lbj.javase.collection;import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;public class ArrayListTest02 {public static void main(String[] args) {//创建HashSet集合Collection collection=new HashSet();//添加元素collection.add(100);collection.add(200);collection.add(300);//将HashSet集合传入ArrayListList list=new ArrayList(collection);for (int i = 0; i <list.size(); i++) {Object o =list.get(i);System.out.println(o);}}
}

ArrayList:把检索发挥到极致(一般加元素都是末尾添加,且末尾添加元素效率很高)

LinkedList :把随机增删发挥到极致

11、LinkedList双向列表源码分析

引言:不懂什么是单链表数据结构的可以先看以下文章

JAVA进阶教学之(单链表数据结构)

LinkedList双向列表的JVM示意图:

提问:LinkedList集合有初始容量吗?

无,初始化容量是null

提问:写代码时需要关心是哪个集合吗?

不需要,因为我们要面向接口编程,调用的方法都是接口中的方法,不管是LinkedList还是ArrayList,以后写代码的时候都不需要关心具体是哪个集合

public class LinkedListTest01 {public static void main(String[] args) {List list=new LinkedList();//可以改为new ArrayList();,效果一样 list.add("a");list.add("b");list.add("c");for (int i = 0; i <list.size(); i++) {Object o=list.get(i);System.out.println(o);}}
}

总结:

  1. LinkedList集合是双向链表
  2. 对于链表数据结构来说,随机增删效率较高,检索效率较低
  3. 链表中的元素在空间内存上,内存地址并不连续

12、Vector安全数组

引言:

底层:也是数组(线程安全数组)

初始化容量:10

Vector数组扩容:扩容后是原容量的2倍(10--》20--》30--》40)

ArrayList数组扩容:扩容后是原容量的1.5倍(10--》15--》22.5--》33.75)

Vector中所有的方法都是线程同步的,都带有synchronized关键字,是线程安全的

提问:

怎么将一个线程不安全的ArrayList集合转换成线程安全的呢?

回答:

java.util.Collections;  使用集合工具类

注意:

java.util.Collection 是集合接口

java.util.Collections 是集合工具类(方便集合的操作)

代码演示:

package com.lbj.javase.collection;import java.util.*;public class VectorTest01 {public static void main(String[] args) {List list=new Vector();//默认元素10个list.add(1);list.add(2);list.add(3);list.add(4);list.add(5);list.add(6);list.add(7);list.add(8);list.add(9);list.add(10);//超过10个够扩容为原来的两倍(20)list.add(11);Iterator iterator=list.iterator();while(iterator.hasNext()){Object o=iterator.next();System.out.println(o);}//非线程安全这个可能要改为线程安全List list1=new ArrayList<>();//使用工具类Collections,变成线程安全,这里没办法看效果,因为多线程还没接触Collections.synchronizedList(list1);list1.add("aaa");list1.add("bbb");list1.add("ccc");}
}

13、HashSet集合特点

引言:

  1. 存储时顺序和取出时顺序不同
  2. 无序(不可排序)不可重复
  3. 放到HashSet集合中的元素实际上放到HashMap集合的key部分
  4. 初始化容量建议是2的倍数
  5. 扩容之后,是原容量的2倍

代码演示:

package com.lbj.javase.collection;import java.util.HashSet;
import java.util.Set;public class HashSetTest01 {public static void main(String[] args) {Set<String> set=new HashSet<>();set.add("6");set.add("2");set.add("3");set.add("7");for (String a:set) {System.out.println(a);}}
}
2
3
6
7

14、TreeSet集合特点

引言:

  1. 无序(可排序)不可重复
  2. 但是存储的元素可以自动按照大小顺序排序
  3. 这里的无序值得是存进去的顺序和取出来的顺序不同

代码演示:

package com.lbj.javase.collection;import java.util.Set;
import java.util.TreeSet;public class TreeSetTest01 {public static void main(String[] args) {Set<String> set=new TreeSet();set.add("1");set.add("3");set.add("2");set.add("8");set.add("5");for (String s:set) {System.out.println(s);}}
}
1
2
3
5
8

15、Map接口常用方法

引言:

  1. Map接口和Collection接口没有继承关系
  2. Map接口以 key 和 value 的方式存储数据(key,value)【键值对】
  3. (key,value)都是引用数据类型
  4. (key,value)都是存储对象的内存地址
  5. (key,value)key起主导地位,value起附属地位

部分Map接口方法(代码演示):

package com.lbj.javase.collection;import java.util.Collection;
import java.util.HashMap;
import java.util.Map;public class MapTest01 {public static void main(String[] args) {//创建一个 Map 集合Map<Integer,String> map=new HashMap<>();//向Map集合中添加键值对map.put(10,"小明");//10在这里进行了自动装箱map.put(20,"小红");map.put(30,"小刚");//通过key 获取 valueString s= map.get(10);System.out.println(s);//获取 键值对 的 数量Integer integer=map.size();System.out.println(integer);//通过 key 删除 key-valueString s1=map.remove(10);System.out.println(s1);//判断是否包含某个keyBoolean b=map.containsKey(10);System.out.println(b);//判断是否包含某个valueBoolean b1=map.containsValue("小明");System.out.println(b1);//清空集合//map.clear();//判断 集合 是否为空Boolean b2=map.isEmpty();System.out.println(b2);//获取所有的 valueCollection<String> collection=map.values();for (String s2: map.values()) {System.out.println(s2);}}
}小明
3
小明
false
false
false
小红
小刚

Map集合的遍历(!!!非常重要):

方法一(直接获取key,遍历Set):

缺点:效率相对较低, String value=map.get(key);会再次调用哈希表,会影响性能

package com.lbj.javase.collection;import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;public class MapTest02 {public static void main(String[] args) {//第一种方式:获取所有的key,通过遍历key,来遍历valueMap<Integer,String> map=new HashMap<>();//添加元素map.put(1,"zhangsan");map.put(2,"wanwu");map.put(3,"zhaosi");map.put(4,"xiaohong");//遍历Map集合//获取所有的keys,所有的keys是一个Set集合Set<Integer> keys=map.keySet();//遍历key,通过key,获取valueIterator<Integer> iterable=keys.iterator();while (iterable.hasNext()){//取出其中的一个keyInteger key=iterable.next();//通过key获取valueString value=map.get(key);System.out.println(key+"="+value);}}
}1=zhangsan
2=wanwu
3=zhaosi
4=xiaohong

(同等方法的foreach遍历):

package com.lbj.javase.collection;import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;public class MapTest02 {public static void main(String[] args) {//第一种方式:获取所有的key,通过遍历key,来遍历valueMap<Integer,String> map=new HashMap<>();//添加元素map.put(1,"zhangsan");map.put(2,"wanwu");map.put(3,"zhaosi");map.put(4,"xiaohong");//遍历Map集合//获取所有的keys,所有的keys是一个Set集合Set<Integer> keys=map.keySet();for (Integer key:keys) {System.out.println(key+"="+map.get(key));}}
}

方法二(Map转Set,遍历Set):

Set<Map.Entry<K,V>> entrySet()   把Map集合直接全部转换成Set集合

Set集合中元素的类型是 :Map.Entry<K,V>

优点:效率高(原因:Set集合存储的Map集合中的数据,实际上存储在node节点中,通过node节点直接获取Map集合中的数据,不需要再次经过哈希表,性能提高,底层是Node单向链表,下面会继续说说这个Node)

package com.lbj.javase.collection;import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;public class MapTest02 {public static void main(String[] args) {//第一种方式:获取所有的key,通过遍历key,来遍历valueMap<Integer,String> map=new HashMap<>();//添加元素map.put(1,"zhangsan");map.put(2,"wanwu");map.put(3,"zhaosi");map.put(4,"xiaohong");//第二种方式:Map转换成Set集合Set<Map.Entry<Integer,String>> set=map.entrySet();//迭代set集合Iterator<Map.Entry<Integer,String>> iterator=set.iterator();while (iterator.hasNext()){Map.Entry<Integer,String> node=iterator.next();Integer i=node.getKey();String s=node.getValue();System.out.println(i+"="+s);}}
}1=zhangsan
2=wanwu
3=zhaosi
4=xiaohong

JVM示意图(为了方便理解):

(同等方法的foreach遍历):

package com.lbj.javase.collection;import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;public class MapTest02 {public static void main(String[] args) {//第一种方式:获取所有的key,通过遍历key,来遍历valueMap<Integer,String> map=new HashMap<>();//添加元素map.put(1,"zhangsan");map.put(2,"wanwu");map.put(3,"zhaosi");map.put(4,"xiaohong");for (Map.Entry<Integer,String> node:set) {System.out.println(node);//直接获取node结果一样,但是实际开发中,一般需要获取key或者是value,所以拆开使用更好System.out.println(node.getKey()+"="+node.getValue());}}
}1=zhangsan
1=zhangsan
2=wanwu
2=wanwu
3=zhaosi
3=zhaosi
4=xiaohong
4=xiaohong

16、HashMap集合

引言:

HashMap集合底层是 “哈希表/散列表” 的数据结构

哈希表:

哈希表是一个数组和单向链表的结合体,充分发挥各自优点

数组:在查询方面效率很高,随机增删方面效率很低

单向链表:在查询方面效率很低,随机增删方面效率很高

理解:哈希表是一维数组,这个数组中的每一个元素都是一个单项链表,相当于一个中间体,查询效率相对于纯数组是略低,随机增删改查效率相对于纯单向链表是略低,但是综合起来,哈希表是一个优选择,查询不需要都扫描,只需要部分扫描,随机增删改查是在部分链表上完成

HashMap底层源代码分析:

1、由于HashMap的底层和哈希表的关系非常大,因此我们从哈希值开始入手学习

哈希值:哈希值是key的hashCode()方法的执行结果,hash值通过哈希算法(哈希算法不是一种具体的算法,而是一种思想,将不定长的字符串变成定长的字符串),可以转换存储为数组的下标

伪代码演示:(HashMap的底层是一个数组,数组中的每个下标上存储的是单链表,单链表中的节点有4个属性,分别如下):

public class HashMap{//HashMap底层实际上就是一个数组Node<K,V>[ ] table;//静态内部HashMap.Nodestatic class Node<K,V> implements Map.Entry<K,V> {final int hash;  //存储哈希值final K key;     //存储到Map集合中的那个keyV value;         //存储到Map集合中的那个valueNode<K,V> next;  //存储下一个节点的内存地址
}

哈希表数据结构(配合示意图食用效果会更好):

2、需要了解哈希表,可以从 map.put(k,v)  的实现原理入手

  • 第一步:先将k,v封装到Node对象中
  • 第二步:底层会调用k的hashCode()方法得出hash值
  • 第三步:通过哈希算法,将hash值转换成数组的下标,判断当前下标位置上如果没有任何元素,就把Node添加到当前位置上
  • 第四步:判断当前下标位置上如果有链表,此时会将k的值和链表中的每一个节点的k值进行equals比较,如果返回false,则表示没有重复,新节点将被添加到链表尾端;如果其中有一个equals返回true,则表示有重复,新节点的value会被覆盖

3、从map.get(k) 的实现原理入手

  • 第一步:先调用k的hashCode()方法的出hash值
  • 第二步:通过哈希算法,将hash值转换成数组的下标,判断当前下标位置上如果没有任何元素,返回null
  • 第三步:判断当前下标位置上如果有链表,此时会将k的值和链表中的每一个节点的k值进行equals比较,如果返回false,那么get方法返回null,只要其中有一个节点的k值和参数k值的equals比较的时候返回true,那么此时这个节点的value就是我们需要的value,get方法最终返回这个要找的value

示意图:

哈希表补充:

如果k1和k2的hash值相同,一定是放到同一个单项链表上

如果k1和k2的hash值不相同,但是由于哈希算法执行结束后转换的数组下标可能相同(7%3=1,4%3=1),此时会发生“哈希碰撞”

4、了解哈希表后,就可以分析HashMap集合的key部分特点

  • 无序:因为不一定挂载到哪一个单向链表上
  • 不可重复:equals()方法保证HashMap集合中的key不可重复,如果key重复了,新value会覆盖旧value的值
  • 放在HashMao集合的key部分的元素其实就是放到HashSet集合中了
  • 所以HashSet集合中的元素也需要同时重写hashCode()+equals()方法

5、重点:

  • 通过示意图可以得出HashMap集合的key,会先后调用两个方法,一个方法是hashCode(),一个方法是equals(),那么这两个方法都需要进行重写

6、哈希表HashMap使用不当时会无法发挥出哈希表的性能

  • 原因:假设将所有的hashCode() 的方法返回值固定为某个值,那么会导致底层哈希表变成纯的单向链表,这种情况称为:散列分布不均匀
  • 什么是散列分布均匀?
  • 假设有100个元素,10个单向链表,那么每个单向链表上有10个节点的时候,是最好的结果,属于散列分布均匀
  • 假设将所有的hashCode() 的方法返回值都设定为不一样的值,可以吗?
  • 不行,因为这样的话会导致底层哈希表称为一个纯一维数组,没有链表的概念,属于散列分布不均匀
  • 结论:因此,散列分布均匀需要重写hashCode() 的方法时有一定的技巧

7、关于HashMap集合的扩容

默认初始化容量是16,默认加载因子是0.75

默认加载因子:是当HashMap集合底层数组的容量达到75%的时候,数组开始扩容

重点,记住:HashMap集合初始化容量最好是2的倍数,这是官方推荐的

这是因为达到散列均匀,为了提高HashMap集合的存取效率,所以必须的

1<<4  是二进制位移,位移效率比运算效率高很多

扩容:扩容之后的容量是原容量的2倍

8、hashCode()方法和equals()方法不用研究了,直接使用IDEA工具生成,但是这两个方法需要同时生成

9、JDK 8 的HashMap有新特性

如果哈希表单向链表中元素超过8个,单向链表这种数据结构会变成红黑树数据结构

如果红黑树上的节点数量小于6个时,会重新把红黑树变成单向链表数据结构,提高检索效率

终极结论:

放在HashMap集合key部分的,以及放在HashSet集合中的元素,需要同时重写hashCode()+equals()且hashCode()会先被调用

17、HashMap和HashTable的区别

提问:HashMap集合key部分可以存储null吗?

答案:允许,但是需要注意 key的null值只能有一个,再多也只能被覆盖而已

代码演示:

package com.lbj.javase.collection;import java.util.HashMap;
import java.util.Map;public class MapTest03 {public static void main(String[] args) {Map map=new HashMap<>();//HashMap集合允许key为nullmap.put(null,null);//key重复的时候,value进行覆盖map.put(null,100);System.out.println(map.size());//1//通过key获取valueSystem.out.println(map.get(null));//100}
}
  • HashTable的方法都是带有synchronized,属于线程安全的,效率低的
  • 因此Hashtable的key和value都是不能为null的,但是HashMap的key和value都可以为null
  • HashTable的初始化容量是11,默认加载因子是0.75f,HashTable的扩容是原容量*2+1

代码演示:

package com.lbj.javase.collection;import java.util.Hashtable;
import java.util.Map;public class MapTest04 {public static void main(String[] args) {Map map=new Hashtable<>();//Exception in thread "main" java.lang.NullPointerException//   at java.util.Hashtable.put(Hashtable.java:460)//    at com.lbj.javase.collection.MapTest04.main(MapTest04.java:18)map.put(null,null);//空指针异常//同理map.put(1,null);map.put(null,1);}
}

18、属性类Properties

目前只需要掌握Properties属性类对象的相关方法即可

Properties是一个Map集合,继承HashTable

Properties的key和value都是String类型

Properties被称为属性类对象

Properties是线程安全的

代码演示:

package com.lbj.javase.collection;import java.util.Properties;public class PropertiesTest01 {public static void main(String[] args) {//创建一个Properties对象Properties properties=new Properties();//需要掌握两个方法,一个存,一个取//存properties.setProperty("url","jdbc:mysql://localhost:3306/aaa");properties.setProperty("driver","com.mysql.jdbc.Driver");properties.setProperty("username","root");properties.setProperty("password","123");//取String s=properties.getProperty("url");String s1=properties.getProperty("password");System.out.println(s+s1);//jdbc:mysql://localhost:3306/aaa123}
}

19、TreeSet可排序集合

TreeSet集合底层实际上是一个TreeMap,TreeMap集合底层是二叉树

放到TreeSet集合中的元素,等同于放到TreeMap集合中的key部分

TreeSet集合中的元素:无序不可重复,但是取出时可以按照元素类型的大小顺序自动排序

代码演示(按照字典顺序,默认升序):

package com.lbj.javase.collection;import java.util.TreeSet;public class TreeSetTest02 {public static void main(String[] args) {TreeSet<String> treeSet=new TreeSet<>();treeSet.add("d");treeSet.add("d");treeSet.add("b");treeSet.add("b");treeSet.add("a");
//.add()底层其实就是treeMap中的.put()方法for (String s:treeSet) {System.out.println(s);}//a b dTreeSet<Integer> treeSet1=new TreeSet<>();treeSet1.add(6);treeSet1.add(3);treeSet1.add(1);treeSet1.add(2);for (Integer i:treeSet1) {System.out.println(i);}//1 2 3 6}
}

TreeSet无法对自定义类型排序:

提问:对于自定义类型来说,TreeSet可以排序吗?

以下程序中对于Worker类型来说,无法排序,因为Worker类中没有指定Worker对象之间的比较规则

(谁大谁小无法说明)

代码演示:

package com.lbj.javase.collection;import java.util.TreeSet;public class TreeSetTest03 {public static void main(String[] args) {TreeSet<Worker> treeSet=new TreeSet<>();Worker worker=new Worker(19);Worker worker1=new Worker(34);Worker worker2=new Worker(21);Worker worker3=new Worker(35);//添加一个自定义类的对象进去treeSet中,这一步就是错误的treeSet.add(worker);treeSet.add(worker1);treeSet.add(worker2);treeSet.add(worker3);//遍历for (Worker w:treeSet) {System.out.println(w);}}
}
class Worker{private int age;public Worker(int age) {this.age = age;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}
}

出现异常:

com.lbj.javase.collection.TreeSetTest03
Exception in thread "main" java.lang.ClassCastException: com.lbj.javase.collection.Worker cannot be cast to java.lang.Comparable
    at java.util.TreeMap.compare(TreeMap.java:1294)
    at java.util.TreeMap.put(TreeMap.java:538)
    at java.util.TreeSet.add(TreeSet.java:255)
    at com.lbj.javase.collection.TreeSetTest03.main(TreeSetTest03.java:22)

异常分析:
Worker类没有实现java.lang.Comparable接口,导致类型的强制转换不成功,无法进行比较,而再上一例中的String和Integer源码中都实现了Comparable接口,可以进行类型转换,进行比较无非就是<0、=0、>0 因此就可以进行排序

自定义类型重写CompareTo方法:

代码演示:

package com.lbj.javase.collection;import java.util.TreeSet;public class TreeSetTest03 {public static void main(String[] args) {TreeSet<Worker> treeSet=new TreeSet<>();Worker worker=new Worker(19);Worker worker1=new Worker(34);Worker worker2=new Worker(21);Worker worker3=new Worker(35);//添加一个自定义类的对象进去treeSet中,这一步就是错误的treeSet.add(worker);treeSet.add(worker1);treeSet.add(worker2);treeSet.add(worker3);//遍历for (Worker w:treeSet) {System.out.println(w);}}
}//放在TreeSet集合中的元素需要实现java.lang.Comparable接口
//并且实现compareTo方法,equals可以不写
class Worker implements Comparable<Worker>{private int age;public Worker(int age) {this.age = age;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}//需要在这个方法里面编写  比较的逻辑//k.compareTo(t.key)//原理:拿着参数k和集合中的每一个k进行比较,返回值可能是 >0 =0 <0@Overridepublic int compareTo(Worker o) {//假设o1.compareTo(o2)//this就是 o1//o 就是 o2//o1和o2进行比较的时候,实际上就是this和o进行比较int age1=this.age;int age2=o.age;/*//逻辑判断if(age1==age2){return 0;}else if(age1>age2){return 1;}else if(age1<age2){return -1;}*///升序//return age1-age2 ;//有可能是<0 =0 >0//降序return age2-age1 ;}//重写toString方法@Overridepublic String toString() {return "Worker{" +"age=" + age +'}';}
}

问题:为什么在CompareTo方法里面写了一个return age2-age1 就可以对年龄进行比较?

解答:请看下面的比较规则

compareTo() 方法的返回值很重要:

  • 返回0,表示相同,value会覆盖
  • 返回>0,表示会在右子树上寻找
  • 返回<0,表示会在左子树上寻找

代码演示(比较规则应该怎么写):

package com.lbj.javase.collection;import java.util.TreeSet;public class TreeSetTest04 {public static void main(String[] args) {//创建一个treeSet集合TreeSet<Vip> treeSet=new TreeSet<>();Vip vip=new Vip("zhangsan",23);Vip vip1=new Vip("lisi",23);Vip vip2=new Vip("wanwu",32);Vip vip3=new Vip("zhaosi",38);//将创建的对象添加进treeSet集合中treeSet.add(vip);treeSet.add(vip1);treeSet.add(vip2);treeSet.add(vip3);//遍历集合,查看排序for (Vip v:treeSet) {System.out.println(v);}}
}class Vip implements Comparable<Vip>{private String name;private int age;public Vip(String name, int age) {this.name = name;this.age = 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;}@Overridepublic int compareTo(Vip o) {//写排序规则,按照哪一个属性进行比较if(this.age==o.age){//年龄相相等的时候,比较姓名,因为姓名是String类型,已经实现了CompareToreturn this.name.compareTo(o.name);}else {//年龄不相等的时候,只比较年龄return this.age-o.age;}}@Overridepublic String toString() {return "Vip{" +"name='" + name + '\'' +", age=" + age +'}';}
}Vip{name='lisi', age=23}
Vip{name='zhangsan', age=23}
Vip{name='wanwu', age=32}
Vip{name='zhaosi', age=38}

自平衡二叉树的概念(存放的过程就是排序的过程):

图示:

结论:

放到TreeSet或者TreeMap集合key部分的元素想要做到排序,包括两种方式(P714):

第一种:放在集合中的元素实现java.lang.Compareable接口

第二种:在构造TreeSet或者TreeMap集合的时候传入一个比较器对象

Comparable和Comparator怎么选择呢?

当比较规则不会发生改变的时候,或者说当比较规则只有一个的时候,建议实现Comparable接口

当比较规则出现多个,并且需要多个比较规则之间频繁切换,建议使用Comparator接口

代码演示:

package com.lbj.javase.collection;import java.util.Comparator;
import java.util.TreeSet;/**
比较器方法*/
public class TreeSetTest05 {public static void main(String[] args) {//创建TreeSet集合的时候,需要用到比较器的方式//通过构造方法传递一个比较器进去TreeSet集合中TreeSet<WuGui> treeSet=new TreeSet<>(new WuGuiComparator());WuGui wuGui=new WuGui(10);WuGui wuGui1=new WuGui(6);WuGui wuGui2=new WuGui(15);WuGui wuGui3=new WuGui(11);treeSet.add(wuGui);treeSet.add(wuGui1);treeSet.add(wuGui2);treeSet.add(wuGui3);//遍历for (WuGui w:treeSet) {System.out.println(w);}}
}class WuGui{private int age;public WuGui(int age) {this.age = age;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "乌龟{" +"age=" + age +'}';}
}//需要单独写一个比较器
//比较器实现java.util.Compatator接口(Comparable是java.lang包下的,Comparator是java.util包下的)
class WuGuiComparator implements Comparator<WuGui>{@Overridepublic int compare(WuGui o1, WuGui o2) {//指定比较规则//按照年龄排序return o1.getAge()-o2.getAge();}
}乌龟{age=6}
乌龟{age=10}
乌龟{age=11}
乌龟{age=15}

JAVA进阶教学之(集合)相关推荐

  1. Java进阶,Set集合,Map集合

    Java进阶,Set集合,Map集合 一.Set系列集合 1.Set系列集系概述 Set系列集合特点 无序:存取顺序不一致 不重复:可以去除重复 无索引:没有带索引的方法,所以不能使用普通for循环遍 ...

  2. JAVA进阶教学之(序列化和反序列化)

    目录 1.序列化Serialize和反序列化的概念 2.序列化和反序列化的代码演示: 3.序列化多个对象(序列化集合) 4.transient关键字将部分属性不参与序列化 1.序列化Serialize ...

  3. JAVA进阶教学之(IO流)

    目录 1.什么是IO流 2.流的分类 3.流的四大家族首领 4.java.io.*包下需要掌握的16个流 5.FileInputStream的实用方法 6.FileOutputStream的方法 7. ...

  4. JAVA进阶教学之(foreach)

    foreach: 概念:就是for循环的一个增强版,可以方便程序员对于数组的直接操作,不用考虑数组下标的情况下进行遍历输出 代码演示: package com.lbj.javase.collectio ...

  5. JAVA进阶教学之(泛型)

    泛型 引言: 泛型:泛型允许程序员在编写代码时使用一些以后才指定的类型,在实例化时作为参数指明这些类型 人话:泛型对规定范围产生约束,约束后只能存储泛型指定的类型 诞生:JDK5.0以后 作用:泛型旨 ...

  6. JAVA进阶教学之(String类的常用方法)

    接下来我们进入的学习章程,就是可以通过查阅帮助文档进行实际的应有 1. char charAt(int index) 返回指定索引的 char价值. 代码示例: public class String ...

  7. JAVA进阶教学之(一维数组)

    1.java语言中的数组是一种引用数据类型.不属于基本数据类型 数组的父类是object 2.数组实际上是一个容器,可以同时容纳多个元素(数组是一个数据的集合) 3.字面意思:数组意味着一组数据 4. ...

  8. java进阶开发-----Set集合、Map集合(接java集合)

    (一).Set系列集合 Set系列集合特点 无序:存取顺序不一致 不重复:可以去除重复 无索引:没有带索引的方法,所以不能使用普通for循环遍历,也不能通过索引来获取元素. Set集合实现类特点 Ha ...

  9. JAVA进阶教学之(产生随机数)

    import java.util.Random; 代码演示: package com.lbj.javase.random;import java.util.Random;public class Ra ...

最新文章

  1. Swift4.1第二章 The Basics
  2. Gradle 下载不了
  3. 微软十二月补丁星期二修复58个漏洞
  4. JQuery------jQuery.parseHTML()的使用方法
  5. Linux-SHELL基本操作
  6. 公司要一个网站,是选模板建站还是定制化建站?
  7. 用计算机进行坐标反算,反算在测量中的运用(坐标反算桩号)
  8. 突发!图森未来CEO侯晓迪被罢免,公司疑遭SEC、FBI联手调查,市值一夜砍半
  9. Android 腾讯Bugly的应用升级热更新
  10. EMC共模干扰处理,共模扼流圈的应用和选型。
  11. Java 多个pdf合并成一个pdf
  12. “英制长度单位”的由来
  13. Matlab常用函数集锦
  14. Uwsgi+Nginx+Django部署
  15. 软件研发类应届毕业生培养考核的三个阶段
  16. 生活小发现------联想锁屏壁纸找不到??
  17. SQL2000 安装详解~及安装问题解决方法
  18. 国画欣赏:新疆美协会员扬笛人物画《怒放丝路》
  19. 定义一个类Box,类中有三个整型,表示长,宽,高,定义setInfo(int,int,int)方法,设置三个变量的值。定义volum()方法计算体积//定义area()方法计算表面积。定义to
  20. 2017百度之星 资格赛 1003 度度熊与邪恶大魔王(dp)

热门文章

  1. 【42.59%】【codeforces 602A】Two Bases
  2. netty客户端源码
  3. Java Socket实现客户端服务端之间的通信
  4. CSMA/CD协议分析笔记
  5. phpwindexp.php,phpwind Exp 漏洞利用
  6. mysql中的强制索引_MYSQL中常用的强制性操作(例如强制索引)
  7. c#switch语句判断成绩_C#程序流程控制 知多少?
  8. 四位数码管秒表 c语言编程,4位共阴极数码管秒表设计仿真与程序
  9. 圆弧半径计算图解_刀尖圆弧半径补偿G40,G41,G42 左补偿右补偿你真的能搞清楚吗...
  10. c++ opencv 通过网络连接工业相机_摄像头和机器人视觉开发中的「相机标定」,你了解多少?...