数组和集合
数组的特点
- 数组中保存的元素都是有序的,可以通过下标快速访问
- 数组中保存的数据都是同一类型
- 数组的长度在定义后,无法改变
- 数组无法获取其中元素的实际数量
集合的特点
- 能保存一组数据,可以有序也可以无序
- 集合的容量可变
- 集合中可以保存不同类型的数据
- 可以获取集合中保存的元素实际数量
集合框架(集合家族)
Collection还有父接口Iterable,但Iterable接口不算严格意义上的集合的根接口。它称为迭代器,是用于遍历集合元素的一个工具接口。
所以集合的根接口为Collect接口和Map接口,位于java.util包中。
以上所有实现类都是非线程安全的,在多线程的环境下使用以上任意集合,都会产生不确定的效果。
Collection接口
该接口有两个核心子接口:List和Set。
这两个接口都可以保存一族元素,List接口保存元素时,是有序可重复的,Set接口保存元素时,是无需不重复的。
常用方法 | 返回值 | 作用 |
---|---|---|
add(Object obj) | boolean | 将元素添加到集合中 |
size() | int | 获取集合中的元素数量 |
isEmpty() | boolean | 判断集合是否为空 |
clear() | void | 清空集合 |
contains(Object obj) | boolean | 判断集合中是否存在指定元素 |
remove(Object obj) | boolean | 移除集合中得指定元素 |
toArray | Object[] | 将集合转换为数组 |
Iterator() | Iterator | 获取集合的迭代器对象,用于遍历集合 |
List接口(有序可重复)
有序集合,元素可以重复,允许保存null,可以通过索引获取对应位置上的元素。
在该接口继承Collection接口的同时,又拓展了一些操作元素的方法,如添加到指定索引、根据索引删除、获取指定索引的元素、截取子集合的方法等。
常用方法 | 返回值 | 作用 |
---|---|---|
get(int index) | Object | 根据指定索引获取对应元素 |
set(int index,Object obj) | Object | 使用obj替换index上的元素,返回被替换的元素 |
add(int index,Object obj) | void | 将obj添加到index上 |
remove(int index) | Object | 移除指定索引的元素 |
indexOf(Object obj) | int | 得到某元素第一次出现的索引,没有返回-1 |
lastIndexOf(Object obj) | int | 得到某元素最后一次出现的索引,没有返回-1 |
subList(int from,int to) | List | 截取[from,to)区间内的元素,返回子集合 |
ArrayList实现类(掌握)
- 采用数组实现的集合
- 可以通过索引访问元素,可以改变集合的大小,如果要往其中插入或删除元素时,会影响后续元素
- 该集合中保存的都是引用类型,即便保存了数字123,也保存的是Integer类型的123,而不是int类型的123
- 该集合查询效率高,中途增加和删除元素效率低
构造方法
常用构造方法 | 说明 |
---|---|
ArrayList() | 创建一个Object类型的空数组。在调用添加方法后,才会更改该数组大小为10 |
ArrayList(int initialCapacity) | 创建一个指定容量的Object数组,如果参数为负,会抛出illegalArgumentException异常。 |
常用方法
ArrayList中的常用方法,就是Collection接口和List接口中的方法都有对应的实现。
LinkedList实现类
- 采用双向链表实现的集合
- 集合中保存的每个元素也称为节点,除了首尾节点外,其余节点都保存了自己的信息外,还保存了其前一个和后一个节点的地址
- 如果在双向链表的数据结构中插入和删除操作节点时,不会影响其它节点的位置,如添加时,只需重新定义新节点的前后位置即可。
- 如果要查询某个节点时,需要从头结点或尾结点开始一步步得到目标节点的位置
- 双向链表在中间插入和删除的效率高,随机读取的效率低
构造方法
常用构造方法 | 说明 |
---|---|
LinkedList() | 创建一个空链表 |
常用方法
由于LinkedList既实现了List接口,有实现了Deque接口,所以还有Deque接口的一些方法。
实现Deque接口的方法 | 说明 |
---|---|
addFirst(Object obj) | 添加头元素 |
lastFirst(Object obj) | 添加尾元素 |
removeFirst() | 移除头元素 |
removeLast() | 移除尾元素 |
getFirst() | 得到头元素 |
getLast() | 得到尾元素 |
remove() | 移除头元素 |
pop() | 移除头元素 |
push(Object obj) | 添加头元素 |
peek() | 得到头元素 |
poll() | 移除头元素 |
offer(Object obj) | 添加尾元素 |
ArrayList和LinkedList的区别
- 这两个类都是List接口的实现类,保存的元素有序可重复,允许保存null。
- ArrayList采用数组实现,随机读取效率高,插入删除效率低,适合用于查询。
- LinkedList采用双向链表实现,插入删除时不影响其他元素,效率高,随机读取效率低,适合用于频繁更新集合。
Set接口(无序不重复)
无序集合,元素不可以重复,允许保存null,没有索引。
Set接口中没有自己定义的方法,都是继承于Collection接口中的方法
哈希表hash table
哈希表也称为散列表,是一种数据结构,能更快地访问数据。
要保存的数据称为原始值,这个原始值通过一个函数得到一个新的数据,这个函数称为哈希函数,这个新数据称为哈希码,哈希码与原始值之间有一个映射关系,这个关系称为哈希映射,可以构造一张映射表,这个表称为哈希表。在哈希表中,可以通过哈希码快速的访问对应的原始值。
假设原本的数据为左侧的数组。
如果要查询10,需要遍历数组,效率不高。
通过一个特定的函数“原始值%5”,得到一组新数据,让数据重新对应函数,保存到新数组中。
这是如果要查询10,由于哈希函数是通过%5得到了0,所以直接查询哈希表中0对应的元素即可。
整个过程中,这个函数称为哈希函数,得到的数据称为哈希函数,新数组为哈希表,对应关系为哈希映射。
这个哈希函数,有一定的几率让多个原始值得到相同的哈希码,这种情况称为哈希冲突(哈希码一致,实际值不同),
为了解决哈希冲突,可以使用“拉链法”,将2这个哈希码所在的位置像链表一样进行延伸。
哈希码的特点
- 如果两个对象的hashCode不同,这两个对象一定不同
- 如果两个对象的hashCode相同,这两个对象不一定相同
- hashCode相同,对象不同,这种现象称为哈希冲突
- **“通话"和"重地”**这两个字符串的hashCode相同,但不是同一个对象,称为哈希冲突
hashSet实现类
- 采用哈希表实现
- 元素不能重复,无序保存,允许保存一个null
- 本质是一个HashMap对象
- 再使用hashSet集合时,一般要重写hashCode()方法
构造方法
常用构造方法 | 说明 |
---|---|
HashSet() | 实际是创建一个HashMap对象 |
常用方法
HashSet中没有属于自定义的方法,都重写了父接口Set和Collection中的方法。这里参考Collection中的方法即可。
没有与索引相关的方法。
HashSet添加数据的原理
如果两个元素的HashCode相同,且equals结果为true,视为同一个对象,不能添加。
每次向集合中添加元素时,先判断该元素的hashCode是否存在
- 如果不存在,视为不同对象,直接添加
- 如果存在,再判断equals方法的结果
- 如果false,视为不同对象,可以添加
- 如果true,视为同一对象,不能添加
由此可见,不能添加的条件是两个对象的HashCode相同,且equals结果为true。
如果每次只判断equals的话,由于equals方法通常重写时会判断很多属性,效率不高。
如果每次只判断hashCode的话,可能会有哈希冲突。
所以先判断hashCode,再判断equals,既能保证效率,又能保证不添加重复元素
HashSet的应用
如果想要保存的对象不重复,且无关顺序,可以使用HashSet,如
Goods类
package day5.com.hqyj.hashSetTest;
import java.util.Objects;
/*
* 定义商品类
* 品牌、名称、价格
* */
public class Goods {
private String brand;
private String name;
private int price;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Goods goods = (Goods) o;
return price == goods.price &&
Objects.equals(brand, goods.brand) &&
Objects.equals(name, goods.name);
}
/*
* 根据所有属性生成哈希码
* 如果两个对象的所有属性都一致,生成的哈希码就一致
* */
@Override
public int hashCode() {
return Objects.hash(brand, name, price);
}
public Goods(String brand, String name, int price) {
this.brand = brand;
this.name = name;
this.price = price;
}
@Override
public String toString() {
return "Goods{" +
"brand='" + brand + '\'' +
", name='" + name + '\'' +
", price=" + price +
'}';
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
}
Mian类
package day5.com.hqyj.hashSetTest;
import java.util.HashSet;
import java.util.Iterator;
public class Main {
public static void main(String[] args) {
//创建一个hashset集合
HashSet<Goods> hs = new HashSet<>();
//创建Goods对象
Goods g1 = new Goods("康师傅", "冰红茶", 3);
Goods g2 = new Goods("康师傅", "香辣牛肉面", 5);
Goods g3 = new Goods("农夫山泉", "矿泉水", 2);
Goods g4 = new Goods("农夫山泉", "矿泉水", 2);
//第一次添加,一定可以添加
hs.add(g1);
//第二次添加,对象属性不同,hashCode不同,可以添加
hs.add(g2);
//第三次添加,对象属性不同,hashCode不同,可以添加
hs.add(g3);
//第四次添加,对象属性相同,hashCode相同,在判断equals结果,true,视为已存在,不能添加
hs.add(g4);
/*
* hashSet没有可以通过索引获取对象的方法,所以无法使用普通for循环
* 这里可以使用增强for循环
* */
for(Goods g : hs){
System.out.println(g);
}
/*
//可以使用迭代器遍历HashSet集合中的元素
Iterator<Goods> it = hs.iterator();
while (it.hasNext()){
Goods goods =it.next();
System.out.println(goods);
}
*/
}
}
equals方法和hashCode的关系
-
如果两个对象的equals方法结果为true,在没有重写equals方法的前提下,hashCode相同。
- 如果没有重写equals,默认是Object中使用==判断,如果结果为true说明是同一个对象,hashCode一定相同 </