[Java基础复习] Collection和Map(Java类集)

本文深入探讨Java集合框架,包括Collection和Map的主要实现形式,如List、Set、HashMap等,并详细解析了它们的数据结构、特点及应用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

开头先强调一下,Collection和Map才是同级的!List和Set是Collection子类,很多人误认为他们和Map同级!下面放上java类集结构图:
在这里插入图片描述

Collection

Collection 分为Set和List,下面将从这两个部分介绍。主要内容面向细节和实现,还有针对面试的突击,常用方法请查看JDK api.chm或者百度

List

List下的子类有ArrayList, Vector, LinkedList,它们的使用率是94%, 4%, 1%。

ArrayList和Vector非常像,主要区别是Vector采用了同步处理,因此多线程安全,但是效率较低他们俩的底层实现都是数组!!!所以在内存中是连续存放的,因此查询的时间复杂度是O(1),但是因为是基于数组的实现,删除的时间复杂度是O(n)。正常尾部增加是O(1),如果要resize的话是O(n)。

resize机制

1.ArrayList

首先看一下构造方法:
在这里插入图片描述
Arraylist如果是无参情况那么初始容量0,但是一旦进行存储的话会创建一个初始长度为10的数组每次超出初始容量就会创建一个扩增1.5倍大小的数组,然后把原数组赋值进来

它的做法是采用位运算,将原长度+原长度右移一位

int newCapacity = oldCapacity + (oldCapacity >> 1);

也可以用Arraylist(int size)初始化一个有长度的al,这样在明确需要存大量数据且大致懂得一个范围的情况下可以大大减小扩容时间。

还有一个构造方法是Arraylist(Collection<? extends E> c),就是传入的参数是collection下面实现的方法,可以自动转换成al。

2.Vector

首先看一下构造方法:
在这里插入图片描述
构造方法大致类似,初始size也是10,但是每次resize扩容是原来的一倍
Vector还有一个构造方法,就是Vector(int initialCapacity, int increment).第一个参数是初始大小,第二个是每次增加的量。源码里写如果设置的扩容(increment)<=0,它会变成默认扩容而不是一个定长数组。如Vector(10, 0),Vector(10, -1),resize以后都会变成20。

LinkedList

LinkedList就是用链表实现的,每个数据都是用节点保存,在内存里随意放置,因此并不是连续的。它们的连续全靠节点内的指向下一个节点的attribute来寻找。查找的时间复杂度是O(n)。添加是O(1),删除如果是当前指针是O(1),如果要寻找则是O(n)。

Set

Set是接口,最大的特点是:Set中所有的元素不能重复

HashSet

HashSet是采用散列表来保存(同HashMap),随意存储位置并不是顺序的,而是无序的。同时它的对象尽量不要存可变对象,比如指定了一个可变的类,如果贸然进行修改可能会导致resize的时候出错或者存了重复的或者无法找到。
因为hs是散列表。hs的实现是来自于map,或者说map produces set。set就相当于用map的key,存统一的v。
将array转成set的方法直接用遍历(我找了很多其他的方法,但是都不是很好用,有没大哥知道的告诉我一下为什么?)

public Set<Integer> transform(int[] arr){
	HashSet<Integer> set = new HashSet<Integer>();
	for (Integer n : arr) set.add(n);
	return set;
}

//这个用不了不知道为什么
Set<T> mySet = new HashSet<>(Arrays.asList(someArray));
TreeSet

与 HashSet 不同的是,TreeSet 本身属于排序的子类。它的实现是红黑树而不是散列表,在效率上不如HashSet。他的添加是用compareTo进行比较排序。
如果存储的不是基础类型或者String,那么需要自定义去实现comparable< E >接口。这里定义的E就是告诉comparable接口,我要定义比较的类是什么,然后在这个类里override(重写)compareTo方法

public class Person implements Comparable<Person> {
	private String name;
	private int age; 
	
	// 主要重点部分:compareTo的实现
	// 这里调用compareTo比较的是Person里的age
	public int compareTo(Person per) {
 		if (this.age > per.age) { return 1; } 
  		else if (this.age < per.age) { return -1; } 
  		else { return 0; } 
 	}
 	
 	public Person() { };
 	public Person(String name, int age) { 
 		this.name = name; 
 		this.age = age; 
 	}
}
Set小结
  1. 关于 TreeSet 的排序实现,如果是集合中对象是自定义的或者说其他系统定义的类没有实现 Comparable 接口,则不能实现 TreeSet 的排序,会报类型转换(转向 Comparable 接口)错误。 换句话说要添加到 TreeSet 集合中的对象的类型必须实现了 Comparable 接口。 不过 TreeSet 的集合因为借用了 Comparable 接口,同时可以去除重复值。
  2. 而 HashSet 虽然是 Set 接口子类,但是对于没有复写 Object 的 equals 和 hashCode 方法的对象,加入了 HashSet 集合中也是不能去掉重复值的。

