Java集合
数组的特点
- 数组定义完成并启动后,类型确定、长度固定
- 不适合元素的个数和类型不确定的业务场景,更不适合做需要增删数据操作。
- 数组的功能也比较的单一,处理数据的能力并不是很强大。
集合的特点
- 集合的大小不固定,启动后可以动态变化,类型也可以选择不固定。
- 集合非常适合元素个数不能确定,且需要做元素的增删操作的场景。
- 同时,集合提供的种类特别的丰富,功能也是非常强大的,开发中集合用的更多。
集合类体系结构
- Collection单列集合,每个元素(数据)只包含一个值。
- Map双列集合,每个元素包含两个值(键值对)。
注意:集合都是泛型的形式,可以在编译阶段约束集合只能操作某种数据类型。集合和泛型都只能支持引用数据类型,不支持基本数据类型,所以集合中存储的元素都认为是对象。
Collection集合体系
-
List系列集合:添加的元素是有序、可重复、有索引。
ArrayList、LinekdList :有序、可重复、有索引。
-
Set系列集合:添加的元素是无序、不重复、无索引。
HashSet: 无序、不重复、无索引;
LinkedHashSet: 有序、不重复、无索引。
TreeSet:按照大小默认升序排序、不重复、无索引。
Collection集合
Collection是单列集合的祖宗接口,它的功能是全部单列集合都可以继承使用的。
Collection API
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
//添加数据
list.add(i);
}
//获取集合中元素的个数
System.out.println(list.size());
//把集合中的元素,存储到数组中
System.out.println(Arrays.toString(list.toArray()));
//把给定的对象在当前集合中删除
list.remove(Integer.parseInt("2"));
//判断当前集合中是否包含给定的对象
System.out.println(list.contains(2));
System.out.println(list.size());
//清空集合
list.clear();
//判断当前集合是否为空
System.out.println(list.isEmpty());
}
/**
* 10
* [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
* false
* 9
* true
*/
Collection集合的遍历方式
-
迭代器遍历
遍历就是一个一个的把容器中的元素访问一遍。 迭代器在Java中的代表是Iterator,迭代器是集合的专用的遍历方式。
注:Iterator iterator():得到迭代器对象,默认指向当前集合的索引0,迭代器如果取元素越界会出现
NoSuchElementException
异常。 -
增强for循环
for(元素数据类型 变量名 : 数组或者Collection集合) { //在此处使用变量即可,该变量就是元素 }
注:增强for循环既可以遍历集合也可以遍历数组。修改第三方变量的值不会影响到集合中的元素
-
Lambda表达式遍历集合
得益于JDK 8开始的新技术Lambda表达式,提供了一种更简单、更直接的遍历集合的方式。
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
list.add(i);
}
//迭代器遍历
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()){
Integer next = iterator.next();
System.out.print(next);
}
System.out.println();
//增加for循环
for (Integer next : list) {
System.out.print(next);
next = 1;//修改next的值对list集合没有影响
}
System.out.println();
//修改第三方变量的值不会影响到集合中的元素
for (Integer next : list) {
System.out.print(next);
}
System.out.println();
list.forEach(new Consumer<Integer>() {
@Override
public void accept(Integer integer) {
System.out.print(integer);
}
});
System.out.println();
//结合lambda遍历集合
list.forEach(integer -> System.out.print(integer));
System.out.println();
//System里的Out类掉用plint方法,会有一个参数传进去,
// 就是foreach的其中一个,然后方法里会去打印这个参数(函数式编程)
list.forEach(System.out::print);
}
/**
* 0123456789
* 0123456789
* 0123456789
* 0123456789
* 0123456789
* 0123456789
*/
Collection集合存储自定义类型的对象
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;
}
// ... getter + setter
}
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());
}
}
}
注:集合中存储的是元素对象的地址
List系列集合
List系列集合特点
ArrayList、LinekdList :有序,可重复,有索引。
- 有序:存储和取出的元素顺序一致
- 有索引:可以通过索引操作元素
- 可重复:存储的元素可以重复
List集合因为支持索引,所以多了很多索引操作的独特api,其他Collection的功能List也都继承了。
List集合遍历方式
- 迭代器
- 增强for循环
- Lambda表达式
- for循环(因为List集合存在索引)
ArrayList集合底层原理
- ArrayList底层是基于数组实现的:根据索引定位元素块,增删需要做元素的移位操作。
- 第一次创建集合并添加第一个元素的时候,在底层创建一个默认长度为10的数组。
LinkedList的特点
- 底层数据结构是双链表,查询慢,首尾操作的速度是极快的,所以多了很多首尾操作的特有API。
集合的并发修改异常问题
哪些遍历存在问题?
- 迭代器遍历集合且直接用集合删除元素的时候可能出现。
- 增强for循环遍历集合且直接用集合删除元素的时候可能出现。
哪种遍历且删除元素不出问题
- 迭代器遍历集合但是用迭代器自己的删除方法操作可以解决。
- 使用for循环遍历并删除元素不会存在这个问题。
Set系列集合
Set系列集合特点
- 无序:存取顺序不一致
- 不重复:可以去除重复
- 无索引:没有带索引的方法,所以不能使用普通for循环遍历,也不能通过索引来获取元素。
Set集合实现类特点
- HashSet : 无序、不重复、无索引。
- LinkedHashSet:有序、不重复、无索引。
- TreeSet:排序、不重复、无索引。
HashSet底层原理
HashSet集合底层采取哈希表存储的数据。哈希表是一种对于增删改查数据性能都较好的结构。
哈希表的组成
哈希值
是JDK根据对象的地址,按照某种规则算出来的int类型的数值
Object类的API
public int hashCode():返回对象的哈希值
对象的哈希值特点
- 同一个对象多次调用hashCode()方法返回的哈希值是相同的
- 默认情况下,不同对象的哈希值是不同的。
JDK8之前的,底层使用数组+链表组成
JDK8开始后,底层采用数组+链表+红黑树组成。
-
创建一个默认长度16的数组,数组名table
-
根据元素的哈希值跟数组的长度求余计算出应存入的位置(哈希算法)
-
判断当前位置是否为null,如果是null直接存入
-
如果位置不为null,表示有元素,则调用equals方法比较
-
如果一样,则不存,如果不一样,则存入数组
JDK 7新元素占老元素位置,指向老元素
JDK 8中新元素挂在老元素下面,当挂在元素下面的数据过多时,查询性能降低,从JDK8开始后,当链表长度超过8的时候,自动转换为红黑树。
注:如果希望Set集合认为2个内容一样的对象是重复的,必须重写对象的hashCode()和equals()方法
LinkedHashSet集合
- 有序、不重复、无索引。
- 这里的有序指的是保证存储和取出的元素顺序一致
- 原理:底层数据结构是依然哈希表,只是每个元素又额外的多了一个双链表的机制记录存储的顺序。
TreeSet集合
TreeSet集合概述和特点
- 不重复、无索引、可排序
- 可排序:按照元素的大小默认升序(有小到大)排序。
- TreeSet集合底层是基于红黑树的数据结构实现排序的,增删改查性能都较好。
- 注意:TreeSet集合是一定要排序的,可以将元素按照指定的规则进行排序。
TreeSet集合默认的规则
- 对于数值类型:Integer , Double,官方默认按照大小进行升序排序。
- 对于字符串类型:默认按照首字符的编号升序排序。
- 对于自定义类型如Student对象,TreeSet无法直接排序。
注:想要使用TreeSet存储自定义类型,需要制定排序规则
-
让自定义的类(如学生类)实现Comparable接口重写里面的compareTo方法来定制比较规则。
-
TreeSet集合有参数构造器,可以设置Comparator接口对应的比较器对象,来定制比较规则。
两种方式中,关于返回值的规则:
- 如果认为第一个元素大于第二个元素返回正整数即可。
- 如果认为第一个元素小于第二个元素返回负整数即可。
- 如果认为第一个元素等于第二个元素返回0即可,此时Treeset集合只会保留一个元素,认为两者重复。
注:如果TreeSet集合存储的对象有实现比较规则,集合也自带比较器,默认使用集合自带的比较器排序
/*
Book类
compareTo(T o) : 比较此对象与指定对象的顺序。
特别注意在重写Compareto方法时,注意排序 !!!
*/
public class Book implements Comparable{ // 第一步:实现Comparable接口
private Integer bookId;
private String bookName;
private String bookAuthor;
private Double price;
public int compareTo(Object o) { // 第二步:重写Comparable接口中的compareTo方法。
// 若想按照价格从小到大排序,则需要return = 1 ,即已存在数据大于待存数据
Book book = (Book)o;
int flag = (int)(this.getPrice() - book.getPrice());
return flag;
}
}
/**
compare有两个参数,o1和o2,如果
o1 < o2 返回负数,
o1 = o2 返回正数,
o1 > o2 返回正数。
*/
TreeSet treeSet = new TreeSet(new Comparator() { // 匿名内部类操作
public int compare(Object o1, Object o2) {
Book book1 = (Book)o1;
Book book2 = (Book)o2;
return (int)( book1.getPrice() - book2.getPrice());//升序:第二个减第一个,降序:第二个减第一个
}
});
Map集合体系
- Map集合的特点都是由键决定的。
- Map集合的键是无序,不重复的,无索引的,值不做要求(可以重复)。
- Map集合后面重复的键对应的值会覆盖前面重复键的值。
- Map集合的键值对都可以为null。
实现类特点
- HashMap:元素按照键是无序,不重复,无索引,值不做要求。(与Map体系一致)
- LinkedHashMap:元素按照键是有序,不重复,无索引,值不做要求。
- TreeMap:元素按照建是排序,不重复,无索引的,值不做要求。
Map集合 API
Map是双列集合的祖宗接口,它的功能是全部双列集合都可以继承使用的。
Map集合的遍历
键找值的方式遍历:先获取Map集合全部的键,再根据遍历键找值。
先获取Map集合的全部键的Set集合。遍历键的Set集合,然后通过键提取对应值。
键值对的方式遍历,把“键值对“看成一个整体
先把Map集合转换成Set集合,Set集合中每个元素都是键值对实体类型了。遍历Set集合,然后提取键以及提取值。
JDK 1.8开始之后的新技术:Lambda表达式。
public static void main(String[] args) {
Map<Integer,String> map = new HashMap<>();
for (int i = 0; i < 10; i++) {
map.put(i,i+"www");
}
//键找值的方式遍历:先获取Map集合全部的键,再根据遍历键找值
Set<Integer> keySet = map.keySet();
for (Integer key : keySet) {
System.out.println("key: " + key + ", value: " + map.get(key) );
}
//键值对的方式遍历,把“键值对“看成一个整体
Set<Map.Entry<Integer, String>> entries = map.entrySet();
for (Map.Entry<Integer, String> entry : entries) {
System.out.println("key: " + entry.getKey() + ", value: " + entry.getValue() );
}
//JDK 1.8开始之后的新技术:Lambda表达式。
map.forEach((k,y) -> {
System.out.println("key: " + k + ", value: " + y );
});
HashMap
HashMap特点
- HashMap是Map里面的一个实现类。特点都是由键决定的:无序、不重复、无索引
- 没有额外需要学习的特有方法,直接使用Map里面的方法就可以了。
- HashMap跟HashSet底层原理是一模一样的,都是哈希表结构,只是HashMap的每个元素包含两个值而已。
实际上:Set系列集合的底层就是Map实现的,只是Set集合中的元素只要键数据,不要值数据而已。
- 由键决定:无序、不重复、无索引。HashMap底层是哈希表结构的。
- 依赖hashCode方法和equals方法保证键的唯一。
- 如果键要存储的是自定义对象,需要重写hashCode和equals方法。
- 基于哈希表。增删改查的性能都较好。
LinkedHashMap
- 由键决定:有序、不重复、无索引。
- 这里的有序指的是保证存储和取出的元素顺序一致
- 原理:底层数据结构是依然哈希表,只是每个键值对元素又额外的多了一个双链表的机制记录存储的顺序。
TreeMap
TreeMap集合概述和特点
- 由键决定特性:不重复、无索引、可排序
- 可排序:按照键数据的大小默认升序(有小到大)排序。只能对键排序。
- 注意:TreeMap集合是一定要排序的,可以默认排序,也可以将键按照指定的规则进行排序
- TreeMap跟TreeSet一样底层原理是一样的。
TreeMap集合自定义排序规则
- 类实现Comparable接口,重写比较规则。
- 集合自定义Comparator比较器对象,重写比较规则。
按key排序
TreeMap<Integer,Integer> map1 = new TreeMap<Integer,Integer>(); //默认的TreeMap升序排列
TreeMap<Integer,Integer> map2= new TreeMap<Integer,Integer>(new Comparator<Integer>(){
/*
* int compare(Object o1, Object o2) 返回一个基本类型的整型,
* 返回负数表示:o1 小于o2,
* 返回0 表示:o1和o2相等,
* 返回正数表示:o1大于o2。
*/
public int compare(Integer a,Integer b){
return b-a;
}
});
按value排序
Map<String,String> map = new TreeMap<String,String>();
map.put("a", "dddd");
map.put("d", "aaaa");
map.put("b", "cccc");
map.put("c", "bbbb");
List<Entry<String, String>> list = new ArrayList<Entry<String, String>>(map.entrySet());
Collections.sort(list,new Comparator<Map.Entry<String,String>>() {
//升序排序
public int compare(Entry<String, String> o1, Entry<String, String> o2) {
return o1.getValue().compareTo(o2.getValue());
}
});
for (Entry<String, String> e: list) {
System.out.println(e.getKey()+":"+e.getValue());
}
//思路:本例是想对Map元素按value值从大到小排序
//但是 Collections.sort()只支持List,不支持Map
//考虑使用:Arrays.sort()
//说明:
//(1)Arrays.sort()是针对数组排序,默认是从小到大升序排序
//(2)如果要对对象数组进行排序,也需要自定义Comparator比较器
//最终思路:
//(1)通过 Map.entrySet()获取键值对对象的集合(Set集合)
//(2)然后将Set集合转换为数组(集合支持toArray()方法转为数组)
//(3)最后使用Arrays.sort(),需要自定义Comparator比较器
//创建键值对对象数组,并将map键值对对象转存
Map.Entry<Integer, Integer>[] entries = new Map.Entry[map.size()];
map.entrySet().toArray(entries);
//排序:Arrays.sort(数组,自定义比较器)
Arrays.sort(entries, new Comparator<Map.Entry<Integer, Integer>>() {
@Override
public int compare(Map.Entry<Integer, Integer> o1,
Map.Entry<Integer, Integer> o2) {
return o2.getValue() - o1.getValue(); //套路:第一个减第二个是升序,第二个减第一个是降序
}
});
//取键值对对象数组前k个的value值,转存到数组返回
int[] result = new int[k];
for (int i = 0; i < k; i++) {
result[i] = entries[i].getKey();
}