10、容器Collection
容器
Collection接口
有两个子接口:list set
继承iterable
为什么要有集合呢?
数组存储数组的时候,长度一旦确定就无法改变,当我们存储未知长度的数据时,数组就满足不了我们的需求了。
集合是一个庞大的体系。
Collection接口中的方法
collection是一个无序不唯一的集合。
集合作为容器应该具有的功能(增,删,改,查),
不一定全有。
集合的基本操作:增加,删除,判断,取出
序号 | 方法名 | 作用 |
---|---|---|
1 | add(Object obj) | 添加,存储的是对象的引用 |
2 | size() | 容器中元素的实际个数 |
3 | remove(Object obj)clear()removeAll(Collection c)retainAll(Collection c) | 删除 |
4 | contains(Object obj)isEmpty() | 判断元素 |
5 | iterator() | 遍历元素 |
List接口
继承collection接口,是一种 有序 且 不唯一 的集合。
实现类:
ArrayList
数据结构:在内存中开辟一个连续的内存空间。是一个线性表。线性表的存储结构:分为顺序存储结构和链式存储结构两种
该类也是实现了List的接口,实现了可变大小的数组,随机访问和遍历元素时,提供更好的性能。该类也是非同步的,在多线程的情况下不要使用。ArrayList 增长当前长度的50%,插入删除效率低。
默认长度10, 之后的长度 10->16->25->38->58->88->...
低层是数组,可以说是一个动态的数组,查找元素效率高,添加,删除,插入效率低。
get(int index)//根据下标得到一个元素 add(index,e)//将e插入到index位置 set(index,e)//将e替换index位置中的元素
LinkedList
数据结构:不连续的内存空间的双向链表,一个数据分三个存储位置,(first,e,last)first指的是上一个储存的引用,e是元素,last是下个元素的引用,如果是最后一个,则为null
该类实现了List接口,允许有null(空)元素。主要用于创建链表数据结构,该类没有同步方法,如果多个线程同时访问一个List,则必须自己实现访问同步,解决方法就是在创建List时候构造一个同步的List。例如:
Listlist=Collections.synchronizedList(newLinkedList(...));
LinkedList 查找效率低。
效率:增删快,查找慢
addFirst(e)//添加到第一个元素 add(e)//添加到最后一个元素 addLast(e);//添加到最后一个元素 getFirst();//得到第一个元素 getLast();//得到最后一个元素 offer(e);//将指定元素添加到末尾 offerFirst(e)//将指定元素添加到首 peek();//获取第一个元素,但不移除 peekFirst();获取第一个元素,如果此列表为空,返回null poll();//获取并移除第一个元素 pollFirst()获取并移除第一个元素,如果此列表为空,返回null pop();从堆栈中弹出第一个元素 push(e);将元素推进第一个元素 remove();移除表头元素 remove(index);移除指定位置的元素 remove(e);从此列表中移除首次出现的指定元素(如果存在)。 removeFirst();移除第一个元素并返回 set(index,e);将e替换index位置的元素 size();集合长度
set接口
在Collection的基础上添加了唯一性。但还是无序的,所以没有对下标进行操作。
接下来要学习三个set的实现类。
注意:
要想存储的数据是唯一的,必须要重写hashCode和equals方法。
set集合遍历有两种方式:
通过forEach:
for(String str : set){ str;//就是每一个元素 }
通过迭代器:
通过set调用iterator()得到Iterator接口对象,可以通过里面的三个方法来实现遍历 Iterator it = set.iterator(); while(it.hasNext()){//判断是否还有下一个元素 it.next();//得到一个元素,指向下一个位置 it.remove();//删除一个next得到的元素 }
HashSet
底层采用哈希表的结构进行存储。哈希表的储存结构:根据公式进行,比如:y=x%7;用余数分成7个区间,取的时候可以根据哈希码得到y,就可以得到存储在那个区间,这样就可以排除大量的数据,从而大大提高了根据内容查询效率。
优点:根据内容查询效率最高,添加,删除的效率最高。
缺点:无序。
数据结构:底层采用哈希表实现
总结:
HashSet是如何保证元素的唯一性的呢?
答:是通过元素的两个方法,hashCode和equals方法来完成
如果元素的HashCode值相同,才会判断equals是否为true
如果元素的hashCode值不同,不会调用equals方法
LinkedHashSet
是HashSet的一个子类,在HashSet的基础上添加了链表的数据结构,使得set集合变成了有顺序,指的是添加顺序,并不是有下标,而那下标进行操作。
优点:比HashSet查找,添加,删除的效率低一点。因为数据结构更复杂了。还添加了顺序。
缺点:
TreeSet
采用二叉树(红黑树)的存储结构
优点:有序(排序后的升序)查询速度比List快
(按照内容查询)
缺点:查询速度没有HashSet快
注意:二叉树是一个升序的存储结构,所以要有比较的规则。
两种方式
1.内部实现Comparable接口,实现CompareTo();在里面写比较的规则。
package treeSet; /** * 内部比较器 * @author lgx * */ public class Person implements Comparable<Person>{ private String name; private int age; public Person(String name, int age) { super(); this.name = name; this.age = age; } public Person() { super(); // TODO Auto-generated constructor stub } @Override public int compareTo(Person person) { return this.hashCode() - person.hashCode(); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + age; result = prime * result + ((name == null) ? 0 : name.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Person other = (Person) obj; if (age != other.age) return false; if (name == null) { if (other.name != null) return false; } else if (!name.equals(other.name)) return false; return true; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Person [name=" + name + ", age=" + age + "]"; } }
2.外部类的比较规则,外部类实现Comparator接口。实现compare();
package treeSet; /** * 外部比较器 * @author lgx * */ public class Dog { private String name; private int age; public Dog() { super(); // TODO Auto-generated constructor stub } public Dog(String name, int age) { super(); this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Dog [name=" + name + ", age=" + age + "]"; } } //外部实现接口的类 package treeSet; import java.util.Comparator; public class DogSort implements Comparator<Dog>{ @Override public int compare(Dog o1, Dog o2) { return o1.getAge()-o2.getAge(); } }
测试的方法
package treeSet; import java.util.TreeSet; /** * 使用TreeSet 的时候,必须有比较的规则,否则会出错 * 有内部比较器和外不比较器 * @author lgx * */ public class Test { public static void main(String[] args) { // //外部比较器 // TreeSet<Dog> set = new TreeSet<Dog>(new DogSort()); // set.add(new Dog("旺财",1)); // set.add(new Dog("大黄",2)); // System.out.println(set); //内部比较器 TreeSet<Person> set = new TreeSet<Person>(); set.add(new Person("bb",13)); set.add(new Person("aas",14)); set.add(new Person("cc",13)); set.add(new Person("cc",15)); System.out.println(set); } }
Map接口
存储的方式是以键值对的方式存储的,键是唯一的,而值可以相同。一个键对应一个值,比如通过书名来得到书的全部信息。
什么map的数据结构跟什么set的数据结构跟相似,主要在存储的时候多存入一个值,而键是完全跟Set一样。
HashMap
基于哈希表的 Map 接口的实现。此实现提供所有可选的映射操作,并允许使用 null 值和 null 键。(除了非同步和允许使用 null 之外,HashMap 类与 Hashtable 大致相同。)此类不保证映射的顺序,特别是它不保证该顺序恒久不变。 此实现假定哈希函数将元素适当地分布在各桶之间,可为基本操作(get 和 put)提供稳定的性能。迭代 collection 视图所需的时间与 HashMap 实例的“容量”(桶的数量)及其大小(键-值映射关系数)成比例。所以,如果迭代性能很重要,则不要将初始容量设置得太高(或将加载因子设置得太低)。
优点:超级快速的查询速度,如果有人问你什么数据结构可以达到O(1)的时间复杂度,没错是HashMap
动态的可变长存储数据(和数组相比较而言)
缺点:需要额外计算一次hash值
如果处理不当会占用额外的空间
LinkedHashMap
是HashMap的一个子类,在他之前添加了存储顺序
TreeMap
是一个二叉树的存储结构
Iterator接口
是一个Iterable接口中一个iterator()方法的一个返回值类型。
Iterator是一个迭代器,里面有三个方法:hasNext()判断是否还有下一个元素。next()返回当前的元素,指向下个元素。remove();移除返回的元素
所有的集合类均未提供相应的遍历方法,而是把遍历交给迭代器完成。迭代器为集合而生,专门实现集合遍历。
Collections类
是一个工具类,提供了很多静态的 方法,是为了方法操作Collection接口的实现类。
主要的方法:
addAll(Collection con,T....elements); 将所有指定元素添加到指定 collection 中。
binarySearch();二分查找
sort();对集合进行排序,要定义排序的规则
reverse();倒序排序
fill();替换
copy(...);将所有元素从一个列表复制到另一个列表。执行此操作后,目标列表中每个已复制元素的索引将等同于源列表中该元素的索引。目标列表的长度至少必须等于源列表。如果目标列表更长一些,也不会影响目标列表中的其余元素。
forEach
增强for循环,根据集合引用遍历,可以直接得到对象,一般用做遍历。
For-each循环
增强的for循环,遍历array或Collection的时候相当简便
无需获得集合和数组的长度,无需使用索引访问元素,无需循环条件
遍历集合时底层调用Iterator完成操作
For-each缺陷
数组:
不能方便的访问下标值
不要在for-each中尝试对变量赋值,只是一个临时变量
集合:
与使用Iterator相比,不能方便 的删除集合中的内容
泛型
泛型是程序设计语言的一种特性。在编写代码的时候,不确定该使用什么类型的时候,我们可以先定义一个泛型,那些部分在使用前必须要作出声明。这样可以达到代码的复用提高软件的开发效率。泛型是一个引用类型,是堆对象,主要引用了类型参数的概念。
有泛型类,泛型方法,泛型接口。
泛型类
public class A<T> { // 泛型类:定义类的时候指定类型形参T,在类里面T就可以当成类型使用 private T a; public T getA() { return a; } public void setA(T a) { this.a = a; } }
继承泛型类的几种方式
class B1 extends A<String> {} class B2<E> extends A<String> {} class B3<E> extends A<E> {} class B4<E1, E2> extends A<E1> {}
使用泛型类
public static void main(String[] args) { B1 b1 = new B1(); b1.setA("b1"); System.out.println(b1.getA()); A<String> a1 = new B1(); a1.setA("a1"); System.out.println(a1.getA()); //B2<?> b2 = new B2<String>(); //B2<String> b2:声明变量时已经指定了B2的类型形参E为String, //new B2<>():创建对象时可以使用菱形语法(泛型推断) B2<String> b2 = new B2<>();//菱形语法 b2.setA("b2"); System.out.println(b2.getA()); // 无法通过A<String>推断出B2的类型形参E的类型,不可以使用菱形语法 A<String> a2 = new B2<Object>(); a2.setA("a2"); System.out.println(a2.getA()); B3<String> b3 = new B3<>();//菱形语法 b3.setA("b3"); System.out.println(b3.getA()); A<String> a3 = new B3<>();//菱形语法 a3.setA("a3"); System.out.println(a3.getA()); }
泛型方法
泛型接口
实现该接口的类可以确定泛型的类型,也可以不确定,那么创建对象的时候就要确定泛型的类型。泛型类型确定了,其他的也会跟着变成该类型。
//泛型接口 package test; public interface DogInterface<E> { E test(); void test02(E e); } //接口实现类 package test; public class DogImpl<E> implements DogInterface<E>{//可以不确定,也可以确定 @Override public E test() { // TODO Auto-generated method stub return null; } @Override public void test02(E e) { // TODO Auto-generated method stub } } //测试 package test; public class Test { public static void main(String[] args) { DogImpl dog = new DogImpl<String>();//创建对象的时候就要确定类型 } }
Comparable接口
问题:上面的算法根据什么确定集合中对象的“大小”顺序?
所有可以“排序”的类都实现了java.lang.Comparable 接口,Comparable接口中只有一个方法
public int compareTo(Object obj);
该方法:
返回 0 表示 this == obj
返回正数 表示 this > obj
返回负数 表示 this < obj
实现了Comparable 接口的类通过实现 comparaTo 方法从而确定该类对象的排序方式。
总结
名称 | 存储结构 | 顺序 | 唯一性 | 查询效率 | 添加/删除效率 |
---|---|---|---|---|---|
ArrayList | 顺序表 | 有序(添加) | 不唯一 | 索引查询效率高 | 低 |
LinkedList | 链表 | 有序(添加) | 不唯一 | 下标查低 | 最高 |
HashSet | 哈希表 | 无序 | 唯一 | 最高 | 最高 |
HashMap | 哈希表 | Key无序 | Key唯一 | 最高 | 最高 |
LinkedHashSet | 哈+链 | 有序(添加) | 唯一 | 最高 | 最高 |
LinkedHashMap | 哈+链 | Key有序( 添加) | Key唯一 | 最高 | 最高 |
TreeSet | 二叉树 | 有序(升序) | 唯一 | 中等 | 中等 |
TreeMap | 二叉树 | 有序(升序) | Key唯一 | 中等 | 中等 |
特性 | Collection | Map | ||
---|---|---|---|---|
无序不唯一 | Collection | Map.values() | ||
有序不唯一 | ArrayList | LinkedList | ||
无序唯一 | HashSet | HashMapkeySet | ||
有序唯一 | LinkedHashSet | TreeSet | LinkedHashMapkeySet | TreeMapkeySet |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· 字符编码:从基础到乱码解决
· Open-Sora 2.0 重磅开源!