Java高级—集合
首先建议看一下前一章的集合框架
从这一章开始,我们正式进入Java高级技术部分
集合:Collection 无序可重复
文章目录
- Java高级---集合
- 一、List :有序,可重复
- 二、Set :无序,不可重复
- 三、Map:键值对
- Map怎么通过Value进行比较呢?
一、List :有序,可重复
1、ArrayList:一维数组作为底层存储
特点:遍历、随机访问快、修改方便,其他慢
这里讲一下插入方法:add(Obj)和add(1,Obj)
插入数据元素的底层实现原理:【这里建议看Java源码】
插入前判断容量是否越界,如果越界,则扩容。
扩容后比数组最大长度大,则是int最大值
没有达到数组最大长度,就是数组最大长度
正常情况:扩容1.5倍,如果不够,则直接把当前长度赋值给数组新长度
如果是新数组,与10比较,比10大,大的直接赋值给数组长度
不需要扩容或扩容完成则直接插入
那接下来,我们看一下ArrayList如何使用:
1.插入
ArrayList a = new ArrayList();//声明并分配空间
//元素的插入
a.add("abc");
a.add("123");
a.add("唯颂科技");
a.add("2s留#%");
a.add("abc");
a.add(1,"shall"); //指定位置插入,元素后移
System.out.println(a);//因为重写了toString方法,所以可以直接输出
结果显示
2.删除
if(a.contains("123")) {
a.remove("123");//删除对象
}
a.remove(1);//删除指定下标元素,下标从0开始
两种方式删除都可以,如果list中没有你要删除的元素,你删除了也不会报错
3.查找
a.get(2); //只有根据下标指定查找元素一个查询方法
4.修改
a.set(1,"溜溜球"); //指定位置修改
5.输出(常见的三种输出方式)
//1.普通for
for (int i = 0; i < a.size(); i++) {
System.out.println(a.get(i));//获取当前元素
}
//2.增强for
for(Object s:a){ //由于上面一开始没有定义类型,所以这里写了Object
System.out.println(s);
}
//3.迭代器
Iterator it = a.iterator();
while (it.hasNext()){
System.out.println(it.next());//一次循环只能使用一次next(),不然会出错
//如果要用多次,赋值给变量即可
}
ArrayList常用方法
2、LinkedList:以链表作为底层存储
特点:增加、删除快,其他慢
其实有点数据结构基础的都知道这二者的特点LinkedList中的方法和ArrayList几乎一样,只不过多一些头结点、尾节点的操作方法
那些一样的我就不写了,这里主要讲一下没有的:
然后:
push:在头结点处插入元素
//jdk1.8源码
public void push(E e) {
addFirst(e);
}
pop:删除头结点
//jdk1.8源码
public E pop() {
return removeFirst();
}
peek:返回头结点
//jdk1.8源码
public E peek() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
/**
* Retrieves, but does not remove, the head (first element) of this list.
*
* @return the head of this list
* @throws NoSuchElementException if this list is empty
* @since 1.5
*/
peekFirst:返回头结点
peekLast:返回尾结点
poll:返回头结点,只不过是unlinked
//jdk1.8源码
public E poll() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
/**
* Retrieves and removes the head (first element) of this list.
*
* @return the head of this list
* @throws NoSuchElementException if this list is empty
* @since 1.5
*/
pollFirst:返回头结点
pollLast:返回尾结点
offer:插入元素
//jdk1.8源码
public boolean offer(E e) {
return add(e); //其实就是插入
}
offerFirst:在头结点插入元素
offerLast:在尾结点插入元素
是不是感觉LinkedList里面一堆功能重复的方法
3、Vector:ArrayList的线程安全版本
由于使用比较少,所以在这里就不赘述了------------------------------------------------------------------------------------------------------
最后,做个小小的练习:
Book类
/* 定义图书类Book,具有属性账号id,书名name、作者author 和价格price,
* 在创建图书对象时要求通过构造器进行创建,一次性将四个属性全部赋值,
* 1)要求账户属性是int型,名称是String型,作者是String型,价格是double,
* 请合理进行封装。
* 2)在Book类,添加toString方法,要求返回 图书信息字符串,使用\t隔开各信息
* 3)要求定义一个图书馆Library类,在图书馆类中添加一个集合用于保存多本图书
* 4)在图书馆类中要求能够新增图书
* 5)在图书馆类中要求可以查看所有添加过的图书
* 6)不允许添加重复的图书(如果账号id和书名name相同,则认为两本书是相同的)
* (重写equals方法)
*/
public class Book {
private int id;
private String name;
private String author;
private double price;
public Book(int id, String name, String author, double price) {
this.id = id;
this.name = name;
this.author = author;
this.price = price;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
@Override
public String toString() {
return "Book{" +
"书籍编号=" + id +"\t"+
"书籍名称=" + name + "\t"+
"书籍作者=" + author + "\t" +
"书籍价格=" + price +
'}'+"\n";
}
public boolean equals(Book b){
if(null==this.name||0>=this.id||b.getId()<=0||null==b.getName()){
return false;
}
return this.name.equals(b.getName())&&this.id==b.getId();
}
}
Library类
public class Library {
public static void main(String[] args) {
List<Book> list = new ArrayList();
Scanner sc = new Scanner(System.in);
String s = "";
do{
System.out.println("需要添加书籍吗?(y/n)");
s = sc.next();
if("y".equals(s)){
System.out.println("请输入图书编号:");
int id = sc.nextInt();
System.out.println("请输入图书名称:");
String name = sc.next();
System.out.println("请输入图书作者:");
String author = sc.next();
System.out.println("请输入图书价格:");
double price = sc.nextDouble();
Book temp = new Book(id,name,author,price);
if(list.size()==0){ //为空时,直接插入
list.add(temp);
}
int flag = 1;
for (int i = 0; i < list.size(); i++) {
if(!list.get(i).equals(temp)) {//不同
flag = 2;
}else {
flag = 1;
break;
} //相同
}
if(flag==2)
list.add(temp);
}else
break;
}while ("y".equals(s));
System.out.println(list);
}
}
结果显示
二、Set :无序,不可重复
1、HashSet:是以HashMap的key值的Hash码来进行存储(保证唯一,理论上)
Set中的方法和List几乎一样,所以就不写了。这里就看一次add方法
可以点进Set的add方法看一看
具体使用
HashSet hashSet = new HashSet(); //先不使用泛型
hashSet.add("abc");
hashSet.add(123);
hashSet.add("123");
hashSet.add("123"); //add源码是使用equals进行比较,只输出一个
hashSet.add(new String("123"));
System.out.println(hashSet);
hashSet.remove("123");
System.out.println(hashSet);
Iterator it = hashSet.iterator(); //这里不能是用fori形式,可以foreach,因为set没有下标
while (it.hasNext()){
System.out.println(it.next());
}
ArrayList al = new ArrayList();
al.addAll(hashSet);//把set扔到list中
结果显示
2、TreeSet是一个有序的集合,它的作用是提供有序的Set集合。它继承了AbstractSet抽象类,实现了NavigableSet,Cloneable,Serializable接口。TreeSet是基于TreeMap实现的,TreeSet的元素支持2种排序方式:自然排序或者根据提供的Comparator进行排序
TreeSet的底层实际使用的存储容器就是TreeMap。对于TreeMap而言,它采用一种被称为”红黑树”的排序二叉树来保存Map中每个Entry。每个Entry被当成”红黑树”的一个节点来对待。
这里使用就和上面的HashSet一样,所以就不写了。
三、Map:键值对
key不能重复,value可以。
包含接口Entry<K,V>
在HashMap中使用Node结点实现Entry键值对
Node结点用next属性实现单链表
Map底层:数组+单向链表,1.8之后加入红黑树【红黑树是一种自平衡二叉树,通过左旋和右旋进行平衡】
数组+链表—>红黑树 的条件
一个是链表的长度达到8个,一个是数组的长度达到64个,转用红黑树进行底层存储
为什么是节点数到8个就转成红黑树?
官方解释:理想情况下使用随机的哈希码,容器中节点分布在hash桶中的频率遵循泊松分布,按照泊松分布的计算公式计算出了桶中元素个数和概率的对照表,可以看到链表中元素个数为8时的概率已经非常小,再多的就更少了,所以原作者在选择链表元素个数时选择了8,是根据概率统计而选择的
泊松分布(Poisson)、二项式分布都属于 离散分布
正态分布(Normal)属于 连续分布
二项式分布,样本无穷大—>泊松分布,样本无穷大—>正态分布
为什么使用红黑树
因为不仅查询速度快,虽然不及平衡二叉树,但是也没差多少,相反,插入删除的效率却大大优于平衡二叉树
Map中HashMap(不安全,会死循环,但是快)最为典型,至于什么HashTable(线程安全,但是慢),concurrentHashMap【分段锁,解决二者问题】等这里不重点讲了。
HashMap
默认容量:16,负载因子:0.75
首先是方法,和List几乎一样,只不过插入和修改不同,在Map中插入和修改都是put,put相同key的时候,value就会覆盖,原来内容会因为失去指向而在内存当中,等待Java垃圾回收机制清理。
具体可以看下面代码:
public class TestHashMap {
public static void main(String[] args) {
HashMap<Integer,String> map = new HashMap(); //注意泛型,这里写不写无所谓
map.put(1,"zxx"); //增加:key-value
map.put(3,"Jad");
map.put(3,"厉害的感觉"); //修改:一个key只能有一个value
map.replace(1,"Lif"); //修改,本质还是put,可以进去看源码
map.remove(2); //删除
map.remove(2,"dg");
System.out.println(map.get(3)); //输出
System.out.println(map);
//4种遍历
Iterator it = map.entrySet().iterator(); //键值对:entrySet
while (it.hasNext()){
System.out.println(it.next());
}
Iterator iter = map.keySet().iterator(); //key
while (iter.hasNext()){
System.out.println(iter.next());
}
Iterator iters = map.values().iterator(); //value
while (iters.hasNext()){
System.out.println(iters.next());
}
map.forEach((key,value)->{ //lamba表达式
System.out.println(key);
System.out.println(value);
});
}
}
【注意:keySet返回的是Set类型,value返回的是Collection类型】
Map常用方法
举个例子:
要求:
学员应聘至外企工作,每个学员都会有一个英文名称,对应该学员对象。请实现通过英文名称,获得该学员对象的详细信息
学员属性包括姓名、性别、成绩
学生类:
public class Student implements Comparable<Student>{
private String name;
private String gender;
private int score;
public Student(String name, String gender) {
this.name = name;
this.gender = gender;
}
public Student(String name, String gender, int score) {
this.name = name;
this.gender = gender;
this.score = score;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public double getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
@Override
public String toString() {
return "姓名:" + name + "\t" +
"性别:" + gender + "\t"+
"成绩:" + score ;
}
}
测试类:
public class TestStudent {
public static void main(String[] args) {
Student s1 = new Student("Jack","男",98);
Student s2 = new Student("Alis","女",87);
Student s3 = new Student("Louis","女",59);
Student s4 = new Student("Tom","男",88);
HashMap<String,Student> map = new HashMap();
map.put("杰克",s1);
map.put("爱丽丝",s2);
map.put("路易斯",s3);
map.put("汤姆",s4);
//2中输出方式
// Collection c = map.values();
// for(Object s: c){
// System.out.println(s);
// }
// Scanner sc = new Scanner(System.in); //按输入进行输出
// System.out.println("请输入名称");
// String name = sc.next();
// if(map.containsKey(name)) {
// System.out.println(name+map.get(name).toString());
// }else {
// System.out.println("抱歉没有这个人");
// }
}
}
Map怎么通过Value进行比较呢?
通过转成其他类型的集合,例如转成List,自定义比较器,利用Collections.sort(List,Comparator)进行排序
/**
* @Author shall潇
* @Date 2021/1/29
* @Description 由于Collections类只给list,set用,Map不能直接用,所以得转成list
*/
public class MapValueSort {
public static void main(String[] args) {
Map<String,Integer> map = new TreeMap<>();
//插入数据
map.put("zzz",23);
map.put("Tom",53);
map.put("Jack",63);
map.put("Luis",27);
map.put("Shall",34);
//自定义比较规则
Comparator<Map.Entry<String,Integer>> comparator = new Comparator<Map.Entry<String, Integer>>() {
@Override
public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {
return o1.getValue()-o2.getValue(); //年龄升序
}
};
//放入List
List<Map.Entry<String,Integer>> list = new LinkedList(map.entrySet());
Collections.sort(list,comparator); //两个参数:list集合,比较器对象
//输出
for (int i = 0; i < list.size(); i++) {
System.out.print(list.get(i).getKey()+" = "+list.get(i).getValue()+"\t");
}
}
}
结果显示