一丶 Collection
1.集合和数组的区别
数组:
1.可以存储多个元素
2.数组的长度是固定的
3.数组即可以存储基本类型的数据,又可以存储引用数据类型的数据
int[] double[] String[] Student[]
集合:
1.集合也可以存储多个元素
2.集合的长度是可以变化的
3.只能存储引用数据类型的数据
ArrayList ArrayList
2.集合常用类的继承体系
3.Collection 常用功能
/*
java.util.Collection:接口
Collection 层次结构 中的根接口。Collection 表示一组对象,这些对象也称为 collection 的元素。
一些 collection 允许有重复的元素,而另一些则不允许。一些 collection 是有序的,而另一些则是无序的。
Collection接口中定义了所有单列集合共性的成员方法,所有的单列集合都可以使用
共性的成员方法:
- public boolean add(E e): 把给定的对象添加到当前集合中 。
- public boolean remove(E e): 把给定的对象在当前集合中删除。
- public boolean contains(Object obj): 判断当前集合中是否包含给定的对象。
- public boolean isEmpty(): 判断当前集合是否为空。
- public int size(): 返回集合中元素的个数。
- public Object[] toArray(): 把集合中的元素,存储到数组中
- public void clear() :清空集合中所有的元素。
*/
public class Demo01Collection {
public static void main(String[] args) {
//创建Collection集合对象:多态
//Collection<String> coll = new ArrayList<>();
Collection<String> coll = new HashSet<>();
System.out.println(coll);//[]
/*
- public boolean add(E e)往集合中添加元素
返回值:boolean
添加成功,返回true,添加元素百分之百都会成功
添加失败,返回false
*/
boolean b1 = coll.add("张三");
System.out.println("b1:"+b1);//b1:true
coll.add("李四");
coll.add("王五");
coll.add("张三");
coll.add("赵六");
coll.add("田七");
System.out.println(coll);//[张三, 李四, 王五, 张三, 赵六, 田七] 打印对象名,不是地址,重写了toString方法
/*
- public boolean remove(E e): 移除集合中指定的元素
返回值:boolean
集合中存在指定的元素,移除元素,返回true;如果集合中有相同的元素,只会移除第一个
集合中不存在指定的元素,对集合没有影响,返回false
*/
boolean b2 = coll.remove("张三");
System.out.println("b2:"+b2);//b2:true
System.out.println(coll);//[李四, 王五, 张三, 赵六, 田七]
boolean b3 = coll.remove("赵四");
System.out.println("b3:"+b3);//b3:false
System.out.println(coll);//[李四, 王五, 张三, 赵六, 田七]
/*
- public boolean contains(Object obj): 判断当前集合中是否包含指定的元素
集合中包含指定的元素,返回true
集合中不包含指定的元素,返回false
*/
boolean b4 = coll.contains("柳岩");
System.out.println("b4:"+b4);//b4:false
boolean b5 = coll.contains("田七");
System.out.println("b5:"+b5);//b5:true
/*
- public boolean isEmpty(): 判断当前集合是否为空。
返回值:boolean
集合中没有元素,是空的,返回true
集合中有元素,返回false
*/
boolean b6 = coll.isEmpty();
System.out.println("b6:"+b6);//b6:false
/*
- public int size(): 返回集合中元素的个数。
*/
int size = coll.size();
System.out.println("size:"+size);//size:5
/*
- public Object[] toArray(): 把集合中的元素,存储到数组中
*/
Object[] arr = coll.toArray();
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
/*
- public void clear() :清空集合中所有的元素。
注意:
此方法只是清空集合,集合还在,还可以使用
*/
coll.clear();
System.out.println(coll);//[]
System.out.println(coll.isEmpty());//true
}
}
4 Collections常用功能2
/*
java.util.Collections:集合的工具类,里边的方法都是静态的,通过类名.方法名(参数)可以直接使用
常用方法:
- public static void shuffle(List<?> list):打乱集合中元素顺序。
- public static <T> void sort(List<T> list):将集合中元素按照默认规则排序(升序:小->大)。
- public static <T> void sort(List<T> list,Comparator<? super T> ):根据指定比较器产生的顺序对指定集合进行排序。
注意:
以上3个方法,参数都必须传递List接口下的集合
*/
public class Demo01Collections {
public static void main(String[] args) {
//创建List集合对象
ArrayList<Integer> list01 = new ArrayList<>();
list01.add(1);
list01.add(3);
list01.add(2);
list01.add(4);
System.out.println(list01);//[1, 3, 2, 4]
//使用Collections集合工具类中的方法sort,对List集合按照默认规则排序(升序)
Collections.sort(list01);
System.out.println(list01);//[1, 2, 3, 4]
//使用Collections集合工具类中的方法shuffle,打乱集合中元素的顺序
Collections.shuffle(list01);
System.out.println(list01);//[2, 3, 1, 4]
ArrayList<String> list02 = new ArrayList<>();
list02.add("ab");
list02.add("aa");
list02.add("12");
list02.add("AB");
list02.add("CC");
System.out.println(list02);//[ab, aa, 12, AB, CC]
//使用Collections集合工具类中的方法sort,对List集合按照默认规则排序(升序:编码表的顺序 ASCII)
Collections.sort(list02);//[12,AB,CC,aa, ab] 0:48 A:65 a:97
System.out.println(list02);//[12, AB, CC, aa, ab]
}
}
5 Comparator比较器
/*
- public static <T> void sort(List<T> list,Comparator<? super T> ):根据指定比较器产生的顺序对指定集合进行排序。
参数:
java.util.Comparator<T>接口:对集合进行排序的比较器
强行对某个对象 collection 进行整体排序 的比较函数。
Comparator接口中的抽象方法:
int compare(T o1, T o2) 比较用来排序的两个参数。
参数:
T o1, T o2:依次比较的集合中的元素[1,3,2,4]
比较的规则:
升序:o1-o2
降序:o2-o1
两个元素相等:o1==o2
*/
public class Demo02Collections {
public static void main(String[] args) {
//创建List集合对象
ArrayList<Integer> list01 = new ArrayList<>();
list01.add(1);
list01.add(3);
list01.add(2);
list01.add(4);
System.out.println(list01);//[1, 3, 2, 4]
//使用Collections集合工具类中的方法sort,按照比较器产生的规则对集合中的元素进行排序
Collections.sort(list01, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
//升序
return o1-o2;
}
});
System.out.println(list01);//[1, 2, 3, 4]
Collections.sort(list01, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
//降序
return o2-o1;
}
});
System.out.println(list01);//[4, 3, 2, 1]
}
}
/*
- public static <T> void sort(List<T> list,Comparator<? super T> ):根据指定比较器产生的顺序对指定集合进行排序。
*/
public class Demo03Collections {
public static void main(String[] args) {
ArrayList<Person> list = new ArrayList<>();
list.add(new Person("zhangsan",18));
list.add(new Person("awangwu",21));
list.add(new Person("lisi",19));
list.add(new Person("btianqi",21));
list.add(new Person("zhaoli",12));
//使用Collections集合工具类中的方法sort,根据比较器产生的规则,对List集合进行排序(年龄升序排序)
Collections.sort(list, ( o1, o2)-> {
//年龄升序排序
return o1.getAge()-o2.getAge();
});
System.out.println(list);
//按照两个人的年龄升序排序,如果两个人的年龄相同,按照姓名的首字母降序排序
Collections.sort(list, new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
//按照两个人的年龄升序排序
int a = o1.getAge()-o2.getAge();
if(a==0){//如果两个人的年龄相同
//按照姓名的首字母降序排序
a = o2.getName().charAt(0)-o1.getName().charAt(0);
}
return a;
}
});
System.out.println(list);
}
}
6.Collections集合工具类中的方法addAll
/*
java.util.Collections集合工具类中的方法:
static <T> boolean addAll(Collection<T c, T... elements) 给指定的集合添加多个元素 */
public class Demo05Collections {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
//list.add(1);
//list.add(2);
//list.add(3);
//list.add(4);
//list.add(5);
Collections.addAll(list,1,2,3,4,5);
System.out.println(list);
}
}
二 丶Iterator迭代器
1.迭代器概述
/*
迭代器
迭代器的由来:
集合有很多种,每种集合的数据结构是不同的,集合存储元素和取出元素的方式也不同,
我们不可能为每种集合都定义一种取出元素的方式,浪费,可以使用迭代器,是集合通用的取出元素的方法
迭代器的原理:
判断集合中还有没有元素,有就取出元素;在判断集合中还有没有元素,有在取出元素,一直到集合中没有元素为止
这种取出元素的方式叫迭代
java.util.Iterator<E>:描述迭代器的接口
对 collection 进行迭代的迭代器。
Iterator迭代器中常用的方法:
boolean hasNext() 如果仍有元素可以迭代,则返回 true。判断集合中是否含有元素
有返回true,没有返回false
E next() 返回迭代的下一个元素。 取出集合中的下一个元素
Iterator接口无法创建对象使用,使用Iterator接口的实现类对象,Iterator接口的实现类是每个集合的内部类
我们可以使用Collection接口中的方法iterator()获取Iterator接口的实现类对象
Iterator<E> iterator() 返回在此 collection 的元素上进行迭代的迭代器。
注意:
iterator()返回的就是迭代器的实现类对象,我们无需关注返回的是哪个实现类对象,我们只需要会使用Iterator接口
来接收这个实现类对象即可,这种不关注实现类,只关注接口的编程方式,叫面向接口编程
----------------------------------------------------------------
迭代器的使用步骤(重点):
1.创建集合对象,往集合中添加元素
2.使用Collection接口中的方法iterator(),获取迭代器的实现类对象
3.使用Iterator接口中的方法hasNext和next遍历集合
*/
2.迭代器的基本使用
public class Demo01Iterator {
public static void main(String[] args) {
//1.创建集合对象,往集合中添加元素
Collection<String> coll = new ArrayList<>();
coll.add("詹姆斯");
coll.add("科比");
coll.add("姚明");
coll.add("欧文");
coll.add("艾弗森");
//2.使用Collection接口中的方法iterator(),获取迭代器的实现类对象
//注意:迭代器的泛型跟着集合走,集合是什么泛型,迭代器就是什么泛型
//多态 接口 = 实现类对象
Iterator<String> it = coll.iterator();
//3.使用Iterator接口中的方法hasNext和next遍历集合
/*
我们发现使用迭代器取出集合中元素是一个重复的过程,所以可以使用循环优化
不知道集合中有多少个元素,使用while循环
while循环结束的条件:hasNext方法返回false
*/
while (it.hasNext()){//判断集合中是否还有元素
//有元素,取出元素
String s = it.next();
System.out.println(s);
}
System.out.println("------------------------------------");
for(Iterator<String> it2 = coll.iterator();it2.hasNext();){
String s = it2.next();
System.out.println(s);
}
b = it.hasNext();
System.out.println(b);//true
s = it.next();
System.out.println(s);//科比
b = it.hasNext();
System.out.println(b);//false
s = it.next();//没有元素,在取出元素,就会抛出NoSuchElementException:没有这个元素异常*/
}
}
3.迭代器的并发修改异常
在使用迭代器遍历集合的时候,对集合长度进行了修改,迭代器就会抛出并发修改异常
满足条件:
1.并发:遍历和修改必须同时进行
2.修改集合长度:添加或删除元素
/*
迭代器的并发修改异常:在使用迭代器遍历集合的过程中,对集合的长度进行了修改,迭代器就会抛出并发修改异常
ConcurrentModificationException
注意:
1.并发:遍历和修改必须同时进行
2.修改集合的长度:添加元素,删除元素
解决方案:
1.遍历集合的同时,不对集合进行长度修改
2.Iterator迭代器有一个子接口ListIterator,在ListIterator中定义了往集合中添加元素和删除元素的方法
public interface ListIterator<E>extends Iterator<E>
void add(E e) 往集合中添加元素
void remove() 移除next方法返回的元素
注意:
1.如果要使用迭代器中的add|remove方法,往集合中添加元素|删除元素
就相当于集合和迭代器商量好了,可以添加和删除,迭代器就不会抛出并发修改异常了
2.ListIterator迭代器只能遍历List接口下的集合,Set接口下的集合不能使用
*/
public class Demo02Iterator {
public static void main(String[] args) {
//创建集合对象,给集合添加元素
ArrayList<String> list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
list.add(null);
list.add("ccc");
list.add("ddd");
//使用迭代器遍历集合
//获取迭代器
Iterator<String> it = list.iterator();
//使用迭代器中的方法hasNext和next遍历集合
while (it.hasNext()){
String s = it.next();
System.out.println(s);
/*
增加一个判断,如果取出的元素s是"ccc"
就给集合添加一个元素"itcast"
注意:判断时要把已知的值写在前边,防止空指针异常
*/
if("ccc".equals(s)){
//list.add("itcast");
}
}
System.out.println("--------------------------------------------");
//使用List接口中的方法listIterator获取ListIterator迭代器的实现类对象
ListIterator<String> lit = list.listIterator();
//使用迭代器中的方法hasNext和next遍历集合
while (lit.hasNext()){
String s = lit.next();
System.out.println(s);
/*
增加一个判断,如果取出的元素s是"ccc"
就给集合添加一个元素"itcast"
注意:判断时要把已知的值写在前边,防止空指针异常
*/
if("ccc".equals(s)){
//lit.add("itcast");//迭代器往集合中添加元素的方法
lit.remove();//删除的是next方法取出的元素
}
}
System.out.println(list);//[aaa, bbb, null, ccc, itcast, ddd]
}
}
4.增强for循环
/*
增强for循环:
是JDK1.5之后出现的新特性
使用for循环的方式,对迭代器进行简化,增强for的内部是对迭代器进行封装
Collection接口有一个父接口Iterable
public interface Collection<E>extends Iterable<E>
实现这个接口允许对象成为 "foreach" 语句的目标。
Collection继承了Iterable接口,就可以使用增强for循环
Collection接口所有的实现类,都可以使用增强for循环
增强for循环的格式:
for(集合|数组中元素的数据类型 变量名: 集合|数组){
sout(变量名);
}
*/
三丶 List接口
1.List接口介绍
/*
java.util.List<E>接口 extends Collection<E>接口
List接口特点:
1.是一个有序的集合:存储元素和取出元素的顺序是一致的
2.是允许重复元素的集合
3.含有一些带索引的方法
List接口特有的含有索引的方法:
- public void add(int index, E element): 将指定的元素,添加到该集合中的指定位置上。
- public E get(int index):返回集合中指定位置的元素。
- public E remove(int index): 移除列表中指定位置的元素, 返回的是被移除的元素。
- public E set(int index, E element):用指定元素替换集合中指定位置的元素,返回值的更新前的元素。
注意:
使用带索引的方法,必须防止索引越界异常
IndexOutOfBoundsException: 索引越界异常,一般集合会报
StringIndexOutOfBoundsException: 字符串索引越界异常
ArrayIndexOutOfBoundsException: 数组索引越界异常
*/
2.ArrayList集合
java.util.ArrayList<E>集合 implements List<E>接口
List 接口的大小可变数组的实现。
ArrayList集合底层采用的就是一个数组结构:查询快,增删慢
ArrayList集合底层源码:
transient Object[] elementData = {};
add方法:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // 把原来数组长度+1
elementData[size++] = e;
return true;
}
3.LinkedList集合
/*
java.util.LinkedList<E>集合 implements List<E>接口
LinkedList底层是一个双向链表:查询慢,增删快
双向:是一个有序的集合
LinkedList集合中有一些操作首尾元素的方法:
- public void addFirst(E e):将指定元素插入此列表的开头。
- public void push(E e):将元素推入此列表所表示的堆栈。
- public void addLast(E e):将指定元素添加到此列表的结尾。
- public E getFirst():返回此列表的第一个元素。
- public E getLast():返回此列表的最后一个元素。
- public boolean isEmpty():如果列表不包含元素,则返回true。
- public E removeFirst():移除并返回此列表的第一个元素。
- public E pop():从此列表所表示的堆栈处弹出一个元素。
- public E removeLast():移除并返回此列表的最后一个元素。
注意:
要使用LinkedList集合中特有的方法,不能使用多态
*/
public class DemoLinkedList {
/*
- public E removeFirst():移除并返回此列表的第一个元素。
- public E pop():从此列表所表示的堆栈处弹出一个元素。此方法等效于 removeFirst()。
- public E removeLast():移除并返回此列表的最后一个元素。
*/
private static void show04() {
LinkedList<String> linked = new LinkedList<>();
linked.add("aaa");
linked.add("bbb");
linked.add("ccc");
linked.add("ddd");
System.out.println(linked);//[aaa, bbb, ccc, ddd]
//String first = linked.removeFirst();
String first = linked.pop();
System.out.println("first:"+first);//first:aaa
String last = linked.removeLast();
System.out.println("last:"+last);
System.out.println(linked);//[bbb, ccc]
}
/*
- public E getFirst():返回此列表的第一个元素。
- public E getLast():返回此列表的最后一个元素。
- public boolean isEmpty():如果列表不包含元素,则返回true。
*/
private static void show03() {
LinkedList<String> linked = new LinkedList<>();
linked.add("aaa");
linked.add("bbb");
linked.add("ccc");
linked.add("ddd");
//linked.clear();//清空集合
//为了防止NoSuchElementException:没有元素异常,增加一个判断,不是集合不是空在获取
if(!linked.isEmpty()){//return size() == 0;
String first = linked.getFirst();
System.out.println("first:"+first);//first:aaa
String last = linked.getLast();
System.out.println("last:"+last);//last:ddd
}
if(linked.size()!=0){
String first = linked.getFirst();
System.out.println("first:"+first);//first:aaa
String last = linked.getLast();
System.out.println("last:"+last);//last:ddd
}
}
private static void show02() {
LinkedList<String> linked = new LinkedList<>();
linked.addFirst("1");
linked.addFirst("2");
linked.addFirst("3");
linked.addLast("a");
linked.addLast("b");
linked.addLast("c");
linked.addLast("d");
System.out.println(linked);//[3, 2, 1, a, b, c, d]
}
/*
- public void addFirst(E e):将指定元素插入此列表的开头。
- public void push(E e):将元素推入此列表所表示的堆栈。此方法等效于 addFirst(E)。
- public void addLast(E e):将指定元素添加到此列表的结尾。
*/
private static void show01() {
LinkedList<String> linked = new LinkedList<>();
linked.add("aaa");
linked.add("bbb");
linked.add("ccc");
linked.add("ddd");
System.out.println(linked);//[aaa, bbb, ccc, ddd]
//- public void addFirst(E e):将指定元素插入此列表的开头。
//linked.addFirst("www");
linked.push("www");
System.out.println(linked);//[www, aaa, bbb, ccc, ddd]
//- public void addLast(E e):将指定元素添加到此列表的结尾。 等效于add()
linked.addLast("com");
System.out.println(linked);//[www, aaa, bbb, ccc, ddd, com]
}
}
4.Vector集合
java.util.Vector<E>集合
Vector是JDK1.0时期就存在的单列集合,Collection接口下边的集合(ArrayList,LinkedList)1.2之后出现的
Vector 类可以实现可增长的对象数组。底层和ArrayList集合一样也是一个数组结构
Vector集合在1.0时期的方法:
void addElement(E obj) 将指定的组件添加到此向量的末尾,将其大小增加 1。
Enumeration<E> elements() 返回此向量的组件的枚举。
Enumeration:就是1.0时期的迭代器==>向量枚举
boolean hasMoreElements() 判断集合中是否还有元素
E nextElement() 取出集合中的元素
与新 collection 实现不同,Vector 是同步的。
同步技术:可以保证多线程的安全
同步:效率低下
四丶 Set接口
1.Set接口介绍
java.util.Set<E>接口 extends Collection<E>接口
Set接口的特点:
1.不允许存储重复的元素
2.不包含带索引的方法
2.HashSet集合基本使用
/*
java.util.HashSet<E>集合 implements Set<E>接口
HashSet集合的特点:
1.不允许存储重复的元素
2.不包含带索引的方法(不能使用普通for循环遍历Set集合)
3.是一个无序的集合(存储的元素和取出的元素顺序有可能不一致)
4.底层是一个哈希表
JDK1.8之前:数组+单向链表
JDK1.8之后:数组+单向链表|红黑树(提高查询的效率)
*/
public class Demo01Set {
private static void show02() {
HashSet<String> set = new HashSet<>();
set.add("aaa");
set.add("bbb");
set.add("aaa");
set.add("ccc");
//使用增强for循环遍历Set集合
for (String s : set) {
System.out.println(s);
}
}
private static void show01() {
HashSet<Integer> set = new HashSet<>();
//往集合中添加元素
set.add(1);
set.add(3);
set.add(2);
set.add(1);
set.add(4);
//遍历Set集合,使用迭代器
Iterator<Integer> it = set.iterator();
while (it.hasNext()){
Integer s = it.next();
System.out.println(s);
}
}
}
3.哈希值
通过一定的哈希算法(典型的有MD5,SHA-1等),将一段较长的数据映射为较短小的数据,这段小数据就是大数据的哈希值。他有这样一个特点,他是唯一的,一旦大数据发生了变化,哪怕是一个微小的变化,他的哈希值也会发生变化。
hash值是通过一个f(hash)计算出一个整数,然后当查找一个数据或者字符串的时候就将计算出来的整数进行对比,只用看整数相不相等就可以,而不用去暴力O(n)(如果是要对比n个数那就是O(n^2)了,所以,hash值就是为查找算法,提供一个优秀的O(1)复杂度的解决方案(hash的开销主要是对函数进行计算)
/*
哈希值:
是一个十进制的整数,由操作系统随机给出,我们打印对象的地址值,使用的就是哈希值
Object类中有获取对象哈希值的方法:
int hashCode() 返回该对象的哈希码值。
hashCode方法的底层源码:
public native int hashCode();
native:调用的是操作系统底层的方法,我们看不到
*/
public class Demo02HashCode {
public static void main(String[] args) {
//Person类默认继承了Object类,所以可以使用Object类中的hashCode方法
Person p1 = new Person();
int h1 = p1.hashCode();
System.out.println(h1);//1163157884==>1
Person p2 = new Person();
int h2 = p2.hashCode();
System.out.println(h2);//1956725890==>1
/*
Object类的toStirng方法
String toString() 返回该对象的字符串表示。
toString方法的底层源码:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
Integer.toHexString:把十进制的整数转换为十六进制
}
但是哈希值是系统随机给出的,不是对象在内存中的实际物理地址,是虚拟出来的
*/
//直接打印对象的名字,就是调用对象的toString方法,对象的地址值就是对象的哈希值
System.out.println(p1.toString());// com.demo03Set.Person@4554617c==> @1
System.out.println(p2.toString());// com.demo03Set.Person@74a14482==> @1
System.out.println(p1==p2);//==引用数据类型比较的是对象的地址值 false
}
}
4.String类的哈希值
/*
String类重写了Object类中的hashCode方法
相同的字符串,返回的哈希值是一样的
*/
public class Demo03StringHashCode {
public static void main(String[] args) {
String s1 = new String("abc");
String s2 = new String("abc");
System.out.println(s1.hashCode());//96354
System.out.println(s2.hashCode());//96354
System.out.println(s1==s2);//false
System.out.println("重地".hashCode());//1179395
System.out.println('重'+0);//37325
System.out.println('地'+0);//22320
System.out.println("通话".hashCode());//1179395
}
}
5.HashSet集合存储数据的结构(哈希表)
6.使用HashSet集合存储String不重复的原理
/*
使用HashSet集合存储String不重复的原理
String类重写了hashCode方法和equals方法
Integer,Double,Character,....类重写了hashCode方法和equals方法
*/
public class Demo01HashSetSaveString {
public static void main(String[] args) {
//创建HashSet集合,泛型使用String
HashSet<String> set = new HashSet<>();
String s1 = new String("abc");
String s2 = new String("abc");
set.add(s1);
set.add(s2);
set.add("重地");
set.add("通话");
set.add("abc");
System.out.println(set);
}
}
7.HashSet存储自定义类型元素Person
/*
HashSet存储自定义类型元素Person
要求:
同名同年龄的人,认为是同一个人,只能存储一次
Person必须重写hashCode和equals方法,保证元素唯一
*/
public class Demo02HashSetSavePerson {
public static void main(String[] args) {
//创建HashSet集合对象,泛型使用Person
HashSet<Person> set = new HashSet<>();
Person p1 = new Person("a",10);
Person p2 = new Person("a",10);
set.add(p1);//1163157884
set.add(p2);//1956725890
System.out.println(p1.hashCode());
System.out.println(p2.hashCode());
set.add(new Person("b",11));
set.add(new Person("b",9));
set.add(new Person("c",12));
set.add(new Person("d",13));
set.add(new Person("e",14));
set.add(new Person("f",15));
//遍历set集合
for (Person p : set) {
System.out.println(p);
}
}
}
public class Person {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
/*
重写Object类的hashCode方法:模拟String类
name本身就是一个字符串,可以直接调用Stirng重写的hashCode方法,获取字符串的哈希值
name.hashCode()+age;
set.add(new Person("a",10)); 97+10 = 107
set.add(new Person("a",10)); 97+10 = 107
set.add(new Person("b",11)); 98+11 = 109
----------------------------------------
name.hashCode()+age;
set.add(new Person("a",10)); 97+10 = 107
set.add(new Person("b",9)); 98+9 = 107
降低相同哈希值出现的概率:可以避免在比较equals方法,可以提高效率
name.hashCode()*2+age;
set.add(new Person("a",10)); 97*2+10 = 204
set.add(new Person("b",9)); 98*2+9 = 205
name.hashCode()*31+age;
*/
/*@Override
public int hashCode() {
return name.hashCode()*31+age;
}*/
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
return result;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
if (age != person.age) return false;
return name != null ? name.equals(person.name) : person.name == null;
}
}
注意:
ArrayList集合存储自定义类型元素Person,Person类没有必要重写hashCode和equals方法
因为ArrayList的add方法不会调用hashCode和equals方法判断元素是否重复
8.LinkedHashSet集合
/*
java.util.LinkedHashSet<E> extends HashSet<E>
具有可预知迭代顺序的 Set 接口的哈希表和链接列表实现。此实现与 HashSet 的不同之外在于,后者维护着一个运行于所有条目的双重链接列表。
LinkedHashSet特点:
底层是哈希表+单向链表
JDK1.8之前:数组+单向链表+单向链表
JDK1.8之后:数组+单向链表|红黑树(提高查询的效率)+单向链表
结构就是一个双向链表接口,可以保证迭代的顺序,是一个有序的集合
*/
public class Demo03LinkedHashSet {
public static void main(String[] args) {
HashSet<String> set = new HashSet<>();
set.add("aaa");
set.add("bbb");
set.add("ccc");
set.add("aaa");
set.add("ddd");
System.out.println(set);//[aaa, ccc, bbb, ddd] 不允许存储重复元素,无序集合
LinkedHashSet<String> linked = new LinkedHashSet<>();
linked.add("aaa");
linked.add("bbb");
linked.add("ccc");
linked.add("aaa");
linked.add("ddd");
System.out.println(linked);//[aaa, bbb, ccc, ddd] 不允许存储重复元素,有序集合
}
}
9.TreeSet集合
/*
java.util.TreeSet<E> implement Set<E>
基于Set接口的红黑树的实现。
使用元素的自然顺序对元素进行排序(内部会使用Comparator比较器对元素进行默认升序排序),
或者根据创建 set 时提供的 Comparator 进行排序,具体取决于使用的构造方法。
构造方法:
TreeSet() 构造一个新的空 set,该 set 根据其元素的自然顺序进行排序。
TreeSet(Comparator<? super E> comparator) 构造一个新的空 TreeSet,它根据指定比较器进行排序。
注意:
TreeSet特点:
1.不允许存储重复的元素
2.不包含带索引的方法
3.底层是一个红黑树接口
4.可以根据比较器对元素进行排序
*/
public class Demo04TreeSet {
public static void main(String[] args) {
TreeSet<Integer> set1 = new TreeSet<>();
set1.add(100);
set1.add(0);
set1.add(-1);
set1.add(88);
set1.add(-23);
set1.add(66);
System.out.println(set1);//[-23, -1, 0, 66, 88, 100]
TreeSet<Integer> set2 = new TreeSet<>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
//降序排序
return o2-o1;
}
});
set2.add(100);
set2.add(0);
set2.add(-1);
set2.add(88);
set2.add(-23);
set2.add(66);
System.out.println(set2);//[100, 88, 66, 0, -1, -23]
}
}
五丶 Map集合
1.Map集合概述
java.util.Map<k,v>:接口
1.Map是一个双列集合,每个元素包含两个值,一个key,一个value
2.Map集合中key不允许重复,value可以重复
3.Map集合中一个key只能对应一个vlaue
4.Map集合中key和value的数据类型可以使用相同的,也可以是不同的
2.Map的常用子类
java.util.HashMap<k,v> implements Map<k,v>:底层是一个哈希表
基于哈希表的 Map
接口的实现。
是一个无序的集合
java.util.LinkedHashMap<k,v>集合 extends java.util.HashMap<k,v>集合
Map
接口的哈希表和链接列表实现
是一个有序的集合
java.util.TreeMap<K,V>集合 implements Map<k,v>
基于红黑树(Red-Black tree)的 NavigableMap
实现
集合中自带一个比较器,里边存储的key有序的
3.Map的常用方法
/*
Map的常用方法
- public V put(K key, V value): 把指定的键与指定的值添加到Map集合中。
- public V get(Object key) 根据指定的键,在Map集合中获取对应的值。
- public V remove(Object key): 把指定的键 所对应的键值对元素 在Map集合中删除,返回被删除元素的值。
- public boolean containKey(Object key):判断该集合中是否有此键。
*/
public class Demo01Map {
/*
public boolean containKey(Object key) 判断集合中是否包含指定的键
boolean containsValue(Object value) 判断集合中是否包含指定的值
*/
/*
public V remove(Object key) 根据key移除键值对
返回值:
key存在:移除键值对,返回被移除健值对中的值
key不存在,对集合没有影响,返回null
*/
/*
public V get(Object key) 根据key获取value值
返回值:
key存在:返回对应的value
key不存在,返回null
*/
/*
public V put(K key, V value):往Map集合中添加元素(键值对)
返回值:
key不重复,返回值是null
key重复,会使用新的value,替换之前的value,返回值就是被替换的value
*/
}
4.Map的遍历
1).键找值方式
/*
Map集合遍历的第一种方式:键找值的方式
使用的方法:
Set<K> keySet() 把所有的key取出来存储到一个Set集合中
V get(Object key) 根据key获取value值
实现步骤:
1.创建Map集合对象,添加元素
2.使用Map集合中的方法keySet,把所有的key取出来存储到一个Set集合中
3.遍历Set集合,获取Map集合中的每一个key
4.使用Map集合中的方法get,根据key获取value值
*/
public class Demo02Map {
public static void main(String[] args) {
//1.创建Map集合对象,添加元素
Map<String,Integer> map = new HashMap<>();
map.put("迪丽热巴",168);
map.put("古力娜扎",165);
map.put("林志玲",180);
map.put("冯提莫",154);
//2.使用Map集合中的方法keySet,把所有的key取出来存储到一个Set集合中
Set<String> set = map.keySet();
//3.遍历Set集合,获取Map集合中的每一个key
//使用迭代器遍历Set集合
Iterator<String> it = set.iterator();
while (it.hasNext()){
String key = it.next();
//4.使用Map集合中的方法get,根据key获取value值
Integer value = map.get(key);
System.out.println(key+"="+value);
}
System.out.println("----------------------");
//使用增强for循环遍历Set集合
for (String key : set) {
//4.使用Map集合中的方法get,根据key获取value值
Integer value = map.get(key);
System.out.println(key+"="+value);
}
System.out.println("----------------------");
//使用增强for循环遍历Set集合
for (String key : map.keySet()) {
//4.使用Map集合中的方法get,根据key获取value值
Integer value = map.get(key);
System.out.println(key+"="+value);
}
}
}
2).键值对方式
/*
Map集合遍历的第二种方式_键值对方式
使用的方法:
Set<Map.Entry<K,V>> entrySet()返回此映射中包含的映射关系的 Set 视图。
Entry对象中的方法:
K getKey() 返回与此项对应的键。
V getValue() 返回与此项对应的值。
实现步骤:
1.创建Map集合对象,添加元素
2.使用Map集合中的方法entrySet,获取Map集合中所有的Entry对象,存储到Set集合中
3.遍历Set集合,获取每一个Entry对象
4.使用Entry对象中的方法getKey和getValue获取键与值
*/
public class Demo03Map {
public static void main(String[] args) {
//1.创建Map集合对象,添加元素
Map<String,String> map = new HashMap<>();
map.put("郭靖","黄蓉");
map.put("杨过","小龙女");
map.put("张无忌","赵敏");
map.put("冷锋","龙小云");
//2.使用Map集合中的方法entrySet,获取Map集合中所有的Entry对象,存储到Set集合中
//成员内部类使用:外部类名.内部类名 Map.Entry
Set<Map.Entry<String, String>> set = map.entrySet();
//3.遍历Set集合,获取每一个Entry对象
//使用迭代器遍历Set集合
Iterator<Map.Entry<String, String>> it = set.iterator();
while (it.hasNext()){
Map.Entry<String, String> entry = it.next();
//4.使用Entry对象中的方法getKey和getValue获取键与值
String key = entry.getKey();
String value = entry.getValue();
System.out.println(key+"="+value);
}
System.out.println("------------------------------");
//使用增强for循环遍历Set集合
for (Map.Entry<String, String> entry : set) {
//4.使用Entry对象中的方法getKey和getValue获取键与值
String key = entry.getKey();
String value = entry.getValue();
System.out.println(key+"="+value);
}
}
}
5.HashMap存储自定义类型
/*
HashMap存储自定义类型
*/
public class Demo01HashMapSavePerson {
public static void main(String[] args) {
show02();
}
/*
HashMap存储自定义类型
key:Person
保证同名同年龄的人视为同一个人
作为key的Person必须重写hashCode和equals方法
value:String
可以重复
*/
private static void show02() {
HashMap<Person,String> map = new HashMap<>();
map.put(new Person("张三",18),"z");
map.put(new Person("李四",18),"l");
map.put(new Person("王五胖",1),"w");
map.put(new Person("赵六",30),"s");
//使用entrySet和增强for循环遍历Map集合
//使用Map集合中的方法entrySet,获取Map集合中每一个Entry对象,存储到Set集合中
Set<Map.Entry<Person, String>> set = map.entrySet();
//遍历Set集合,获取每一个Entry对象
for (Map.Entry<Person, String> entry : set) {
//使用Entry对象中方法getKey和getValue分别获取键与值
Person key = entry.getKey();
String value = entry.getValue();
System.out.println(key+"-->"+value);
}
}
/*
HashMap存储自定义类型
key:String类型
String类重写了hashCode和equals方法,所以可以保证key唯一
value:Person类型
可以重复
*/
private static void show01() {
HashMap<String,Person> map = new HashMap<>();
map.put("z",new Person("张三",18));
map.put("l",new Person("李四",75));
map.put("w",new Person("王五",30));
map.put("s",new Person("赵六",1));
//使用keySet+增强for循环遍历Map集合
//使用Map集合中的方法keySet,取出所有的key,存储到Set集合中
Set<String> set = map.keySet();
//遍历Set集合,获取Map集合中每一个key
for (String key : set) {
//根据key获取value值
Person value = map.get(key);
System.out.println(key+"-->"+value);
}
}
}
6.LinkedHashMap集合
/*
java.util.LinkedHashMap<k,v>集合 extends HashMap<k,v>集合
Map 接口的哈希表和链接列表实现,具有可预知的迭代顺序。
LinkedHashMap底层是一个:哈希表+单向链表
*/
public class Demo02LinkedHashMap {
public static void main(String[] args) {
HashMap<String,String> map = new HashMap<>();
map.put("aaa","111");
map.put("bbb","222");
map.put("ccc","333");
map.put("aaa","111");
map.put("ddd","444");
System.out.println(map);//{aaa=111, ccc=333, bbb=222, ddd=444} key不允许重复,是一个无序集合
LinkedHashMap<String,String> linked = new LinkedHashMap<>();
linked.put("aaa","111");
linked.put("bbb","222");
linked.put("ccc","333");
linked.put("aaa","111");
linked.put("ddd","444");
System.out.println(linked);//{aaa=111, bbb=222, ccc=333, ddd=444} key不允许重复,是一个有序集合
}
}
7.TreeMap集合
/*
java.util.TreeMap<k,v>集合 implement Map<k,v>接口
基于红黑树(Red-Black tree)的 NavigableMap 实现。
TreeMap特点:
底层是一个红黑树结构
内部包含了一个比较器,会对key进行默认升序排序
也可以使用比较器自定义key的排序规则
构造方法:
TreeMap()使用键的自然顺序构造一个新的、空的树映射。
TreeMap(Comparator<? super K> comparator) 构造一个新的、空的树映射,该映射根据给定比较器进行排序。
*/
public class Demo03TreeMap {
public static void main(String[] args) {
TreeMap<Integer,String> tree1 = new TreeMap<>();
tree1.put(1,"a");
tree1.put(3,"b");
tree1.put(2,"c");
tree1.put(4,"d");
System.out.println(tree1);//{1=a, 2=c, 3=b, 4=d} 默认升序排序
TreeMap<Double,String> tree2 = new TreeMap<>(new Comparator<Double>() {
@Override
public int compare(Double o1, Double o2) {
//key降序排序:o2-o1
return (int)(o2-o1);
}
});
tree2.put(1.1,"a");
tree2.put(3.1,"a");
tree2.put(5.1,"a");
tree2.put(2.1,"a");
tree2.put(8.1,"a");
System.out.println(tree2);//{8.1=a, 5.1=a, 3.1=a, 2.1=a, 1.1=a}
}
}
8.Hashtable集合
/*
java.util.Hashtable<k,v>集合 implements Map<k,v>接口
此类实现一个哈希表,该哈希表将键映射到相应的值。任何非 null 对象都可以用作键或值。
Hashtable特点:
1.底层是一个哈希表
2.Hashtable不允许存储null值null键
3.Hashtable是一个线程安全的集合,效率低
HashMap特点:
1.底层是一个哈希表
2.HashMap可以使用null值null键
3.HashMap是一个线程不安全的集合,效率高
Hashtable集合效率没有HashMap高,所以已经被淘汰了,但是Hashtable的子类Properties依然活跃在历史舞台
*/
public class Demo04Hashtable {
public static void main(String[] args) {
HashMap<String,String> map = new HashMap<>();
map.put("a",null);
map.put(null,"b");
map.put(null,null);
System.out.println(map);//{null=null, a=null}
Hashtable<String,String> table = new Hashtable<>();
//table.put("a",null);//NullPointerException
//table.put(null,"b");//NullPointerException
table.put(null,null);//NullPointerException
}
}
9.Map集合练习
需求:
输入一个字符串中统计字符串中每个字符出现次数。
代码实现:
/*
需求:
输入一个字符串中统计字符串中每个字符出现次数。
分析:
1.使用Scanner获取用户输入的字符串
2.创建一个Map集合,key使用Character类型,存储字符;value使用Integer类型,存储字符个数
3.遍历字符串,获取字符串中的每一个字符
a.使用length()方法+charAt(i)方法遍历字符串
b.使用toCharArray方法把字符串转换为字符数组,遍历数组
4.使用Map集合中的方法containsKey判断集合中是否有指定key(字符)
boolean containsKey(Object key)
true:包含指定的字符
a.根据key(字符)获取value(字符个数)
b.value++
c.把改变后的value存储到Map集合中(更新)
false:不包含指定的字符,第一次存储
put(字符,1)
*/
public class Demo05Test {
public static void main(String[] args) {
//1.使用Scanner获取用户输入的字符串
String s = new Scanner(System.in).nextLine();
//2.创建一个Map集合,key使用Character类型,存储字符;value使用Integer类型,存储字符个数
HashMap<Character,Integer> map = new HashMap<>();
//3.遍历字符串,获取字符串中的每一个字符
char[] arr = s.toCharArray();
for (char c : arr) {
/*//4.使用Map集合中的方法containsKey判断集合中是否有指定key(字符)
if(map.containsKey(c)){
//true:包含指定的字符
//a.根据key(字符)获取value(字符个数)
int value = map.get(c);
//b.value++
value++;
//c.把改变后的value存储到Map集合中(更新)
map.put(c,value);
}else{
//false:不包含指定的字符,第一次存储
//put(字符,1)
map.put(c,1);
}*/
/*
JDK1.8之后Map集合中的方法
default V getOrDefault(Object key, V defaultValue)
参数:
Object key:map集合中的key
V defaultValue:给key设置一个默认的value值
返回值:
V:如果key存在,根据key获取value返回;key不存在,返回设置的默认值
*/
Integer value = map.getOrDefault(c, 0);
map.put(c,++value);
}
System.out.println(map);
}
}
六丶 JUC并发包
在JDK的并发包java.util.concurrent里提供了几个非常有用的并发容器和并发工具类。供我们在多线程开发中进行使用。这些集合和工具类都可以保证高并发的线程安全问题.
1.并发List集合_CopyOnWriteArrayList
1).java.util.concurrent.CopyOnWriteArrayList(类):它是一个“线程安全”的ArrayList,我们之前学习的java.utils.ArrayList不是线程安全的。
2).如果是多个线程,并发访问同一个ArrayList,我们要使用:CopyOnWriteArrayList
import java.util.ArrayList;
import java.util.concurrent.CopyOnWriteArrayList;
public class MyThread extends Thread {
//创建List集合
//public static ArrayList<Integer> list = new ArrayList<>();//多线程不安全(异常,数据不准确)
public static CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>();//多线程安全
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
list.add(i);
}
System.out.println("Thread-0线程执行结束!");
}
}
public class Demo01CopyOnWriteArrayList {
public static void main(String[] args) throws InterruptedException {
//创建新线程对象并开启
MyThread mt = new MyThread();
mt.start();
System.out.println("main线程继续执行");
for (int i = 0; i < 1000; i++) {
MyThread.list.add(i);
}
System.out.println("main线程休息1秒");
Thread.sleep(1000);
System.out.println("最终集合的长度为:"+MyThread.list.size());
}
}
ArrayList集合并发的问题:
main线程会继续执行,往集合中添加元素
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 73
Thread-0线程开始执行了...往集合中添加元素!
Thread-0线程结束执行了!
at java.util.ArrayList.add(ArrayList.java:463)
at com.itheima.demo12CopyOnWriteArrayList.Demo01.main(Demo01.java:12)
main线程会继续执行,往集合中添加元素
Thread-0线程开始执行了...往集合中添加元素!
main线程添加数据结束,等待1秒钟,等待Thread-0线程执行完毕
Thread-0线程结束执行了!
ArrayList集合的总长度为:1999
main线程会继续执行,往集合中添加元素
main线程添加数据结束,等待1秒钟,等待Thread-0线程执行完毕
Thread-0线程开始执行了...往集合中添加元素!
Thread-0线程结束执行了!
ArrayList集合的总长度为:2000
2.并发Set集合_CopyOnWriteArraySet
import java.util.HashSet;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
public class MyThread extends Thread {
//创建Set集合
//public static HashSet<Integer> set = new HashSet<>();//多线程不安全(数据不准确)
public static CopyOnWriteArraySet<Integer> set = new CopyOnWriteArraySet<>();//多线程安全
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
set.add(i);
}
System.out.println("Thread-0线程执行结束!");
}
}
public class Demo01CopyOnWriteArraySet {
public static void main(String[] args) throws InterruptedException {
//创建新线程对象并开启
MyThread mt = new MyThread();
mt.start();
System.out.println("main线程继续执行");
for (int i = 1000; i < 2000; i++) {
MyThread.set.add(i);
}
System.out.println("main线程休息1秒");
Thread.sleep(1000);
System.out.println("最终集合的长度为:"+ MyThread.set.size());
}
}
HashSet集合存在并发问题:
main线程会继续执行,往集合中添加元素
Thread-0线程开始执行了...往集合中添加元素!
Thread-0线程结束执行了!
main线程添加数据结束,等待1秒钟,等待Thread-0线程执行完毕
HashSet集合的总长度为:1996
main线程会继续执行,往集合中添加元素
Thread-0线程开始执行了...往集合中添加元素!
Thread-0线程结束执行了!
main线程添加数据结束,等待1秒钟,等待Thread-0线程执行完毕
HashSet集合的总长度为:1999
3.并发Map集合_ConcurrentHashMap
import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap;
public class MyThread extends Thread {
//创建Map集合
//public static HashMap<Integer,Integer> map = new HashMap<>();//多线程不安全(数据不准确)
public static ConcurrentHashMap<Integer,Integer> map = new ConcurrentHashMap<>();//多线程安全
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
map.put(i,i);
}
System.out.println("Thread-0线程执行结束!");
}
}
package demo10ConcurrentHashMap;
public class Demo01ConcurrentHashMap {
public static void main(String[] args) throws InterruptedException {
//创建新线程对象并开启
MyThread mt = new MyThread();
mt.start();
System.out.println("main线程继续执行");
for (int i = 1000; i < 2000; i++) {
MyThread.map.put(i,i);
}
System.out.println("main线程休息1秒");
Thread.sleep(1000);
System.out.println("最终集合的长度为:"+ MyThread.map.size());
}
}
hashMap集合存在并发问题:
main线程会继续执行,往集合中添加元素
Thread-0线程开始执行了...往集合中添加元素!
main线程添加数据结束,等待1秒钟,等待Thread-0线程执行完毕
Thread-0线程结束执行了!
HashMap集合的总长度为:1992
比较ConcurrentHashMap和Hashtable的效率
Java类库中,从1.0版本也提供一个线程安全的Map:Hashtable
Hashtable和ConcurrentHashMap有什么区别:
Hashtable采用的synchronized——悲观锁,效率更低。
ConcurrentHashMap:采用的CAS 机制——乐观锁,效率更高。
开启1000个线程,每个线程存储100000个数据
import java.util.Hashtable;
import java.util.concurrent.ConcurrentHashMap;
/*
Hashtable和ConcurrentHashMap有什么区别:
Hashtable采用的synchronized——悲观锁,效率更低。
ConcurrentHashMap:采用的CAS 机制——乐观锁,效率更高。
开启1000个线程,每个线程存储100000个数据
*/
public class MyThread extends Thread{
//定义一个Hashtable集合,供多个线程使用
//public static Hashtable<Integer,Integer> map = new Hashtable<>();
//定义ConcurrentHashMap集合,供多个线程使用
public static ConcurrentHashMap<Integer,Integer> map = new ConcurrentHashMap<>();
@Override
public void run() {
long s = System.currentTimeMillis();
//往集合中存储存储100000个数据
for (int i = 0; i < 100000; i++) {
map.put(i,i);
}
long e = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName()+"线程供耗时:"+(e-s)+"毫秒");
}
}
public class Demo {
public static void main(String[] args) {
for (int i = 0; i < 1000; i++) {
new MyThread().start();
}
}
}
Hashtable容器使用synchronized来保证线程安全,但在线程竞争激烈的情况下Hashtable的效率非常低下。因为当一个线程访问Hashtable的同步方法,其他线程也访问Hashtable的同步方法时,会进入阻塞状态。如线程1使用put进行元素添加,线程2不但不能使用put方法添加元素,也不能使用get方法来获取元素,所以竞争越激烈效率越低。
ConcurrentHashMap高效的原因:CAS + 局部(synchronized)锁定