数组和集合
数组的特点
- 数组中保存的元素都是有序的,可以通过下标快速访问
- 数组中保存的数据都是同一种类型
- 数组的长度在定义后,无法改变
- 数组无法获取其中保存的元素实际数量
集合的特点
- 能保存一组数据,可以有序可以无序
- 集合的容量可变
- 集合中可以保存不同类型的数据
- 可以获取集合中保存的元素实际数量
集合框架(集合家族)
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) | 添加头元素 |
addLast(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集合时,通常要重写实体类中的equals和hashcode方法
构造方法
常用构造方法 | 说明 |
---|---|
HashSet() | 创建一个空集合,实际是创建一个HashMap对象。 |
常用方法
HashSet中没有属于自定义的方法,都是重写了父接口Set和Collection中的方法。这里参考Collection中的方法即可。
没有与索引相关的方法。
HashSet添加数据的原理
如果两个元素的hashCode相同且equals结果为true,视为同一个对象,不能添加。
每次向集合中添加元素时,先判断该元素的hashCode是否存在
- 如果不存在,视为不同对象,直接添加
- 如果存在,再判断equals方法的结果
- 如果false,视为不同对象,可以添加
- 如果true,视为同一对象,不能添加
由此可见,不能添加的条件是两个对象的hashCode相同且equals的结果为true。
如果每次只判断equals的话,由于equals方法通常重写时会判断很多属性,效率不高。
如果每次只判断hashCode的话,效率高,但有可能会有哈希冲突,
所以先判断hashCode,再判断equals,技能保证效率,又能保证不添加重复元素。
equals方法和hashCode的关系
-
如果两个对象的equals方法结果为true,在没有重写equals方法的前提下,hashCode相同吗
- 如果没有重写equals,默认是Object中使用==判断,如果结果为true,说明是同一个对象,hashCode一定相同
-
如果两个对象的hashCode不同,在没有重写equals方法的前提下,equals方法的结果为?
- hashCode不同,说明不是同一个对象,没有重写equals,说明使用Object中equals的==判断,结果为false
-
如果两个对象的hashCode相同,equals方法的比较结果为?
-
可能为true也可能为false
String str1="hello"; String str2="hello"; //以上两个字符串使用同一个地址,hashCode相同,equals方法为true String str3="通话"; String str4="重地"; //以上连个字符串是不同地址,但hashCode相同,因为哈希冲突,equals方法为false
-
HashSet的应用
如果想要保存的对象保证不重复,且无关顺序,可以使用HashSet。如学生管理
Goods类
package 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);
}
@Override
public String toString() {
return "Goods{" +
"brand='" + brand + '\'' +
", name='" + name + '\'' +
", price=" + price +
'}';
}
public Goods(String brand, String name, int price) {
this.brand = brand;
this.name = name;
this.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;
}
}
Main类
package 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对象
//g1、g2、g3的属性不同,生成的hashcode不同,都能添加
Goods g1 = new Goods("康师傅", "冰红茶", 3);
Goods g2 = new Goods("康师傅", "红烧牛肉面", 5);
Goods g3 = new Goods("农夫山泉", "矿物质水", 2);
//g3与g4的属性相同,生成的hashcode相同,继续判断equals
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);
}*/
}
}
TreeSet实现类
- 特殊的Set实现类,数据可以有序保存,可以重复,不能添加null
- 采用红黑树(自平衡二叉树)实现的集合
- 二叉树表示某个节点最多有两个子节点
- 某个节点右侧节点值都大于左侧节点值
- 红黑树会经过不停的"变色"、"旋转"达到二叉树的平衡
- 只能添加同一种类型的对象且该类实现了Comparable接口
- 实现Comparable接口后必须要重写compareTo()方法
- 每次调用添加add(Object obj)方法时,就会自动调用参数的compareTo()方法
- compareTo()方法的返回值决定了能否添加新元素和新元素的位置
- 如果返回0,视为每次添加的是同一个元素,不能重复添加
- 如果返回正数,将新元素添加到现有元素之后
- 如果返回负数,将新元素添加到现有元素之前
- 添加的元素可以自动排序
构造方法
常用构造方法 | 说明 |
---|---|
TreeSet() | 创建一个空集合,实际是创建了一个TreeMap对象 |
常用方法
属于Set的实现类,所以能使用Collection和Set中的方法,除此之外,还有独有的方法
常用方法 | 作用 |
---|---|
fisrt() | 得到集合中的第一个元素 |
last() | 得到集合中的最后一个元素 |
ceil(Object obj) | 得到比指定元素obj大的元素中的最小元素 |
floor(Object obj) | 得到比指定元素obj小的元素中的最大元素 |
TreeSet的应用
如果要保存的元素需要对其排序,使用该集合。
保存在其中的元素必须要实现Comparable接口,且重写compareTo()方法,自定义排序规则
Employee类
package com.hqyj.TreeSetTest;
import java.util.Objects;
/*
* 定义员工类
* 编号
* 姓名
* 部门
* */
public class Employee implements Comparable {
private int no;
private String name;
private String dept;
@Override
public String toString() {
return "Employee{" +
"no='" + no + '\'' +
", name='" + name + '\'' +
", dept='" + dept + '\'' +
'}';
}
public Employee(int no, String name, String dept) {
this.no = no;
this.name = name;
this.dept = dept;
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDept() {
return dept;
}
public void setDept(String dept) {
this.dept = dept;
}
@Override
public int compareTo(Object o) {
//此时this是当前添加的对象
//o是已存在的集合中的对象
Employee emp = (Employee) o;
//这里用当前员工号-已有员工号,根据员工号升序
return this.getNo()-emp.getNo();
//根据姓名字符串的排序方式排序
// return emp.getName().compareTo(this.getName());
}
}
Main类
package com.hqyj.TreeSetTest;
import java.util.TreeSet;
public class Main {
public static void main(String[] args) {
TreeSet<Employee> emps = new TreeSet<>();
Employee e1 = new Employee(10023,"aaa","市场部");
Employee e2 = new Employee(10025,"ccc","市场部");
Employee e3 = new Employee(10028,"bbb","市场部");
Employee e4 = new Employee(10028,"xxx","市场部");
//第一个元素直接添加
emps.add(e1);
//第二个元素添加时,调用compareTo()方法 e2.compareTo(e1) e2.10025 - e1.10023 结果为正,添加在现有元素之后
emps.add(e2);
emps.add(e3);
//添加该元素时,调用compareTo()方法 e4.10028 - e3.10028 结果为0,不添加
emps.add(e4);
for (Employee emp : emps) {
System.out.println(emp);
}
}
}
Map接口
Map称为映射,数据以键值对的形式保存。保存的是键与值的对应关系。
键称为Key,值称为Value,键不能重复,键允许出现一个null作为键,值无限制。
键和值都是引用类型。
如,yyds就是一个键key,代表了一个含义:“永远单身”即为值value。
常用方法 | 作用 |
---|---|
size() | 得到键值对的数量 |
clear() | 清空所有键值对 |
put(Object key,Object value) | 向集合中添加一组键值对 |
get(Object key) | 在集合中根据键得到对应的值 |
remove(Object key)/remove(Object key,Object key) | 根据键或键值对移除 |
keyset() | 获取键的集合 |
values() | 获取值的集合 |
containsKey(Object key) | 判断是否存在某个键 |
containsValue(Object value) | 判断是否存在某个值 |
entrySet() | 得到键值对的集合 |
HashMap实现类(掌握)
- JDK1.8之后,HashMap采用"数组+链表+红黑树"实现
- 当没有哈希冲突时,元素保存到数组中
- 如果出现哈希冲突,在对应的位置上创建链表,元素保存到链表中
- 如果链表的长度大于8,将链表转换为红黑树
- 数据采用键值对key-value的形式保存,键不能重复,能用null作为键;值没有限制,键和值都是引用类型
- 向HashMap集合中添加元素时,原理同HashSet
构造方法
常用构造方法 | 说明 |
---|---|
HashMap() | 创建一个空的映射集合,默认大小为16,加载因子为0.75 |
常用方法
常用方法参考Map中的方法
遍历集合中元素的方式
遍历List集合
ArrayList<String> nameList = new ArrayList();
nameList.add("Tom");
nameList.add("Jerry");
nameList.add("LiHua");
nameList.add("Danny");
方式一:普通for循环
System.out.println("使用普通for循环遍历");
//方式一:普通for循环
for (int i = 0; i < nameList.size(); i++) {//从0遍历到size()
String name = nameList.get(i);//通过get(int index)获取指定索引的元素
System.out.println(name);
}
方式二:增强for循环
System.out.println("使用增强for循环遍历");
//方式二:增强for循环
for (String name : nameList) {
System.out.println(name);
}
方式三:迭代器
System.out.println("使用迭代器遍历");
//方式三:迭代器
//Collection类型的集合对象.iterator(),获取迭代器
Iterator<String> iterator = nameList.iterator();
// iterator.hasNext()判断集合中是否还有下一个元素
// iterator.next();获取下一个元素
while (iterator.hasNext()) {
String name = iterator.next();
System.out.println(name);
}
遍历Set集合
Set hs = new HashSet();
hs.add(123);
hs.add("hello");
hs.add(null);
hs.add(987);
方式一:增强for循环
for(Object o : hs){
System.out.println(o);
}
方式二:迭代器
Iterator<Object> it = hs.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
遍历Map集合
Map<Integer, User> hm = new HashMap<>();
User u1 = new User("admin", "123123");
User u2 = new User("tom", "123123");
User u3 = new User("jerry", "123123");
hm.put(1001, u1);
hm.put(1002, u2);
hm.put(1003, u3);
//遍历hashMap
for (Integer id : hm.keySet()) {//遍历键
//根据键得到对应的值
System.out.println(id + "\t" + hm.get(id).getUsername());
}
//得到当前hashmap对象中的所有键值对的集合
Set<Map.Entry<Integer, User>> entries = hm.entrySet();
//遍历键值对
for (Map.Entry<Integer, User> entry : entries) {
System.out.println(entry);
}
泛型
一种规范,常用于限制集合中元素的类型,省去遍历元素时判断是否为对应类型和转型的过程
//集合在定义后,默认可以添加任意类型的数据,但通常情况下,都是保存同一种类型
List list = new ArrayList();
list.add(123);
list.add(null);
list.add("hello");
//这时如果没有限制类型,使用增强for循环遍历集合中的元素时,就只能使用Object类型变量接收
for(Object o : list){
}
用法
在定义集合遍历时,在类后面写上**<引用数据类型>**
集合类或接口<引用数据类型> 集合变量名 = new 集合实现类();
List<String> list = new ArrayList();
//当前集合只能保存String类型的元素
list.add("sdfsdf");
//list.add(123);//无法添加
List<Integer> list2 = new ArrayList();
list2.add(123);
Collections集合工具类
- Collection是集合的根接口,定义了集合操作元素的方法
- Collections是集合的工具类,定义了集合操作元素的静态方法
常用方法
常用方法 | 说明 |
---|---|
Collections.shuffle(List list) | 打乱List集合中元素的顺序 |
Collections.sort(List list) | 对List集合中的元素进行排序,元素必须实现Comparable接口 |
Collections.swap(List list,int a,int b) | 交换List集合中元素的索引 |
Collections.replaceAll(List list,Object oldObj,Object newObj) | 替换List集合中的旧元素为新元素 |
Collections.reverse(List list) | 将List集合中的元素反转 |
Collections.fill(List list , Object obj) | 使用指定元素填充List集合 |
Collections.rotate(List list , int n) | 将集合中最后n个元素放在最前 |
Collections.max(Collection col)/min(Collection col) | 得到集合中的最大/最小值,集合中的元素必须实现Comparable接口 |
集合和数组之间的转换
-
集合转换为数组:使用Collection接口中的toArray()方法
Object[] obj = 集合对象.toArray(); List<Integer> list = new ArrayList(); list.add(123); list.add(63); list.add(3); Integer[] nums =(Integer[]) list.toArray();
-
数组转换为集合
//一个数组对象 int[] nums ={11,2,66,3,6,21}; //定义集合对象 List list = new ArrayList(); //遍历数组的同时添加到集合中 for(int i:nums){ list.add(i); }
-
一组数据转换为集合:使用Arrays工具类中的asList(一组数据)方法
//通常将数组中的数据直接作为参数 List<String> strings = Arrays.asList("XX", "aa", "qq", "xx");