集合输出

这里补充一个很好用的东西,迭代器。但是它只能迭代Collection的子类
使用率:Iterator 迭代输出(90%)、ListIterator(5%)、Enumeration(1%)、foreach(4%)。如果碰到了集合,则输出的时候尽量使用 Iterator 进行输出
这个其实很简单, 但是我以前一直以为很难没去接触。首先要使用它必须先创建一个对象
Iterator iterator = new data.Iterator();
同理ListIterator iterator = new data.ListIterator();
主要的方法也就那几个.hasNext(), .next(), .remove()

ListIterator相比于Iterator多了:

  1. hasPrevious(); 向上操作,
  2. add(e);添加元素到指针当前所在位置与下一个元素之间,比如指针在-1,下一个元素是0,那么插入的元素会在-1和0之间。也就是说新元素会在0的位置,0会变成1的位置,指针指向的位置不变
  3. set(Element e),将当前指针指向的位置设置成e;
  4. .previous() //这里注意一下previous和next都会在执行完语句之后,返回当前指针所指的元素

注意,迭代器最开始的指针位置都在0之上,可以认为是-1,所以要先next()才能到0。初始内存图如下:

在这里插入图片描述
一般可以用这个来遍历Set,写法如下:

1.迭代器Iterator遍历:  
Set<String> set = new HashSet<String>();  
Iterator<String> it = set.iterator();  
while (it.hasNext()) {  
  String str = it.next();  //这是因为一开始指针在-1位置
  System.out.println(str);  
}  
  
2.for循环遍历:  
for (String str : set) {  
      System.out.println(str);  
}  

Map

HashMap< K, V >

Key就是一个Set,所以存储的时候不允许重复,会覆盖先前值。但是允许为null。存放也是属于散列表,所以是无序存放。一般用keySet()获取key,然后循环遍历。常用的方法put,remove就不提了,主要讲一下底层和resize。HashMap存放对象的方式是先计算出对象的hashCode, 然后使用hashCode % HashMap.size()

resize

当数组长度为2的n次幂的时候,不同的key算得得index相同的几率较小,那么数据在数组上分布就比较均匀,也就是说碰撞的几率小。相对的,查询的时候就不用遍历某个位置上的链表,这样查询效率也就较高了。所以初始大小不是10,也不是20,而是16(2^4)。
hashmap的默认扩容机制是当达到散列因子0.75时(如果是16,那么容量一旦达到12就会扩容),那么会把数据全部取出来,创建一个大小为原来两倍的hashmap,然后重新储存。因此位置可能会重新散乱发生变化,因此也叫散列表。

为什么负载因子是0.75呢?
document解释:负载因子是0.75的时候,空间利用率比较高,而且避免了相当多的Hash冲突,使得底层的链表或者是红黑树的高度比较低,提升了空间效率。0.75是最折衷的方法,他在空间利用率高的情况下尽可能的减少了查找的时间

遍历方式(迭代器和foreach)

1. 迭代器

  Map map = new HashMap();
  Iterator iter = map.entrySet().iterator();
  while (iter.hasNext()) {
	  Map.Entry entry = (Map.Entry) iter.next();
	  Object key = entry.getKey();
	  Object val = entry.getValue();
  }

第二种(迭代keySet,然后用get(key)取value)

  Map map = new HashMap();
  Iterator iter = map.keySet().iterator();
  while (iter.hasNext()) {
	  Object key = iter.next();
	  Object val = map.get(key);
  }

2.foreach

  1.get(key)
	Map<String, String> map = new HashMap<String, String>();
	for (String key : map.keySet()) {
		map.get(key);
	}

  2. map.entry()
	Map<String, String> map = new HashMap<String, String>();
	for (Entry<String, String> entry : map.entrySet()) {
		entry.getKey();
		entry.getValue();
	}

HashTable< K, V >

多线程安全,但是采用的方法是对整个HashTable所以效率低下。Key不允许为空,会出现空指针异常。

Properties

Properties是继承自HashTable的一个持久的属性集。属性列表中每个键及其对应值都是一个字符串。也可以说是<K, V> = <String, String> Properties 类被许多 Java 类使用。例如,在获取环境变量时它就作为 System.getProperties() 方法的返回值。

ConcurrentHashMap

多线程安全,Key不允许为空,会出现空指针异常。跟HashTable主要的区别是采用了分部锁。这里举一个例子,加入有三个模块0,1,2,他们分别有不同的锁。比如A要访问1,B要访问0,这样其实可以直接访问,因为他们不会冲突,这样就提高效率了。但是如果同时访问1的话还是要排队的。

JDK 1.9新特性

JDK9新特性,允许且仅允许Map, List, Set这三个类(不包括其子类)创建固定的small size容器
用class.of( e1, e2, … e10) 最多10个,但是如果是set和map不保证顺序
class.of(k1, v1, k2, v2…)注意这里全部用逗号,map会自动分辨
例如
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值