10_集合
一、Java集合框架概述
1.集合的引入
①数组在内存存储方面的特点:
数组初始化以后,长度就确定了。
数组声明的类型,就决定了进行元素初始化时的类型
②数组在存储数据方面的弊端:
数组初始化以后,长度就不可变了,不便于扩展;
数组中提供的属性和方法少,不便于进行添加、删除、插入等操作, 且效率不高。同时无法直接获取存储元素的个数;
数组存储的数据是有序的、可以重复的。 ---->存储数据的特点单一
Java 集合类可以用于存储数量不等的多个对象,还可用于保存具有映射关系的关联数组
2.集合的体系
Java 集合可分为 Collection 和 Map 两种体系
①Collection接口: 单列数据, 定义了存取一组对象的方法的集合
List: 元素有序、可重复的集合
Set: 元素无序、不可重复的集合
②Map接口: 双列数据,保存具有映射关系“key-value对”的集合
3.Collection接口继承树
4.Map接口继承树
一些集合允许重复元素,而其他集合不允许
一些集合时有序的 一些是无序
二、Collection接口方法
1.Collection 接口
①Collection 接口是 List、 Set 和 Queue 接口的父接口,该接口里定义的方法既可用于操作 Set 集合,也可用于操作 List 和 Queue 集合。
②JDK不提供此接口的任何直接实现,而是提供更具体的子接口(如: Set和List)实现。
③在 Java5 之前, Java 集合会丢失容器中所有对象的数据类型,把所有对象都当成 Object 类型处理; 从 JDK 5.0 增加了泛型以后, Java 集合可以记住容器中对象的数据类型
2.Collection 接口方法
1、 添加
add(Object obj)
addAll(Collection coll)
2、 获取有效元素的个数
int size()
3、 清空集合
void clear()
4、 是否是空集合
boolean isEmpty()
5、 是否包含某个元素
boolean contains(Object obj): 是通过元素的equals方法来判断是否是同一个对象
boolean containsAll(Collection c): 也是调用元素的equals方法来比较的。 拿两个集合的元素挨个比较。
6、删除
boolean remove(Object obj) : 通过元素的equals方法判断是否是要删除的那个元素。 只会删除找到的第一个元素
boolean removeAll(Collection coll): 取当前集合的差集
7、取两个集合的交集
boolean retainAll(Collection c): 把交集的结果存在当前集合中,不影响c
8、 集合是否相等
boolean equals(Object obj)
9、 转成对象数组
Object[] toArray()
10、获取集合对象的哈希值
hashCode()
11、遍历
iterator(): 返回迭代器对象,用于集合遍历
import java.util.ArrayList;
import java.util.Collection;
public class CollectMethodTest {
public static void main(String[] args) {
Collection c = new ArrayList();
// 1、 添加
// add(Object obj)
c.add(1);
c.add(true);
c.add(12.3);
c.add("java");
Collection c1 = new ArrayList();
c1.add("andriod");
c1.add("hadoop");
c1.add("spark");
c1.add("python");
// addAll(Collection coll)
c.addAll(c1);
for(Object obj : c) {
System.out.println(obj);
}
// 2、 获取有效元素的个数
// int size()
System.out.println("-------"+ c.size());
// 3、 清空集合
// void clear()
// c.clear();
// System.out.println("-------"+ c.size());
// 4、 是否是空集合
// boolean isEmpty() 集合为空则返回true 否则返回false
Collection c2 = new ArrayList();
c2.add("andriod");
c2.add("hadoop");
c2.add("spark");
c2.add("python");
System.out.println(c2.isEmpty());
// 5、 是否包含某个元素
// boolean contains(Object obj): 是通过元素的equals方法来判断是否是同一个对象
System.out.println(c.contains("javaee"));
// boolean containsAll(Collection c): 也是调用元素的equals方法来比较的。 拿两个集合的元素挨个比较。
System.out.println(c.containsAll(c2));
// 6、删除
// boolean remove(Object obj) : 通过元素的equals方法判断是否是要删除的那个元素。 只会删除找到的第一个元素
// c.remove("java");
// System.out.println("********************************8");
// c.removeAll(c1);
// for(Object obj : c) {
// System.out.println(obj);
// }
// boolean removeAll(Collection coll): 取当前集合的差集
// 7、取两个集合的交集
// boolean retainAll(Collection c): 把交集的结果存在当前集合中,不影响c 将交集保存在原集合中
// c.retainAll(c1);
// System.out.println("********************************8");
// for(Object obj : c) {
// System.out.println(obj);
// }
// 8、 集合是否相等
// boolean equals(Object obj) 判断是两个集合所包含的元素是否相同
System.out.println(c.equals(c1));
System.out.println(c1.equals(c2));
// 9、 转成对象数组
// Object[] toArray()
Object[] arr = c.toArray();
for(int i = 0 ; i < arr.length;i++) {
System.out.println(arr[i]);
}
// 10、获取集合对象的哈希值
// hashCode()
System.out.println(c.hashCode());
// 11、遍历
// iterator(): 返回迭代器对象,用于集合遍历
}
}
三、Iterator迭代器接口
Iterator 一个迭代器 是专门用来迭代集合
1、使用:
1 获取该集合的迭代器Iterator
Iterator iter = c.iterator();
Iterator iter = c.iterator();
System.out.println(iter.hasNext());
Object obj1 = iter.next();
System.out.println(obj1);
System.out.println(iter.hasNext());
Object obj2 = iter.next();
System.out.println(obj2);
当迭代的超出集合范围 :NoSuchElementException
while(iter.hasNext()) {
System.out.println(iter.next());
}
for(Iterator ite = c.iterator();ite.hasNext();) {
Object obj = ite.next();
System.out.println(obj);
}
for循环更好(迭代器生命周期短)
但是在实际使用中 使用while
2、迭代器的工作原理
使用迭代器时 需要注意的问题:
Iterator iter = c.iterator();
while(iter.hasNext()) {
//在使用时 判断一次 移动一次 不能判断一次 移动多次
Object obj = iter.next();//调用一次next方法就移动一次
System.out.println(iter.next());
}
①使用迭代器移除元素
Iterator iter = c.iterator();
while(iter.hasNext()) {
Object obj = iter.next();
if(obj.equals(12.3)) {
iter.remove();
}else {
System.out.println(obj);
}
}
②使用迭代器迭代 使用集合删除
Iterator iter = c.iterator();
while(iter.hasNext()) {
Object obj = iter.next();
if(obj.equals(12.3)) {
c.remove(12.3);
}else {
System.out.println(obj);
}
}
注意:
1、Iterator可以删除集合的元素, 但是是遍历过程中通过迭代器对象的remove方法, 不是集合对象的remove方法。
2、如果还未调用next()或在上一次调用 next 方法之后已经调用了 remove 方法,再调用remove都会报IllegalStateException。
注意:
Collection集合中 只能保存Object及其子类
Collection只能保存对象 而不能保存基本类型
Collection c = new ArrayList();
// 1、 添加
// add(Object obj)
c.add(1);
c.add(true);
c.add(12.3);
c.add("java");
上述代码中的基本类型都是其对应的包装类 (自动装箱 自动拆箱)
数组是可以保存基本类型的 :
int[] arr = new int[10];
小结:
集合:
Collection:单列
Map:双列 映射 key-value
Collection:List Set
Collection:
添加 删除 清空 获取长度 判断 交集 迭代
四、Collection子接口之一:List接口
1.List接口概述
①有序集合(也称为序列 )。
②可以精确控制列表中每个元素的插入位置
③列表通常允许重复的元素
2.List的常用方法
void add(int index, Object ele):在index位置插入ele元素
boolean addAll(int index, Collection eles):从index位置开始将eles中
的所有元素添加进来
Object get(int index):获取指定index位置的元素
int indexOf(Object obj):返回obj在集合中首次出现的位置
int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置
Object remove(int index):移除指定index位置的元素,并返回此元素
Object set(int index, Object ele):设置指定index位置的元素为ele
List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex位置的子集合
public static void main(String[] args) {
//创建List集合
List list = new ArrayList();
list.add("hello");
list.add("wolrd");
list.add("java");
list.add(null);
list.add(null);
list.add(null);
// void add(int index, Object ele):在index位置插入ele元素 这里也可以添加一个集合
// list.addAll(2,c); 将集合整体作为list元素
list.add(1,"IOS");
Collection c = new ArrayList();
c.add(1);
c.add(2);
c.add(3);
c.add(4);
// boolean addAll(int index, Collection eles):从index位置开始将eles中
// 的所有元素添加进来
list.addAll(2,c);
System.out.println(list.size());
// Object get(int index):获取指定index位置的元素
// int indexOf(Object obj):返回obj在集合中首次出现的位置
System.out.println("--------------++++"+list.indexOf("IOS"));
// int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置
list.add(5,"IOS");
System.out.println("----------------+++++"+list.lastIndexOf("IOS"));
// Object remove(int index):移除指定index位置的元素,并返回此元素
list.remove(5);
// Object set(int index, Object ele):设置指定index位置的元素为ele
list.set(1, "Hadoop");
// List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex位置的子集合
//遍历
for(Object obj : list) {
System.out.println(obj);
}
List subList = list.subList(3, 7);
System.out.println("子集遍历");
for(int i = 0 ; i < subList.size(); i++) {
System.out.println(subList.get(i));
}
System.out.println("****************************");
for(int i = 0 ; i < list.size(); i++) {
System.out.println(list.get(i));
}
System.out.println("****************************");
Iterator iter = list.iterator();
while(iter.hasNext()) {
System.out.println(iter.next());
}
}
}
3.list集合中的特点:
1 允许存在重复元素
2 允许null元素的存在 并且可以存储多个null
3 list 是有序集合 (有序:存入顺序和遍历顺序一致)
4 可以通过索引来精确控制集合中的元素 并且索引是从0开始
5 通过get(int index) 获取元素的时候 如果超出了列表的长度 则会出现异常IndexOutOfBoundsException
4.List实现类之一: ArrayList
1 ArrayList 本质:Object类型的数组
2 在jdk1.8 中 如果没有指定list的长度 则会首先在创建list对象的时候 创建一个长度为0的空数组 当第一次调用add方法时 则会为列表分配空间10
jdk1.7的时候 在创建列表对象的时候 会直接创建一个长度为10的数组
3 当列表初始容量满的时候 则会自动扩容 按照初始容量的1.5倍进行扩充
4 列表长度可变的本质:就是按照扩容机制 创建一个新的数组 并且将原数组复制到新数组(扩容之后的数组)
5 ArrayList 该实现不是线程安全的
常用方法:
trimToSize()方法的作用: 就是将list的大小 改变为列表中元素所占用空间的实际大小
ListIterator中的方法:
实现list的逆序遍历 前提时必选先正序遍历
public static void main(String[] args) {
List list = new ArrayList();
list.add("hello");
list.add("java");
list.add("ios");
list.add("hadoop");
list.add("spark");
// 获取特有的迭代器
ListIterator iter = list.listIterator();
while(iter.hasNext()) {
System.out.println(iter.next());
}
System.out.println("******************************");
while(iter.hasPrevious()) {
System.out.println(iter.previous());
}
}
使用迭代器添加元素:
// 获取特有的迭代器
ListIterator iter = list.listIterator();
while(iter.hasNext()) {
Object obj = iter.next();
if(obj.equals("ios")) {
iter.add("andriod");
iter.previous();
}
System.out.println(obj);
}
添加完成之后 如果想要获取新增元素 需要调用iter.previous();
注意事项:
如果在使用迭代器迭代集合的时候 如果使用集合的add方法 则会出现
解决异常的方案:
使用迭代器迭代 则对集合的修改 应该使用迭代器
5.List实现类之二: LinkedList
1 LinkedList 的实现采用的是双向链表
2 插入删除数据速度快 但是遍历慢(对于频繁的插入或删除元素的操作,建议使用LinkedList类,效率较高)
新增方法:
void addFirst(Object obj)
void addLast(Object obj)
Object getFirst()
Object getLast()
Object removeFirst()
Object removeLast()
public static void main(String[] args) {
LinkedList list = new LinkedList();
list.add("hello");
list.add("java");
list.add("ios");
list.add("hadoop");
list.add("spark");
list.addFirst("Python");//将当前元素增加到链表头
list.addFirst("AAAA");
list.addLast("BBBB");//将当前元素增加到链表尾部
list.removeFirst();
list.removeLast();
System.out.println(list.getFirst());//获取头元素
System.out.println(list.getLast());//获取尾元素
Iterator iter = list.iterator();
while(iter.hasNext()) {
System.out.println(iter.next());
}
}
6.List 实现类之三: Vector
Vector 是一个古老的集合, JDK1.0就有了。大多数操作与ArrayList相同,区别之处在于Vector是线程安全的
在各种list中,最好把ArrayList作为缺省选择。当插入、删除频繁时,使用LinkedList; Vector总是比ArrayList慢,所以尽量避免使用。
面试题:
请问ArrayList/LinkedList/Vector的异同? 谈谈你的理解? ArrayList底层是什么?扩容机制?Vector和ArrayList的最大区别 ==
ArrayList和LinkedList的异同
二者都线程不安全,相对线程安全的Vector,执行效率高。
此外, ArrayList是实现了基于动态数组的数据结构, LinkedList基于链表的数据结构。对于随机访问get和set, ArrayList觉得优于LinkedList,因为LinkedList要移动指针。对于新增和删除操作add(特指插入)和remove, LinkedList比较占优势,因为ArrayList要移动数据。
ArrayList和Vector的区别
Vector和ArrayList几乎是完全相同的,唯一的区别在于Vector是同步类(synchronized),属于强同步类。因此开销就比ArrayList要大,访问要慢。正常情况下,大多数的Java程序员使用ArrayList而不是Vector,因为同步完全可以由程序员自己来控制。 Vector每次扩容请求其大小的2倍空间,而ArrayList是1.5倍。== Vector还有一个子类Stack。
五、Collection子接口之二:Set接口
1、特点
1.不包含重复元素的集合
2.set集合是无序
3.set集合中也可以存储null 但是只能存储一个
4.在set中判断元素是否重复 使用equals()方法
如果两个对象的equals方法返回true 则他们的hashcode值应该也是相等的
换句话来说 :如果两个对象的equals返回是true 但是hashcode不一致 则也会认为元素不重复
在自定义对象的时候 都需要去重写Object的hashcode 和equals方法
public class Student {
private String name;
private int age;
public Student() {
super();
}
public Student(String name, int age) {
super();
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;
}
// @Override
// public boolean equals(Object obj) {
// Student stu = (Student)obj;
// if(this.name .equals(stu.name)) {
// return true;
// }
// return false;
// }
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + "]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Student other = (Student) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
public static void main(String[] args) {
//创建set集合对象
Set set = new HashSet();
set.add("ccc");
set.add("aaa");
set.add("1111");
set.add("aaa");
set.add("eeee");
set.add("aaa");
set.add(null);
set.add("bbb");
set.add("bbb");
set.add(null);
set.add("bbb");
set.add(null);
Student stu1 = new Student("张三",20);
Student stu2 = new Student("张三",21);
Student stu3 = new Student("张三",23);
Student stu4 = new Student("张三",21);
Student stu5 = new Student("张三",21);
set.add(stu1);
set.add(stu2);
set.add(stu3);
set.add(stu4);
set.add(stu5);
Iterator iter = set.iterator();
while(iter.hasNext()) {
System.out.println(iter.next());
}
}
面试题:Set集合中 判断元素是否重复的机制是什么?
小结:
集合:
Collection
Map
Colletion :List Set
List :有序 可重复 多个null
ArrayList :可变数组
LinkedList:双向链表
Vector:可变数组
Set:无序 不可重复 一个null
HashSet
LinkedHashSet
TreeSet
2.Set实现类之一: HashSet
1.此实现不同步 (不是线程安全的)
2.基于HashMap来实现的
3.初始容量为16 负载因子为0.75 扩容两倍(底层也是数组, 初始容量为16, 当如果使用率超过0.75, (16*0.75=12)就会扩大容量为原来的2倍。 )
当调用HashSet的add方法时 是将要保存的元素保存在HashMap key的位置
1.重写 hashCode() 方法的基本原则:
在程序运行时,同一个对象多次调用 hashCode() 方法应该返回相同的值。
当两个对象的 equals() 方法比较返回 true 时,这两个对象的 hashCode()方法的返回值也应相等。
对象中用作 equals() 方法比较的 Field,都应该用来计算 hashCode 值
2.重写 equals() 方法的基本原则
以自定义的Customer类为例,何时需要重写equals()?
当一个类有自己特有的“逻辑相等”概念,当改写equals()的时候,总是要改写hashCode(),根据一个类的equals方法(改写后),两个截然不同的实例有可能在逻辑上是相等的,但是, 根据Object.hashCode()方法,它们仅仅是两个对象。
因此,违反了“相等的对象必须具有相等的散列码”。
结论:复写equals方法的时候一般都需要同时复写hashCode方法。 通常参与计算hashCode的对象的属性也应该参与到equals()中进行计算。
3.Set实现类之二: LinkedHashSet
LinkedHashSet 是 HashSet 的子类
LinkedHashSet 根据元素的 hashCode 值来决定元素的存储位置,但它同时使用双向链表维护元素的次序,这使得元素看起来是以插入顺序保存的。(有序)
效率低于HashSet
不允许集合元素重复。
4.Set实现类之三: TreeSet
基于TreeMap
TreeSet底层使用红黑树结构存储数据
特点:
有序,查询速度比List快
有序指的是他的自然顺序(不是存储顺序)
所有的基本类型的包装类 及String类都实现了一个Comparable接口
该接口对实现它的每个类的对象强加一个整体排序。 这个排序被称为类的自然排序 ,类的compareTo方法被称为其自然比较方法 。
1.排 序—自然排序
当需要将自定义对象保存到TreeSet集合的时候
1 该类必须实现Comparable 并且要重写compareTo 在compareTo方法中来定义针对该类对象的排序规则
2 在TreeSet针对字符的排序 如果是字母 则按照字典顺序 如果是汉字 则按照汉字的Unicod码从小到大排序
3.因为只有相同类的两个实例才会比较大小,所以向 TreeSet 中添加的应该是同一个类的对象。
/*
* 优先按照姓名排序 如果姓名相同 则按照年龄排序
*/
@Override
public int compareTo(Object o) {
Student stu = (Student)o;
int result = this.name.compareTo(stu.getName());
if( result == 0) {
result = this.age.compareTo(stu.age);
}
return result;
}
2.排 序—定制排序
使用外部类来实现定制排序
public class StuComparator implements Comparator {
@Override
public int compare(Object o1, Object o2) {
Student stu1 = (Student)o1;
Student stu2 = (Student)o2;
int result = stu1.getName().compareTo(stu2.getName());
if(result == 0 ) {
result = stu1.getAge() - stu2.getAge();
}
return result;
}
}
public class TreeSetTest {
public static void main(String[] args) {
TreeSet set = new TreeSet(new StuComparator());
Student stu1 = new Student("张三",21);
Student stu2 = new Student("张三",23);
Student stu3 = new Student("王五",18);
Student stu4 = new Student("赵四",20);
Student stu5 = new Student("刘能",22);
set.add(stu1);
set.add(stu2);
set.add(stu3);
set.add(stu4);
set.add(stu5);
for(Object obj : set) {
System.out.println(obj);
}
}
}
第二种实现方式(使用成员内部类来实现定制排序)
public class TreeSetTest {
public static void main(String[] args) {
TreeSet set = new TreeSet(new TreeSetTest().new StuComparator1());
Student stu1 = new Student("张三",21);
Student stu2 = new Student("张三",23);
Student stu3 = new Student("王五",18);
Student stu4 = new Student("赵四",20);
Student stu5 = new Student("刘能",22);
set.add(stu1);
set.add(stu2);
set.add(stu3);
set.add(stu4);
set.add(stu5);
for(Object obj : set) {
System.out.println(obj);
}
}
//使用成员内部类来实现定制排序
public class StuComparator1 implements Comparator{
@Override
public int compare(Object o1, Object o2) {
Student stu1 = (Student)o1;
Student stu2 = (Student)o2;
int result = stu1.getName().compareTo(stu2.getName());
if(result == 0 ) {
result = stu1.getAge() - stu2.getAge();
}
return result;
}
}
}
实现方式三:匿名内部类
public class TreeSetTest {
public static void main(String[] args) {
//使用匿名内部类来实现比较
TreeSet set = new TreeSet(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
Student stu1 = (Student)o1;
Student stu2 = (Student)o2;
int result = stu1.getName().compareTo(stu2.getName());
if(result == 0 ) {
result = stu1.getAge() - stu2.getAge();
}
return result;
}
});
Student stu1 = new Student("张三",21);
Student stu2 = new Student("张三",23);
Student stu3 = new Student("王五",18);
Student stu4 = new Student("赵四",20);
Student stu5 = new Student("刘能",22);
set.add(stu1);
set.add(stu2);
set.add(stu3);
set.add(stu4);
set.add(stu5);
for(Object obj : set) {
System.out.println(obj);
}
}
}
随堂练习:
在List内去除重复数字值,要求尽量简单
拓展:去除List集合中的重复元素 并对集合中的元素进行排序(TreeSet特性)
小结:
Set 无序 不可重复
HashSet:hashMap
hashCode决定元素在数组中的位置 在存入元素时 首先计算该元素的hashCode值 hashCode决定了该元素在数组中存储的位置
如果该HashCode值 与当前数组中所有的元素的hashCode都不相同 直接将元素保存到set中
如果HashCode 与当前数组中某一个元素的hasCode相同,
则需要调用其equals方法
equals返回true 此时判定为重复 不保存
equals返回false 则认为不重复 保存可以成功 存储的方式是采用链表
LinkedHashSet:HashSet的子类 在其中采用双向链表来维护元素的存入顺序
LinkedHashSet 效率低于HashSet
TreeSet:TreeMap 红黑树结构
TreeSet要求对于存入其中的元素要实现一个比较器
排序方式:自然排序 定制排序
在排序中 对数组 英文 采用字典顺序
汉字 采用汉字的Unicode码进行排序
定制排序:在定义容器的时候 为容器来指定所使用的比较器
1 外部类来实现
2 采用成员内部类
3 匿名内部类。
六:Map接口
Map 是双列 反应的是映射关系 key - value 函数 y = f(x)
1.Map接口概述
Map与Collection并列存在。用于保存具有映射关系的数据:key-value
Map 中的 key 和 value 都可以是任何引用类型的数据
Map 中的 key 用Set来存放, 不允许重复,即同一个 Map 对象所对应的类,须重写hashCode()和equals()方法
常用String类作为Map的“键”
key 和 value 之间存在单向一对一关系,即通过指定的 key 总能找到唯一的、确定的 value
Map接口的常用实现类: HashMap、 TreeMap、 LinkedHashMap和Properties。
其中, HashMap是 Map 接口使用频率最高的实现类
2.常用方法
①添加、 删除、修改操作:
Object put(Object key,Object value):将指定key-value添加到(或修改)当前map对象中
void putAll(Map m):将m中的所有key-value对存放到当前map中
Object remove(Object key):移除指定key的key-value对,并返回value
void clear():清空当前map中的所有数据
②元素查询的操作:
Object get(Object key):获取指定key对应的value
boolean containsKey(Object key):是否包含指定的key
boolean containsValue(Object value):是否包含指定的value
int size():返回map中key-value对的个数
boolean isEmpty():判断当前map是否为空
boolean equals(Object obj):判断当前map和参数对象obj是否相等
Map中存入元素
1 当前key如果不存在 则直接保存
2 如果key已经存在 则会使用新的value 替换原来的key所映射的value
3 key 不允许重复 key是用set集合进行保存的 Value是可以重复的
4 允许null键和null值的存在 但是key中null 只能有一个 value中的null可以多个
③元视图操作的方法:
Set keySet():返回所有key构成的Set集合
Collection values():返回所有value构成的Collection集合
Set entrySet():返回所有key-value对构成的Set集合
key —value 一对一关系
value - -key 一对多关系
public static void main(String[] args) {
Map map = new HashMap();
//存入元素
map.put("1", "AAA");
map.put("4", "DDD");
map.put("2", "BBB");
map.put("3", "CCC");
map.put("5", "EEE");
System.out.println(map.size());
//map.remove("3");
System.out.println(map.size());
//通过key获取value
System.out.println(map.get("1"));
//判断当前集合中是否包指定的key
System.out.println(map.containsKey("0"));
//判断当前集合中是否包指定的value
System.out.println(map.containsValue("AAA"));
//map中key value的特点
map.put("3", "HHHH");
System.out.println(map.containsValue("CCC"));//true
System.out.println(map.size());
map.put("6", "HHHH");
System.out.println(map.size());
map.put(null,null);
map.put("7",null);
map.put("8",null);
System.out.println(map.size());
//Set keySet():返回所有key构成的Set集合
Set set = map.keySet();
//第一种遍历方式
for(Object key : set) {
System.out.println(key +"----"+map.get(key));
}
//Collection values():返回所有value构成的Collection集合
Collection c = map.values();
for(Object value : c) {
System.out.println(value);
}
//Set entrySet():返回所有key-value对构成的Set集合
//map的第二种遍历方式
Set entrySet = map.entrySet();
Iterator iter = entrySet.iterator();
while(iter.hasNext()) {
Map.Entry entry = (Entry) iter.next();
System.out.println(entry.getKey() +"-----"+entry.getValue());
}
}
3.Map实现类之一: HashMap
HashMap 使用频率最高的实现
HashMap是 Map 接口使用频率最高的实现类。
底层使用哈希表结构
允许使用null键和null值,与HashSet一样,不保证映射的顺序。
所有的key构成的集合是Set:无序的、不可重复的。所以, key所在的类要重写:equals()和hashCode()
所有的value构成的集合是Collection:无序的、可以重复的。所以, value所在的类要重写: equals()
一个key-value构成一个entry
所有的entry构成的集合是Set:无序的、不可重复的
HashMap 判断两个 key 相等的标准是:两个 key 通过 equals() 方法返回 true,hashCode 值也相等。
HashMap 判断两个 value相等的标准是:两个 value 通过 equals() 方法返回 true。
非线程安全
1.HashMap的存储结构:
jdk7:数组+ 链表
jdk8: 数组 + 链表 + 红黑树
(存储的整体称为桶)
以jdk7 为例,保存元素:
1 集合的初始化:创建HashMap 时 会初始化一个容量为16的数组 负载因子 0.75 临界值(阈值)扩充的临界值 12
jdk8 初始化时 并不会立即创建一个容量为16的数组
而是在第一次put元素的时候才会初始化数组分配空间。
2 元素的保存:
①当保存元素时 首先会根据key来计算出hashCode值 hashCode决定了元素(Entry)在数组中的保存位置
②当key的hashCode值 与当前数组中的所有的元素的hashCode都不相同 则直接保存
③当key的hashCode值 与当前数组中的某一个或多个的hashCode相同时。此时并不会直接判定保存失败。
④此时需用来使用该key的equals方法 来判定 如果equals返回的是true不会保存
(此时 value 会使用新值来替换旧值)
⑤equals返回的时false 会以链表的形式保存元素
jdk7和8的区别:
1.数组类型不同:
jdk7 使用的时数组+ 链表
Entry[] table
jdk8 使用的时数组 + 链表 + 红黑树
Node[] table
2.在jdk8的时候 当链表的长度大于8时 则将链表转换为红黑树 红黑树的引入 提升元素检索效率
3.JDK1.8中,形成链表结构时,新添加的key-value对在链表的尾部(七上八下)
4.初始化数组不同:
JDK1.7中 创建HashMap 时 会初始化一个容量为16的数组
JDK1.8中默认情况下,先不创建长度为16的数组,当首次调用map.put()时,再创建长度为16的数组
扩容机制:
当当前数组的容量的使用达到原数组的3/4时 则就扩容
扩容的时机 是由其中的临界值(阈值)来决定 扩容为原来的2倍 并将原来的元素复制到扩容之后的集合中
JDK1.8中:
当HashMap中的其中一个链的对象个数如果达到了8个,此时如果capacity没有达到64,那么HashMap会先扩容解决,如果已经达到了64,那么这个链会变成树,结点类型由Node变成TreeNode类型。当然,如果当映射关系被移除后,下次resize方法时判断树的结点个数低于6个,也会把树再转为链表。
关于映射关系的key是否可以修改? answer:不要修改
映射关系存储到HashMap中会存储key的hash值,这样就不用在每次查找时重新计算每一个Entry或Node(TreeNode)的hash值了,因此如果已经put到Map中的映射关系,再修改key的属性,而这个属性又参与hashcode值的计算,那么会导致匹配不上
面试题:
谈谈你对HashMap中put/get方法的认识?如果了解再谈谈HashMap的扩容机制?默认大小是多少?什么是负载因子(或填充比)? 什么是吞吐临界值(或阈值、 threshold)?
面试题:负载因子值的大小,对HashMap有什么影响
负载因子的大小决定了HashMap的数据密度。
负载因子越大密度越大,发生碰撞的几率越高,数组中的链表越容易长,造成查询或插入时的比较次数增多,性能会下降。
负载因子越小,就越容易触发扩容,数据密度也越小,意味着发生碰撞的几率越小,数组中的链表也就越短,查询和插入时比较的次数也越小,性能会更高。但是会浪费一定的内容空间。而且经常扩容也会影响性能,建议初始化预设大一点的空间。
按照其他语言的参考及研究经验,会考虑将负载因子设置为0.7~0.75,此时平均检索长度接近于常数
(相当于n = n | n >>>1)
4.Map实现类之二: LinkedHashMap
LinkedHashMap 是 HashMap 的子类
在HashMap存储结构的基础上,使用了一对双向链表来记录添加元素的顺序
与LinkedHashSet类似, LinkedHashMap 可以维护 Map 的迭代顺序:迭代顺序与 Key-Value 对的插入顺序一致
非线程安全
HashMap中的内部类: Node
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
}
LinkedHashMap中的内部类: Entry
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after;
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
5.Map实现类之三: TreeMap
①TreeMap存储 Key-Value 对时, 需要根据 key-value 对进行排序。
TreeMap 可以保证所有的 Key-Value 对处于有序状态。
TreeSet底层使用红黑树结构存储数据
非线程安全
TreeMap 的 Key 的排序:
自然排序: TreeMap 的所有的 Key 必须实现 Comparable 接口,而且所有的 Key 应该是同一个类的对象,否则将会抛出 ClasssCastException
定制排序:创建 TreeMap 时,传入一个 Comparator 对象,该对象负责对TreeMap 中的所有 key 进行排序。此时不需要 Map 的 Key 实现Comparable 接口
TreeMap判断两个key相等的标准:两个key通过compareTo()方法或者compare()方法返回0。
6.Map实现类之四: Hashtable
Hashtable是个古老的 Map 实现类, JDK1.0就提供了。不同于HashMap,
Hashtable是线程安全的。
Hashtable实现原理和HashMap相同,功能相同。底层都使用哈希表结构,查询速度快,很多情况下可以互用。
与HashMap不同, Hashtable 不允许使用 null 作为 key 和 value
与HashMap一样, Hashtable 也不能保证其中 Key-Value 对的顺序
Hashtable判断两个key相等、两个value相等的标准, 与HashMap一致。
7.Map实现类之五: Properties
Properties 类是 Hashtable 的子类,该对象用于处理属性文件
由于属性文件里的 key、 value 都是字符串类型,所以Properties 里的 key和 value 都是字符串类型
存取数据时,建议使用setProperty(String key,String value)方法和getProperty(String key)方法
public static void main(String[] args) throws FileNotFoundException, IOException {
Properties p = new Properties();
p.load(new FileInputStream(new File("jdbc.properties")));
String username = p.getProperty("username");
String password = p.getProperty("password");
String driver = p.getProperty("driver");
System.out.println(username);
System.out.println(password);
System.out.println(driver);
}
七:Collections工具类
Collections 是一个操作 Set、 List 和 Map 等集合的工具类
Collections 中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作,还提供了对集合对象设置不可变、对集合对象实现同步控制等方法
排序操作: (均为static方法)
reverse(List): 反转 List 中元素的顺序
shuffle(List): 对 List 集合元素进行随机排序
sort(List): 根据元素的自然顺序对指定 List 集合元素按升序排序
sort(List, Comparator): 根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
swap(List, int, int): 将指定 list 集合中的 i 处元素和 j 处元素进行交换
1.Collections常用方法
查找、替换:
Object max(Collection): 根据元素的自然顺序,返回给定集合中的最大元素
Object max(Collection, Comparator): 根据 Comparator 指定的顺序,返回给定集合中的最大元素
Object min(Collection)
Object min(Collection, Comparator)
int frequency(Collection, Object): 返回指定集合中指定元素的出现次数
void copy(List dest,List src):将src中的内容复制到dest中
boolean replaceAll(List list, Object oldVal, Object newVal): 使用新值替换List 对象的所有旧值
类中所有的方法都是static
public static void main(String[] args) {
List list = new ArrayList();
list.add(2);
list.add(3);
list.add(1);
list.add(5);
list.add(4);
// 排序操作: (均为static方法)
// reverse(List): 反转 List 中元素的顺序
Collections.reverse(list);
// shuffle(List): 对 List 集合元素进行随机排序
Collections.shuffle(list);
// sort(List): 根据元素的自然顺序对指定 List 集合元素按升序排序
Collections.sort(list);
// sort(List, Comparator): 根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
Collections.sort(list, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
Integer i1 = (Integer)o1;
Integer i2 = (Integer)o2;
return -i1.compareTo(i2);
}
});
// swap(List, int, int): 将指定 list 集合中的 i 处元素和 j 处元素进行交换
Collections.swap(list, 2, 3);
for(Object obj : list) {
System.out.println(obj);
}
}
Copy 在复制时 目标数组不能为空 而且元素的个数必须大于等于源集合的元素个数
List list1 = new ArrayList ();
list1.add("aaa");
list1.add("aaa");
list1.add("aaa");
list1.add("aaa");
list1.add("aaa");
list1.add("aaa");
list1.add("aaa");
Collections.copy(list1, list);
否则会抛出异常:
2.Collections常用方法:同步控制
Collections 类中提供了多个 synchronizedXxx() 方法,该方法可使将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题