集合和数组都是容器
数组:类型确定,长度固定,可以存储基本类型和引用类型的数据
集合:类型可以不固定,大小可变,只能存储引用数据类型的数据
Collection | 单列 | 单列集合,每个元素只包含一个值 |
Map | 双列 | 双列结合,每个元素包含两个值(键值对) |
1 Collection
List(接口)系列集合:添加元素是有序、可重复、有索引
- ArrayList、LinkedList(实现类):有序、可重复、有索引
Set(接口)系列结合:添加元素是无序、不重复、无索引的
- HashSet(实现类):无序、不重复、无索引。LinkedHashSet:有序、不重复、无索引
// 不定义集合的数据类型
Collection list = new ArrayList();
// 定义集合的数据类型,只能支持引用数据类型
Collection<Integer> list = new ArrayList<>();
// 添加元素
list.add('a');
Collection hash = new HashSet();
// 添加元素
hash.add('a');
1.1 Collection
1.1.1 常用API
public boolean add(E e) | 把给定的对象添加到当前集合中 |
public void clear() | 清空集合中所有的元素 |
public boolean remove(E e) | 把给定的对象在当前集合中删除 |
public boolean contains(Object obj) | 判断当前集合中是否包含给定的对象 |
public boolean isEmpty() | 判断当前集合是否为空 |
public int size() | 返回集合中元素的个数。 |
public Object[] toArray() | 把集合中的元素,存储到数组中 |
1.1.2 使用
如果希望元素可以重复,又有索引,索引查询要快?
- 用ArrayList集合,基于数组的。(用的最多)
如果希望元素可以重复,又有索引,增删首尾操作快?
- 用LinkedList集合,基于链表的。
如果希望增删改查都快。但是元素不重复、无序、无索引。
- 用HashSet集合,基于哈希表的.
如果希望增删改查都快。但是元素不重复、有序、无索引。
- 用LinkedHashSet集合,基于哈希表和双链表。
如果要对对象进行排序。
- 用TreeSet集合,基于红黑树。后续也可以用List集合实现排序。
扩展知识
可变参数:可以接收多个数据(本质是一个数组)(一个形参列表可变参数只有一个,可变参数必须放在形参列表的最后)
// 数据类型...参数名称
public static void sum(int...nums){
}
1.1.3 Collections集合工具类
public static <T> boolean addAll(Collection<? super T> c, T... elements) | 给集合对象批量添加元素 |
public static void shuffle(List<?> list) | 打乱List集合元素的顺序 |
List<Integer> test = new Arraylist<>();
// 批量添加数据
Collection.addAll(test,1,2,3,4,5);
//打乱list集合顺序
Collection.shuffle(test);
排序(只针对list)
public static <T> void sort(List<T> list) | 将集合中元素按照默认规则排序 |
public static <T> void sort(List<T> list,Comparator<? super T> c) | 将集合中元素按照指定规则排序 |
Collections.sort(test);
方式一
- 让自定义的类(如学生类)实现Comparable接口重写里面的compareTo方法来定制比较规则。
// Student类里重写CompareTo方法
// 按年龄升序
@Override
public int compareTo(String o){
return this.age - o.age;
}
方式二
- 设置Comparator接口对应的比较器对象,来定制比较规则。
// 按年龄降序排
Collections.sort(stulist, new Comparator<Student>(){
@Override
public int compare(Student o1,Student o2){
return o2.getAge() - o1.getAge();
}
});
1.2 List集合
有序、可重复、有索引
1.2.1 ArrayList
基于数组实现的,查询块,增删慢。
List<String> list = new ArrayList<>();
list.add("a");
void add(int index,E element) | 在此集合中的指定位置插入指定的元素 |
E remove(int index) | 删除指定索引处的元素,返回被删除的元素 |
E set(int index,E element) | 修改指定索引处的元素,返回被修改的元素 |
E get(int index) | 返回指定索引处的元素 |
删除重复相邻元素
foreach遍历删除、lambda表达式(会出现bug)
// 迭代器遍历删除
Iterator<String> it = list.iterator();
while (it.hasNext()){
String ele = it.next();
if("Java".equals(ele)){
list.remove("要删除元素");
it.remove(); // 使用迭代器删除当期位置的元素,保证不后移,能够成功遍历到全部元素!
}
}
// for循环删除
for (int i = list.size() - 1; i >= 0; i--) {
String ele = list.get(i);
if("Java".equals(ele)){
list.remove("要删除元素");
}
}
// for循环删除2
for (int i = 0; i < list.size(); i++) {
String ele = list.get(i);
if("要删除元素".equals(ele)){
list.remove("要删除元素");
i--;
}
}
1.2.2 LinkedList
基于双链表实现,查询慢,增删首尾元素快
LinkedList<string> a = new LinkedList<>();
a.addFirst("hhhh");
public void addFirst(E e) | 在该列表开头插入指定的元素 |
public void addLast(E e) | 将指定的元素追加到此列表的末尾 |
public E getFirst() | 返回此列表中的第一个元素 |
public E getLast() | 返回此列表中的最后一个元素 |
public E removeFirst() | 从此列表中删除并返回第一个元素 |
public E removeLast() | 从此列表中删除并返回最后一个元素 |
1.3 Set集合
无序、不重复、无索引
- HashSet : 无序、不重复、无索引。
- LinkedHashSet:有序、不重复、无索引。
- TreeSet:排序、不重复、无索引。
Set集合的功能基本与Collection的API一致
Set<String> sets = new HashSet<>();
sets.add("aaa");
1.3.1 HashSet
采取哈希表存储
哈希表增删改查都很快(JDK8前,底层使用数组+链表组成;JDK8后,底层采用数组+链表+红黑树组成)
- 哈希值是JDK根据对象的地址,按照某种规则算出来的int类型的数值
- 同一对象多次调用hashCode()方法,返回的哈希值相同;
- 默认情况下,不同对象的哈希值是不同的
public int
hashCode
()
| 返回对象的哈希值 |
Set<String> sets = new HashSet<>();
1.3.1.1 LinkedHashSet
HashSet的子类,有序(存储和取出的顺序一致)、不重复、无索引
底层基于哈希表,使用双链表记录添加
1.3.2 TreeSet
不重复、无索引、可排序(按元素的大小默认升序排)
底层基于红黑树的数据结构实现排序,增删改查都很快
TreeSet集合是一定要排序的,可以将元素按照指定的规则进行排序
- 对于数值类型:Integer , Double,官方默认按照大小进行升序排序。
- 对于字符串类型:默认按照首字符的编号升序排序。
- 对于自定义类型如Student对象,TreeSet无法直接排序。
对于没有默认规则的,需自定义规则(若这两个都定义了,则采用就近的规则)
方式一
- 让自定义的类(如学生类)实现Comparable接口重写里面的compareTo方法来定制比较规则。
// Student类里重写CompareTo方法
// 按年龄升序
@Override
public int compareTo(String o){
return this.age - o.age;
}
方式二
- TreeSet集合有参数构造器,可以设置Comparator接口对应的比较器对象,来定制比较规则。
// 按年龄降序排
Set<Student> s1 = new TreeSet<>(new Comparator<Student>(){
@Override
public int compare(Student o1,Student o2){
return o2.getAge() - o1.getAge();
}
});
2 Map
Map集合每个元素是键值对,有点像字典类型
HashMap | 元素按照键是无序,不重复,无索引,值不做要求。(与Map体系一致) |
LinkedHashMap | 元素按照键是有序,不重复,无索引,值不做要求。 |
TreeMap | 元素按照建是排序,不重复,无索引的,值不做要求。 |
Map<String, Integer> maps = new HashMap<>();
2.1 常用API
V put(K key,V value) | 添加元素 |
V remove(Object key) | 根据键删除键值对元素 |
void clear() | 移除所有的键值对元素 |
boolean containsKey(Object key) | 判断集合是否包含指定的键 |
boolean containsValue(Object value) | 判断集合是否包含指定的值 |
boolean isEmpty() | 判断集合是否为空 |
int size() | 集合的长度,也就是集合中键值对的个数 |
2.2 遍历方法
方式一:键找值的方式遍历:先获取Map集合全部的键,再根据遍历键找值。
Set<K> keySet() | 获取所有键的集合 |
V get(Object key) | 根据值获取键 |
方式二:键值对的方式遍历,把“键值对“看成一个整体,难度较大。
Set<Map.Entry<K,V>> entrySet() | 获取所有键值对对象的集合 |
K getKey() | 获得键 |
V getValue() | 获取值 |
// 把Map集合转换成Set集合
Set<Map.Entry<String, Integer>> entries = maps.entrySet();
Set entries = new HashSet();
for (Map.Entry<String, Integer> entry : entries) {
String key = entry.getKey(); // 取键
Integer value = entry.getValue(); // 取值
System.out.println(key + "==>" + value);
}
方式三:JDK 1.8开始之后的新技术:Lambda表达式。
default void forEach(BiConsumer<? super K, ? super V> action) | 结合lambda遍历Map集合 |
// 简化前
maps.forEach(new BiConsumer<String, Integer>() {
@Override
public void accept(String key, Integer value) {
System.out.println(key + "===>" + value);
}
});
// 简化后
maps.forEach((k,v) -> {
System.out.println(k + "--->" + v);
});
2.3 HashMap*
HashMap是Map里面的一个实现类。
- 特点都是由键决定的:无序、不重复、无索引
- 底层是哈希表结构的。增删改查的性能都较好。
- 依赖hashCode方法和equals方法保证键的唯一。
- 如果键要存储的是自定义对象,需要重写hashCode和equals方法。
2.4 LinkedHashMap
- 由键决定:有序(保证存储和取出的元素顺序一致)、不重复、无索引。
- 原理:底层数据结构是依然哈希表,只是每个键值对元素又额外的多了一个双链表的机制记录存储的顺序
2.5 TreeMap
- 由键决定特性:不重复、无索引、可排序(按照键数据的大小默认升序(有小到大)排序。只能对键排序。)
- TreeMap集合是一定要排序的,可以默认排序,也可以将键按照指定的规则进行排序
- TreeMap跟TreeSet一样底层原理是一样的。
TreeMap集合自定义排序规则有2种
- 类实现Comparable接口,重写比较规则。
- 集合自定义Comparator比较器对象,重写比较规则。
3 遍历方式
迭代器
// 得到集合lists对象的迭代器对象
Iterator<String> it = lists.iterator();
while(it.hasNext()){
// it.next() 获取元素
String ele = it.next();
System.out.println(ele);
}
foreach
Collection<String> list = new ArrayList<>();
...
for(String ele : list) {
System.out.println(ele);
}
Lambda表达式
Collection<String> lists = new ArrayList<>();
lists.forEach(s -> {
System.out.println(s);
});
/**
lists.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
});
**/
4 存储自定义对象
public class SystemDemo {
public static void main(String[] args) {
// 定义一个电影类的集合对象
Collection <Movie> movies = new ArrayList<>();
movies.add(new Movie("《肖生克的救赎》", 9.7, "罗宾斯"));
movies.add(new Movie("《霸王别姬》", 9.6 , "张国荣、张丰毅"));
movies.add(new Movie("《阿甘正传》", 9.5, "汤姆.汉克斯"));
// 打出数组内容
System.out.println(movies);
// 遍历内容
for (Movie movie : movies) {
System.out.println("片名:" + movie.getName());
System.out.println("评分:" + movie.getScore());
System.out.println("主演:" + movie.getAcotr());
}
}
}
public class Movie {
private String name;
private double score;
private String acotr;
public Movie(String name, double score, String acotr) {
this.name = name;
this.score = score;
this.acotr = acotr;
}
}
集合中存储的是元素对象的地址
5 常见数据结构
栈:先进后出,后进先出
队列:先进先出,后进后出
数组:查询快,增删慢
- 查询速度快:查询数据通过地址值和索引定位,查询任意数据耗时相同。(元素在内存中是连续存储的)
- 删除效率低:要将原始数据删除,同时后面每个数据前移。
- 添加效率极低:添加位置后的每个数据后移,再添加元素。
链表:查询慢(数据从头开始查找),增删相对快
二叉树:只有一个根节点,每个节点最多支持两个直接子节点
查找二叉树
- 每一个节点上最多有两个子节点
- 左子树上所有节点的值都小于根节点的值
- 右子树上所有节点的值都大于根节点的值
平衡二叉树:任意节点的左右两个子树的高度差不超过1,任意节点的左右两个子树都是一颗平衡二叉树
红黑数:一种自平衡的二叉查找树,是计算机科学中用到的一种数据结构。
红黑树增删改查性能都很好
- 每—个节点或是红色的。或者是黑色的,根节点必须是黑色。、
- 如果一个节点没有子节点或者父节点,则该节点相应的指针属性值为Nil,这些Nil视为叶节点,叶节点是黑色的。
- 如果某一个节点是红色,那么它的子节点必须是黑色(不能出现两个红色节点相连的情况)。
- 对每一个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点。
6 泛型
- 泛型:是JDK5中引入的特性,可以在编译阶段约束操作的数据类型,并进行检查。
- 泛型的格式:<数据类型>;注意:泛型只能支持引用数据类型。
- 集合体系的全部接口和实现类都是支持泛型的使用的。
泛型的好处:
- 统一数据类型。
- 把运行时期的问题提前到了编译期间,避免了强制类型转换可能出现的异常,因为编译阶段类型就能确定下来。
泛型类
// 修饰符 class 类名<泛型变量>{ }
public class MyArrayList<T> {
}
// 调用
MyArrayList<String> myl = new MyArrayList<>();
此处泛型变量T可以随便写为任意标识。常见的如E、T.K、V等。
作用:编译阶段可以指定数据类型,类似于集合的作用。
泛型方法
定义方法时同时定义了泛型的方法就是泛型方法。
// 修饰符 <泛型变量> 方法返回值 方法名称(形参列表){}
public <T> void show(T t) { }
作用:方法中可以使用泛型接收一切实际类型的参数,方法更具备通用性。
方法中可以使用泛型接收一切实际类型的参数,方法更具备通用性
泛型接口
使用了泛型定义的接口就是泛型接口。
// 修饰符 interface 接口名称<泛型变量>{}
public interface Data<E>{}
作用:泛型接口可以让实现类选择当前功能需要操作的数据类型,实现类可以在实现接口的时候传入自己操作的数据类型,这样重写的方法都将是针对于该类型的操作。
通配符
? 可以在“使用泛型”的时候代表一切类型。
E T K V 是在定义泛型的时候使用的。
ArrayList<BMW> bmws = new ArrayList<>();
bmws.add(new BMW());
go(bmws);
ArrayList<BENZ> benzs = new ArrayList<>();
benzs.add(new BENZ());
go(benzs);
// ArrayList<Dog> dogs = new ArrayList<>();
// dogs.add(new Dog());
// go(dogs);
}
/**
定义方法,让汽车可以一起参加比赛
*/
// ? extends Car: ?必须是Car或者其子类 泛型上限
// ? super Car : ?必须是Car或者其父类 泛型下限
public static void go(ArrayList<? extends Car> cars){
}