系列文章目录
文章目录
前言
本文介绍了Java中常用的集合框架HashMap,本文为系列文章也是本系列最后一篇,如果喜欢的话,可以点赞,收藏,关注。
一、Map
1.Map类型的特点
Java中的Map是一种接口类型,它定义了一种将键映射到值的数据结构。Map中的元素是通过键值对来存储和访问的。以下是Map类型的主要特点:
- 键的唯一性:在Map中,每个键只能对应一个值。如果尝试使用相同的键插入多个值,那么后续的插入操作将覆盖先前的值。
- 键和值的存储:Map中的键和值可以是任何类型的对象,包括自定义的对象类型。键和值的存储是相互独立的,即键的类型与值的类型可以是不同的类型。
- 键的有序性:在Java中,Map的内部实现有多种方式,如HashMap、LinkedHashMap等。不同的实现方式对键的存储顺序可能会有所不同。例如,HashMap不保证键的顺序,而LinkedHashMap则按照键的插入顺序进行排序。
- Map的遍历:可以通过迭代器(Iterator)来遍历Map中的所有元素。遍历时,元素的顺序可能与插入顺序不同,具体取决于Map的实现方式。
- Map的操作:Map提供了一些常用的操作方法,如put(key, value)用于插入或更新元素,get(key)用于根据键获取对应的值,remove(key)用于删除指定键对应的元素等。
- Map的实现类:Java提供了多个Map的实现类,如HashMap、LinkedHashMap、TreeMap等。这些实现类具有不同的特点和用途,可以根据实际需求选择合适的实现方式。
2.HashMap基本实现原理
- 数据存储:HashMap内部采用数组+链表(Java 8后为数组+树)的方式存储数据。数组是HashMap的基础,每个数组元素就是一个Node对象,包含三个部分:key、value和next指针。
- 散列映射:当我们将键值对存入HashMap时,首先会对键进行散列(hash)操作,得到一个散列值,这个散列值对应数组的某个位置,然后将该键值对存入该位置的Node中。
- 解决冲突:由于不同的键可能会得到相同的散列值,因此可能会产生冲突。HashMap通过链表的方式解决这个问题。当两个或多个键得到相同的散列值时,它们会被存放在同一个链表中。在查询时,如果根据key得到的散列值相同,就会在对应的链表中按照next指针顺序查找。
- 树的构建(Java 8及以后版本): 当链表的长度大于一定阈值(默认为8)时,会将链表转换为红黑树(一种自平衡的二叉搜索树)。树的节点数总是大于等于链表的节点数,所以查找、插入、删除在树上的操作时间复杂度是O(log n)。
- 树的合并(Java 8及以后版本): 当红黑树的节点数小于等于6时,会将红黑树转回为链表。
3.HashMap案例
public class HashMapTest {
public static void main(String[] args) {
//双列,键值结构
//key不可重复,value可重复
Map hashMap = new HashMap<>();
hashMap.put("a", "注意看这个");
hashMap.put("b", "桃子");
hashMap.put("c", "梨");
hashMap.put("d", "芒果");
hashMap.put("a", "被修改了");//修改
System.out.println("长度为:"+hashMap.size());
//拿到hashMap中所有的key
Set set = hashMap.keySet();
for (Object o : set) {
System.out.println(o+":"+hashMap.get(o));
}
}
}
/**
长度为:4
a:被修改了
b:桃子
c:梨
d:芒果
*/
因为Set是无序的,取到的key也是无序的,所以打印出来也是无序的
4.Map常用方法
代码示例:
public class HashMapTest {
public static void main(String[] args) {
Map hashMap = new HashMap<>();
hashMap.put("a", "注意看这个");
hashMap.put("b", "桃子");
hashMap.put("c", "梨");
hashMap.put("d", "芒果");
hashMap.put("a", "被修改了");
System.out.println("长度为:"+hashMap.size());//获取长度
System.out.println(hashMap.get("a"));//通过key取value
System.out.println(hashMap.containsKey("b"));//查询hashMap中是否存在某个键
System.out.println(hashMap.remove("c"));//移除键,返回对应的value
System.out.println(hashMap.keySet());//返回一个存有key的set
System.out.println(hashMap.values());//返回一个存有value的collection
hashMap.clear();//清空hashMap
System.out.println("长度为:"+hashMap.size());
}
}
/**
长度为:4
被修改了
true
梨
[a, b, d]
[被修改了, 桃子, 芒果]
长度为:0
*/
二、泛型在集合中的应用
1.为什么使用泛型
在上述代码例子中,key和value可以添加任意数据类型的值,会出现一些类型不匹配和强制类型转换的问题。
使用泛型还可以提高代码的灵活性和可重用性。假设我们有一个程序需要处理不同类型的数据,如果使用Object类型的键和值,则需要使用强制类型转换来获取具体类型的对象。而使用泛型,我们可以在定义List,Set,Map集合时指定具体的类型,使得代码更加清晰和易于维护。
代码示例(以List举例,未使用泛型):
public class GenericTest {
public static void main(String[] args) {
List list=new ArrayList();
list.add(new Dog("a","a"));
list.add(1);
list.add("test");
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
}
/**
Dog{name='a', type='a'}
1
test
*/
代码可读性极低,当数据量极大时,需要取list中Dog类型的数据进行操作,需要先使用到instanceof关键字判断是否为Dog类型,然后再进行操作
2.泛型入门
public class GenericTest<T> {
T a;
public void show(T a) {
System.out.println(a);
}
public static void main(String[] args) {
GenericTest objectGenericTest = new GenericTest();
objectGenericTest.show("a");
objectGenericTest.show(123);
}
}
使用泛型可以传入不同数据类型的数据,也可以指定传入的数据类型。
指定传入的数据类型
public class GenericTest<T> {
T a;
public void show(T a) {
System.out.println(a);
}
public static void main(String[] args) {
GenericTest<String> objectGenericTest = new GenericTest<String>();
objectGenericTest.show("a");
}
}
指定传入的数据类型不能是基础数据类型,比如指定传入整型,就只能使用int类型的包装类型Integer
3.在Map中使用泛型
在代码中插入一条非String类型的数据
所以,在使用泛型时申明了传入的数据类型,便只能传入对应的类型。
提示
Map<String, String> hashMap = new HashMap<String, String>();//后面的那个<>可以省略
Map<String, String> hashMap = new HashMap();//可以写成这样,但是只能是jdk6以上的版本才能省略
补充
迭代器Iterator
==增强型for循环(语法糖)==的底层都是靠迭代器实现,其原理大致为:
- 对于数组:增强for循环会隐式地创建一个ArrayIterator,该ArrayIterator负责遍历数组并返回数组的每个元素。对于基本类型的数组,这个ArrayIterator是类型特定的,即每个类型都会有一个对应的ArrayIterator类。
- 对于集合(Collection):增强for循环会隐式地创建一个迭代器,该迭代器会遍历集合并返回集合的每个元素。对于实现了Iterable接口的集合类(如List,Set等),增强for循环可以直接使用Iterable接口的iterator方法获取一个迭代器。
Iterator使用案例
public class IteratorTest {
public static void main(String[] args) {
Set<Integer> set = new HashSet<Integer>();
set.add(1);set.add(2);set.add(3);set.add(4);
Iterator<Integer> iterator = set.iterator();
while (iterator.hasNext()) {//判断是否存在下一个值
System.out.println(iterator.next());//获取迭代器中的下一个元素
}
}
}
/*
1
2
3
4
**/
Collections集合框架工具类
public class CollectionsUtilTest {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
for (int i = 0; i <= 54; i++) {
list.add(i);//生成了一个0到54的列表
}
System.out.println(list);//列表初始状态
Collections.addAll(list,55,56);//追加元素
System.out.println(list);
Collections.reverse(list);//将整个列表反向
System.out.println(list);
Collections.shuffle(list);//类似洗牌的操作,将整个列表打乱
System.out.println(list);
}
}
本节主要介绍了三个工具方法,主要用于列表中的操作,更多方法可以查看官方文档,搜索Collections。
总结
Java中的Map接口为开发人员提供了方便而强大的数据结构,可以有效地存储和操作键值对数据。它的特性使得Map在开发中具有重要的影响,可以简化数据操作,提高代码的可读性和可维护性。