Java集合
Java集合框架(Java Collections Framework,JCF)是为表示和操作集合而规定的一种统一的标准的体系结构。任何集合框架都包含三大块内容:对外的接口、接口的实现和对集合运算的算法。
集合与数组都是对多个数据进行存储操作的结构,简称Java容器(存储主要指内存层面,不涉及持久层)
数组存储的特点
- 初始化后,长度不可改
- 数组一旦定义好,其元素的类型也确定了
数组存储的缺点
- 一旦初始化后,长度不可改
- 数组中提供的方法非常有限,对于添加、删除、插入等操作不方便,效率较低
- 获取数组中实际元素的个数的需求,没有现成方法可用
- 对于无序、不可重复的需求,无法满足
故提出集合框架用于增强对数据的处理!
一、Collection接口
1.1 Collection 常用方法
-
contains(Object obj)方法:判断当前集合中是否包含obj
注意:Collection接口的实现类的对象中添加数据(对象)时,要求对象所在类重写equals()方法
@Test public void test1() { Collection coll = new ArrayList(); coll.add(123); coll.add(456); coll.add(new String("LEIZ")); coll.add(false); coll.add(new Person("Jerry", 20)); //contains(Object obj) 判断当前集合中是否包含obj boolean contains = coll.contains(123); System.out.println(contains); //判断的是内容,不是地址,使用equals方法 System.out.println(coll.contains(new String("JS"))); }
-
containsAll(Collection coll)方法:判断形参coll中所有元素是否都存在于当前集合
@Test public void test2() { Collection coll1 = new ArrayList(); coll1.add(123); coll1.add(456); coll1.add("Hi"); Collection coll = Arrays.asList(123, 456, "Hello"); //coll1的元素是否都存在与coll中 System.out.println(coll.containsAll(coll1)); //false }
-
remove(Object obj)方法:从当前集合中删除obj元素,成功true,失败false
@Test public void test3() { Collection coll1 = new ArrayList(); coll1.add(123); coll1.add(456); coll1.add("Hi"); Collection coll = Arrays.asList(123, 456, "Hello"); //coll1的元素是否都存在与coll中 coll1.remove("Hi"); //将Hi删除 System.out.println(coll.containsAll(coll1)); //true }
-
removeAll(Collection coll1)方法:从当前集合中移除coll1中所有的元素(差集)
@Test public void test4() { Collection coll1 = new ArrayList(); coll1.add(123); coll1.add(456); coll1.add("Hi"); Collection coll = Arrays.asList(123, 456, "Hello"); System.out.println(coll1); System.out.println(coll); coll1.removeAll(coll); System.out.println(coll1); System.out.println(coll); }
-
retainAll(Collection coll1)方法:获取当前集合和coll1集合的交集,并返回给当前集合
@Test public void test5() { Collection coll = new ArrayList(); coll.add(123); coll.add(456); coll.add("Hi"); Collection coll1 = Arrays.asList(123, 456, "Hello"); coll.retainAll(coll1); System.out.println(coll); }
-
equals(Object obj)方法:比较当前集合和形参集合元素是否相同
@Test public void test6() { Collection coll = new ArrayList(); coll.add(123); coll.add(456); Collection coll1 = Arrays.asList(456, 123); System.out.println(coll.equals(coll1)); }
-
hashCode()方法:返回当前对象的哈希值
@Test public void test7() { Collection coll = Arrays.asList("Hello", "world"); System.out.println(coll.hashCode()); //-2023748383 }
-
toArray()方法:将集合转换为数组
@Test public void test8() { Collection coll = Arrays.asList("Hello", "world"); System.out.println(coll); Object[] objects = coll.toArray(); for (Object object : objects) { System.out.print(object + " "); } }
数组 ---> 集合: Arrays.asList(new String[] {"AA", "BB", "CC"});
-
iterator():迭代器,遍历元素
1.2 iterator迭代器
Iterator对象称为迭代器(设计模式的一种),主要用于遍历Collection集合中的元素
迭代器模式:提供一种方法访问一个容器对象中各个元素,且不暴露该对象的内部细节
迭代器模式就是为容器而生!
Iterator仅用于遍历集合!集合对象每次调用iterator()方法都得到一个全新的迭代器对象
1.2.1 Iterator接口的方法
-
hasNext:如果迭代具有更多的元素,则返回true
@Test public void test3() { Collection coll = Arrays.asList("Hello", "World", "LEIZ", 123); Iterator iterator = coll.iterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); } }
-
next:返回迭代中的下一个元素。
@Test public void test2() { Collection coll = Arrays.asList("Hello", "World", "LEIZ", 123); Iterator iterator = coll.iterator(); System.out.println(iterator.next()); System.out.println(iterator.next()); System.out.println(iterator.next()); System.out.println(iterator.next()); }
-
remove:从底层集合中删除此迭代器返回的最后一个元素(可选操作)。
@Test public void test4() { List<Object> list = new ArrayList<>(); list.add(123); list.add(456); list.add("Hello"); list.add("LEIZ"); Iterator iterator = list.iterator(); while (iterator.hasNext()) { Object o = iterator.next(); if ("LEIZ".equals(o)) { iterator.remove(); } } iterator = list.iterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); } }
注意:此remove可以在遍历时,删除集合中的元素,不同于集合中直接调用remove
如果还未调用next()或在上一次调用next方法之后已经调用了remove方法,再调用remove都会报错误
1.3 iterator应用-foreach循环
- 遍历操作不需要获取Collection或者数组的长度,无需使用索引访问元素
- 遍历集合的底层调用Iterator完成操作
for (数据类型 自定义名称 : 要遍历的结构名称) {
System.out.printIn(自定义名称.getName());
}
二、Collection子接口--List接口
List集合类中元素有序,且可以重复,集合中的每个元素都有其对应的顺序索引
List接口 常用的实现类有 ArrayList、LinkedList、Vector
2.1 ArrayList
- List接口的主要实现类,线程不安全、效率高,底层使用Object[]存储
- 本质上,ArrayList是对象引用的一个“变长”数组
原码分析
JDK1.7 版本时:
ArrayList list = new ArrayList(); -- 底层创建了长度为10的Object[]数组elementData
list.add(123); -- elementData[0] = new Integer(123);
...
list.add(11); -- 若此次的添加导致底层elementData数组容量不够,则扩容
-- 默认情况下,扩容到原来的1.5倍,同时将原有数组中的数据复制到新的数组
JDK1.8 版本时:
ArrayList list = new ArrayList(); -- 底层Object[] elementData初始化{},并没有创建长度
list.add(123); -- 第一次调用add()时,底层才创建长度为10的数组,并将123添加到elementData数组
-- 后序和1.7相同
jdk7中的ArrayList的对象的创建类似于单例的饿汉式,jdk8中ArrayList的对象的创建类似于单例的懒汉式,延迟了数组的创建,节省内存。
2.2 LinkedList
- 底层使用双向链表存储,对于插入和删除,此类的效率很高
原码分析
LinkedList list = new LinkedList(); -- 内部声明了Node类型的frist和Last属性,默认值为null
lias.add(123); -- 将123 封装到Node中,创建了Node对象
Node定义为
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
2.3 Vector
- List接口的古老实现,线程安全、效率低,底层使用Object[]存储
- Vector总是比ArrayList慢,尽量避免使用
jdk1.7和1.8中通过Vector()构造器创建对象时,底层都创建了长度为10的数组,在扩容方面,默认扩容为原来的二倍
注意:三个实现类都实现了List接口、存储数据的特点是相同的,存储有序的,可重复的数据
2.4 List常用方法
2.5 常见面试题
@Test
public void test1() {
List<Object> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
updateList(list);
System.out.println(list);
}
public static void updateList(List list) {
list.remove(2);
}
注意:删除的是3,2指索引,若想直接删除元素,使用 Integer
三、Collection子接口--Set接口
Set集合不允许包含相同的元素,如果试把两个相同的元素加入同一个Set集合中,则添加失败(相同的元素只能添加一个)
Set判断两个对象是否相同不能使用 ==
运算符,而是根据 equals() 方法
Set集合存储是无序的!(无序 != 随机性,而是相对于List的无序)
3.1 HashSet
-
HashSet是Set接口的典型实现,大多数时候使用Set集合是都使用这个实现类
-
HashSet按Hash算法来存储集合中的元素,因此具有很好的存取、查找、删除性能
-
HashSet具有以下特点
- 不能保证元素的排列顺序
- 线程不安全
- 集合元素可以是null
-
HashSet集合判断两个元素相等的标准:两个对象通过hashCode()方法比较相等,并且两个对象的equals()方法返回值也相等
-
对于存放在Set容器中的对象,对应的类一定要重写equals()方法 和 hashCode(Object obj)方法,以实现对象相等规则(即:相等的对象必须具有相等的散列码)
@Test
public void test1() {
HashSet<Object> set = new HashSet<Object>();
set.add(5);
set.add(new Person("LEI",22));
set.add(4);
set.add(3);
set.add(new Person("LEI",20));
set.add(2);
set.add(new Person("LEI",21));
set.add(1);
Iterator<Object> iterator = set.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
注意:HashSet存储的数据在底层数据中并非按照数组索引的顺序添加!而是按照数据的哈希值决定的!
HashSet添加元素
-
向HashSet中添加元素 123 时,先调用元素 123 所在类的hashCode()方法,计算元素 123 的哈希值!
-
此哈希值通过某种计算方法,算出在HashSet底层数据中存放的位置(即索引位置)
-
再判断数组此位置上是否存在了元素
-
若存在,则比较哈希值,不相同则存入,若相同。进而调用equals方法,查看方法的返回值,true则添加成功,false则添加失败
jdk1.7中,将要添加的元素指向原来的元素 jdk1.8中,使原来的元素指向要添加的元素
-
若不存在,则存入
-
-
HashSet底层:数组 + 链表
hashCode与equals的重写
重写的基本原则
- 在程序运行时,同一个对象多次调用 hashCode() 方法应该返回相同的值
- 当两个对象的 equals() 方法比较返回 true 时,这两个对象的 hashCode() 方法的返回值也应相等
- 复写equals方法的时候一般都需要同时复写hashCode方法
- 通常参与计算hashCode的对象的属性也应该参与到equals()中进行计算
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Person)) return false;
Person person = (Person) o;
return age == person.age &&
Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
public static int hashCode(Object a[]) {
if (a == null)
return 0;
int result = 1;
for (Object element : a)
result = 31 * result + (element == null ? 0 : element.hashCode());
return result;
}
选择数字31的原因
- 选择系数的时候要选择尽量大的系数。因为如果计算出来的hash地址越大,所谓的 “冲突”就越少,查找起来效率也会提高(减少冲突)
- 31只占用5bits,相乘造成数据溢出的概率较小
- 31可以 由i*31== (i<<5)-1来表示,现在很多虚拟机里面都有做相关优化(提高算法效率)
3.1.1 LinkedHashSet
- 作为HashSet的子类,遍历其内部数据时,可以按照添加的顺序遍历
- 添加数据的同时,每个数据还维护了两个引用,记录此数据前一个数据和后一个数据
- 对于比较频繁的遍历操作,效率更高!
@Test
public void test2() {
Set<Object> set = new LinkedHashSet<Object>();
set.add(5);
set.add(new Person("LEI",22));
set.add(4);
set.add(3);
set.add(new Person("LEI",20));
set.add(2);
set.add(new Person("LEI",21));
set.add(1);
Iterator<Object> iterator = set.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
3.2 TreeSet
-
有序,查询比List快,底层使用红黑树实现
-
可以按照添加对象的指定属性,进行排序
-
向TreeSet中添加的数据,要求是相同类的对象
默认按照从小到大排序
@Test
public void test3() {
Set<Integer> set1 = new TreeSet<>();
set1.add(2);
set1.add(5);
set1.add(0);
for(Integer o: set1) {
System.out.println(o);
}
}
3.2.1 自然排序
实现Comparable接口,implements Comparable
//按照年龄升序排序
@Override
public int compareTo(Object o) {
if(o instanceof Person) {
Person person = (Person)o;
return Integer.compare(this.age, person.age);
}else {
throw new RuntimeException("输入的类型不匹配");
}
}
@Test
public void test4() {
Set<Person> set1 = new TreeSet<>();
set1.add(new Person("LEI",10));
set1.add(new Person("LEI1",11));
set1.add(new Person("LEI2",9));
for(Person o: set1) {
System.out.println(o);
}
}
按姓名排序,名字重复继续按照年龄排序
//按照年龄升序排序
@Override
public int compareTo(Object o) {
if(o instanceof Person) {
Person person = (Person)o;
int compare = this.name.compareTo(person.name);
if(compare != 0) {
return compare;
}else {
return Integer.compare(this.age, person.age);
}
}else {
throw new RuntimeException("输入的类型不匹配");
}
}
3.2.2 定制排序
Comparator
@Test
public void test5() {
Comparator<Person> com = (o1, o2) -> {
if(o1 != null && o2 != null) {
return Integer.compare(o1.getAge(), o2.getAge());
}else {
throw new RuntimeException("输入的数据类型不匹配");
}
};
Set<Person> set1 = new TreeSet<>(com);
set1.add(new Person("LEI",10));
set1.add(new Person("LEI1",11));
set1.add(new Person("LEI2",9));
set1.add(new Person("LEI2",8));
for(Person o: set1) {
System.out.println(o);
}
}
四、Map接口
Map接口与Collection并列存在。用于保存具有映射关系的数据:key - value,其中 key 与 value 都可以是任何引用类型的数据
Map中的 key 使用 Set 存放,不允许重复!即同一个Map对象所对应的类,必须重写hashCode()和eauals()方法,一般常用String类作为Map的“键”
Key 和 value 之间存在单向一对一关系,即通过指定的 key 总能找到唯一的、确定的value
Map接口的常用实现类:HashMap、TreeMap、LinkedHashMap、Properties
4.1 Map接口的常用方法
- 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是否相
- Set keySet():返回所有key构成的Set集合
- Collection values():返回所有value构成的Collection集合
- Set entrySet():返回所有key-value对构成的Set集合
@Test
public void test1() {
Map<String, Object> map = new HashMap<>();
map.put("No1" , new User(1, "LEIZ"));
map.put("No2" , new User(2, "JSSB"));
map.put("No3" , new User(3, "JS2B"));
map.put("No4" , new User(4, "JSSSB"));
System.out.println(map);
Map<String, Object> map1 = new HashMap<>();
map1.putAll(map);
//以上可以替换为 -- Map<String, Object> map1 = new HashMap<>(map);
System.out.println(map1);
map.remove("No4", new User(4, "JSSSB"));
System.out.println(map);
System.out.println(map.values());
System.out.println(map.keySet());
}
4.2 Map结构的理解
- Map 中的key:无序,不可以重复,使用Set存储所有的 key(需重写两个方法)
- Map 中的value:无序,可重复,使用Collection存储所有的 value
- key - value 构成了 一个 Entry 对象
- Entry 是无序的、不可以重复的,使用Set 存储所有的 Entry
五、Map实现类--HashMap
- HashMap是Map接口使用频率最高的是西安类
- 允许使用null键和null值,与HashSet一样,不保证映射的顺序
- HashMap 判断两个 key 相等的标准:两个 key 通过 equals() 方法返回 true, hashCode 值也相等。
- HashMap 判断两个 value相等的标准:两个 value 通过 equals() 方法返回 true
@Test
public void test1() {
Map<Integer, String> map = new HashMap<>();
map.put(1,"jssb");
map.put(2,"jssb1");
map.put(3,"jssb2");
map.put(4,"jssb3");
map.put(6,"jssb6");
map.put(5,"jssb5");
Set<Map.Entry<Integer, String>> set = map.entrySet();
for (Map.Entry<Integer, String> integerStringEntry : set) {
System.out.println(integerStringEntry);
}
}
5.1 HashMap存储结构
JDK 7 之前的版本:数组 + 链表
JDK 8以后:数组 + 链表 + 红黑树
5.2 HashMap的底层实现原理
-
HashMap原码中重要的常量
-
DEFAULT_INITIAL_CAPACITY : HashMap的默认容量,16
-
MAXIMUM_CAPACITY : HashMap的最大支持容量,2^30
-
DEFAULT_LOAD_FACTOR:HashMap的默认加载因子
-
**TREEIFY_THRESHOLD:Bucket中链表长度大于该默认值,转化为红黑树 **
-
**UNTREEIFY_THRESHOLD:Bucket中红黑树存储的Node小于该默认值,转化为链表 **
-
MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量。(当桶中Node的 数量大到需要变红黑树时,若hash表容量小于MIN_TREEIFY_CAPACITY时,此时应执行 resize扩容操作这个MIN_TREEIFY_CAPACITY的值至少是TREEIFY_THRESHOLD的4 倍。)
-
table:存储元素的数组,总是2的n次幂 entrySet:存储具体元素的集
-
**size:HashMap中存储的键值对的数量 **
-
**modCount:HashMap扩容和结构改变的次数。 **
-
threshold:扩容的临界值,=容量*填充因子
-
**loadFactor:填充因子 **
5.2.1 底层实现过程
Map map = new HashMap(); -- 实例化以后,底层创建了长度是16的一维数组Entry[] table
... 执行put ...
map.put(key1, value1); -- 首先调用key1所在类的HashCode()计算key1哈希值,此哈希值经过某种算法
计算后,得到Entry 数组中的存放位置,若此位置的数据为空,则 key1 - value 直接添加成功。(情况一)
若此位置不为空,则意味着此位置有数据(可能为多个,以链表形式存在)比较 key1和已经存在的一个或多个数据的哈希值
若key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1
则添加成功(情况二)
若key1的哈希值和 某一个 数据的哈希值相同了,继续比较eauals方法,
继续比较
若equals返回false,意味着还是不同的,添加成功!(情况三)
若返回true,则一模一样,使用value1去替换相同key的value值
-- 对于情况二、三 :此时key1 - value1 和原来的数据以链表的形式存储
-- 在不断的添加过程中,会涉及到扩容问题,默认的扩容方式:扩容为原来容量的2倍,并将原有的数据复制过来
-- JDK8相较于JDK7在底层实现方面的不同:
1.new HashCode():底层没有创建一个长度为16的数组
2.JDK8底层的数组是Node[],并不是Entry[]
3.首次调用put()方法时,底层去创建长度为16的数组
4.JDK7底层结构只有数组+链表,8出现红黑树
5.当某一个索引位置上的元素以链表的形式存在的数据个数 > 8,且当前数组的长度 > 64时,此时索引位置上的所有
数据更改为使用红黑树存储
5.2.2 扩容
JDK7版本
HashMap的内部存储结构其实是数组和链表的结合。当实例化一个HashMap时, 系统会创建一个长度为Capacity的Entry数组,这个长度在哈希表中被称为容量 (Capacity),在这个数组中可以存放元素的位置我们称之为“桶”(bucket),每个 bucket都有自己的索引,系统可以根据索引快速的查找bucket中的元素。
每个bucket中存储一个元素,即一个Entry对象,但每一个Entry对象可以带一个引 用变量,用于指向下一个元素,因此,在一个桶中,就有可能生成一个Entry链。 而且新添加的元素作为链表的head
当HashMap中的元素越来越多的时候,hash冲突的几率也就越来越高,因为数组的 长度是固定的。所以为了提高查询的效率,就要对HashMap的数组进行扩容,而在 HashMap数组扩容之后,最消耗性能的点就出现了:原数组中的数据必须重新计算 其在新数组中的位置,并放进去,这就是resize。
当HashMap中的元素个数超过数组大小(数组总大小length,不是数组中个数 size)*loadFactor 时 , 就会进行数组扩容
loadFactor 的默认值 (DEFAULT_LOAD_FACTOR)为0.75,这是一个折中的取值。也就是说,默认情况 下,数组大小(DEFAULT_INITIAL_CAPACITY)为16,那么当HashMap中元素个数 超过160.75=12(这个值就是代码中的threshold值,也叫做临界值)的时候,就把 数组的大小扩展为 216=32,即扩大一倍,然后重新计算每个元素在数组中的位置, 而这是一个非常消耗性能的操作,所以如果我们已经预知HashMap中元素的个数, 那么预设元素的个数能够有效的提高HashMap的性能。
JDK8版本
HashMap的内部存储结构其实是数组+链表+树 的结合。当实例化一个 HashMap时,会初始化initialCapacity和loadFactor,在put第一对映射关系 时,系统会创建一个长度为initialCapacity的Node数组,这个长度在哈希表 中被称为容量(Capacity),在这个数组中可以存放元素的位置我们称之为 “桶”(bucket),每个bucket都有自己的索引,系统可以根据索引快速的查 找bucket中的元素。
每个bucket中存储一个元素,即一个Node对象,但每一个Node对象可以带 一个引用变量next,用于指向下一个元素,因此,在一个桶中,就有可能 生成一个Node链。也可能是一个一个TreeNode对象,每一个TreeNode对象 可以有两个叶子结点left和right,因此,在一个桶中,就有可能生成一个 TreeNode树。而新添加的元素作为链表的last,或树的叶子结点。
当HashMap中的元素个数超过数组大小(数组总大小length,不是数组中个数 size)loadFactor 时 , 就会进行数组扩容, loadFactor 的默认值 (DEFAULT_LOAD_FACTOR)为0.75,这是一个折中的取值。也就是说,默认 情况下,数组大小(DEFAULT_INITIAL_CAPACITY)为16,那么当HashMap中 元素个数超过160.75=12(这个值就是代码中的threshold值,也叫做临界值) 的时候,就把数组的大小扩展为 216=32,即扩大一倍,然后重新计算每个元 素在数组中的位置,而这是一个非常消耗性能的操作,所以如果我们已经预知 HashMap中元素的个数,那么预设元素的个数能够有效的提高HashMap的性能。
HashMap中的其中一个链的对象个数如果达到了8个,此时如果capacity没有 达到64,那么HashMap会先扩容解决,如果已经达到了64,那么这个链会变成 树,结点类型由Node变成TreeNode类型。当然,如果当映射关系被移除后, 下次resize方法时判断树的结点个数低于6个,也会把树再转为链表。
映射关系的key是否可以修改?answer:不要修改
映射关系存储到HashMap中会存储key的hash值,这样就不用在每次查找时重新计算 每一个Entry或Node(TreeNode)的hash值了,因此如果已经put到Map中的映射关 系,再修改key的属性,而这个属性又参与hashcode值的计算,那么会导致匹配不上。
5.3 总结(JDK8的变化)
- HashMap map = new HashMap();//默认情况下,先不创建长度为16的数组
- 当首次调用map.put()时,再创建长度为16的数组
- **数组为Node类型,在jdk7中称为Entry类型 **
- **形成链表结构时,新添加的key-value对在链表的尾部(七上八下) **
- 当数组指定索引位置的链表长度>8时,且map中的数组的长度> 64时,此索引位置 上的所有key-value对使用红黑树进行存储
5.3 常见问题
负载因子值的大小,对HashMap有什么影响?
- 负载因子的大小决定了HashMap的数据密度
- 负载因子越大密度越大,发生碰撞的几率越高,数组中的链表越容易长, 造成查询或插入时的比较次数增多,性能会下降
- 负载因子越小,就越容易触发扩容,数据密度也越小,意味着发生碰撞的 几率越小,数组中的链表也就越短,查询和插入时比较的次数也越小,性能会更高。但是会浪费一定的内容空间。而且经常扩容也会影响性能,建议初始化预设大一点的空间
- 按照其他语言的参考及研究经验,会考虑将负载因子设置为0.7~0.75,此 时平均检索长度接近于常数
六、Map其他实现类
6.1 LinkedHashMap
- LinkedHashMap 是 HashMap 的子类
- 在HashMap存储结构的基础上,使用了一对双向链表来记录添加元素的顺序
- 与LinkedHashSet类似,LinkedHashMap可以维护 Map 的迭代顺序:迭代顺序与 Key-Value 对的插入顺序一致
@Test
public void test2() {
Map<Integer, String> map = new LinkedHashMap<>();
map.put(1,"jssb");
map.put(2,"jssb1");
map.put(3,"jssb2");
map.put(4,"jssb3");
map.put(6,"jssb6");
map.put(5,"jssb5");
Set<Map.Entry<Integer, String>> set = map.entrySet();
for (Map.Entry<Integer, String> integerStringEntry : set) {
System.out.println(integerStringEntry);
}
}
6.2 TreeMap
- TreeMap存储 Key-Value 对时,需要根据 key-value 对进行排序,其可以保证所有的 Key-Value 对处于有序状态
- TreeMap 的 Key 的排序
- 自然排序::TreeMap 的所有的 Key 必须实现 Comparable 接口,而且所有 的 Key 应该是同一个类的对象,否则将会抛出 ClasssCastException
- 定制排序:创建 TreeMap 时,传入一个 Comparator 对象,该对象负责对 TreeMap 中的所有 key 进行排序。此时不需要 Map 的 Key 实现 Comparable 接口
- TreeMap判断两个key相等的标准:两个key通过compareTo()方法或 者compare()方法返回0
方式一、继承Comparable类
@Override
public int compareTo(Object o) {
if(o instanceof User) {
User user = (User)o;
int compare = this.name.compareTo(user.name);
if(compare != 0) {
return compare;
}else {
return Integer.compare(this.age,user.age);
}
}else {
throw new RuntimeException("输入类型不匹配");
}
}
@Test
public void test3() {
Map<Object, Integer> map = new TreeMap<>();
User user1 = new User("zl1", 22);
User user2 = new User("zl1", 13);
User user3 = new User("zl2", 14);
User user4 = new User("zl3", 25);
map.put(user1, 98);
map.put(user2, 89);
map.put(user3, 76);
map.put(user4, 100);
Set<Map.Entry<Object, Integer>> set = map.entrySet();
for (Map.Entry<Object, Integer> next : set) {
System.out.println(next.getKey() + "----->" + next.getValue());
}
}
方式二、定制排序
@Test
public void test4() {
Map<Object, Integer> map = new TreeMap<>(new Comparator<Object>() {
@Override
public int compare(Object o1, Object o2) {
if(o1 instanceof User && o2 instanceof User) {
User user = (User)o1;
User user2= (User)o2;
return Integer.compare(user.getAge(),user2.getAge());
}
throw new RuntimeException("输入的类型不匹配!");
}
});
User user1 = new User("zl1", 22);
User user2 = new User("zl1", 13);
User user3 = new User("zl2", 14);
User user4 = new User("zl3", 25);
map.put(user1, 98);
map.put(user2, 89);
map.put(user3, 76);
map.put(user4, 100);
Set<Map.Entry<Object, Integer>> set = map.entrySet();
for (Map.Entry<Object, Integer> next : set) {
System.out.println(next.getKey() + "----->" + next.getValue());
}
}
6.3 Hashtable
- Hashtable是个古老的 Map 实现类,JDK1.0就提供了。不同于HashMap, Hashtable是线程安全的
- Hashtable实现原理和HashMap相同,功能相同。底层都使用哈希表结构,查询速度快,很多情况下可以互用
- 与HashMap不同,Hashtable 不允许使用 null 作为 key 和 value
- 与HashMap一样,Hashtable 也不能保证其中 Key-Value 对的顺序
- Hashtable判断两个key相等、两个value相等的标准,与HashMap一致
6.4 Properties
- Properties 类是 Hashtable 的子类,该对象用于处理属性文件
- 由于属性文件里的 key、value 都是字符串类型,所以 Properties 里的 key 和 value 都是字符串类型
- 存取数据时,建议使用setProperty(String key,String value)方法和 getProperty(String key)方法
public class PropertiesTest {
public static void main(String[] args) throws Exception {
Properties pros = new Properties();
FileInputStream fis = new FileInputStream("db.properties");
pros.load(fis); //加载流文件
String user = pros.getProperty("username");
String pass = pros.getProperty("password");
System.out.println("username = " + user + ", password = " + pass);
}
}
七、Collections工具类
Collections 是一个操作 Set、List 和 Map 等集合的工具类
Collections 中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作, 还提供了对集合对象设置不可变、对集合对象实现同步控制等方法