集合
集合介绍 集合 集合的理解和好处- 可以动态保存任意多个对象,使用比较方便!
- 提供了一系列方便的操作对象的方法:add、remove、set、get等
- 使用集合添加、删除新元素的示意代码简洁了
单列集合:保存单个数据
ArrayList arrayList = new ArrayList();
arrayList.add("array");
arrayList.add("list");
双列集合(存放的是k-v键值对)
HashMap hashMap = new HashMap();
hashMap.put("No1","111");
hashMap.put("No2","222");
Collections方法
Collection接口继承了Iterable接口
1 、collection实现子类可以存放多个元素,每个元素可以是Object
2 、有些Collection的实现类可以存放重复的元素,有些不可以
3 、有些Collection的实现类,存放数据是有序的,有些是无序的
4 、Collection接口没有直接的实现子类,是通过子接口Set和List来实现的
接口是不能直接被实例化的,只有实现了接口的这个类才能被实例化
public class CollectionMethod {
@SuppressWarnings({"all"})
public static void main(String[] args) {
List list = new ArrayList();
// add:添加单个元素
list.add("jack");
list.add(10);//list.add(new Integer(10))
list.add(true);
System.out.println("list=" + list); //list=[jack, 10, true]
// remove:删除指定元素
//list.remove(0);//删除第一个元素
list.remove(true);//指定删除某个元素
System.out.println("list=" + list); //list=[jack, 10]
// contains:查找元素是否存在
System.out.println(list.contains("jack"));//true
// size:获取元素个数
System.out.println(list.size());//2
// isEmpty:判断是否为空
System.out.println(list.isEmpty());//false
// clear:清空
list.clear();
System.out.println("list=" + list); //list=[]
// addAll:添加多个元素
ArrayList list2 = new ArrayList();
list2.add("红楼梦");
list2.add("三国演义");
list.addAll(list2);
System.out.println("list=" + list); // list=[红楼梦, 三国演义]
// containsAll:查找多个元素是否都存在
System.out.println(list.containsAll(list2));//true
// removeAll:删除多个元素
list.add("聊斋");
list.removeAll(list2);
System.out.println("list=" + list);//[聊斋]
// 说明:以ArrayList实现类来演示.
}
}
集合遍历
迭代器遍历
迭代器Iterator
Iterator对象称为迭代器,主要用于遍历Collection集合中的元素
所有实现了Collection接口的集合类都有一个Iterator()方法,用以返回一个实现了Iterator接口的对象,即可以返回一个迭代器
Iterator仅用于遍历集合,Iterator本身并不存储对象
迭代器的执行原理:指针不断下移,直到遍历完集合中的元素
首先得到一个迭代器,iterator然后通过hasNext()判断是否还有下一个元素,如果有,则将下移以后元素位置上的元素返回,指针下移
在调用iterator.next()方法之前必须要调用iterator.hasNext()进行检测。若不调用,且下一条记录无效,直接调用it.next()会抛出NoSuchElementException异常。
public class CollectionIterator {
public static void main(String[] args) {
ArrayList col = new ArrayList();
col.add(new Book("三国演义","罗贯中",10.2));
col.add(new Book("红楼梦","曹雪芹",14.1));
col.add(new Book("聊斋志异","蒲松龄",10.2));
//System.out.println("col = "+col); 直接输出的话达不到遍历的效果
//1.先得到iterator对应的迭代器
Iterator iterator = col.iterator();
//2.使用while循环遍历
while (iterator.hasNext()){
//返回下一个元素,类型是Object
Object next = iterator.next(); //因为add()方法是被重载了,里面可以写Object元素,所以左边为Object,而不是Book,取决于我们真正存放的类型
System.out.println("next ="+next);
}
//3.当退出while循环后,这时iterator迭代器指向的是最后一个元素,如果还想指针向下移的话,就会报错
//iterator.next();// NoSuchElementException
//4.如果还想再次遍历,需要重置我们的迭代器
iterator = col.iterator();
System.out.println("=================第二次遍历");
while (iterator.hasNext()){
Object next = iterator.next();
System.out.println("next ="+next);
}
}
}
class Book{
private String name;
private String author;
private double price;
public Book(String name, String author, double price) {
this.name = name;
this.author = author;
this.price = price;
}
public Book() {
}
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{" +
"name='" + name + ''' +
", author='" + author + ''' +
", price=" + price +
'}';
}
}
集合增强for遍历集合
基本语法
for ( 元素类型 元素名:集合或数组名){
}
public class CollectionFor {
public static void main(String[] args) {
ArrayList col = new ArrayList();
col.add(new Book("三国演义","罗贯中",10.2));
col.add(new Book("红楼梦","曹雪芹",14.1));
col.add(new Book("聊斋志异","蒲松龄",10.2));
//1.使用增强for,用在Collection集合
//2.增强for,底层任然是迭代器
for (Object books : col){
System.out.println("books =>" + books);
}
//增强for,也可以用在数组上
int[] nums = {1,2,42,4};
for (int i : nums) {
System.out.println("i= >"+ i);
}
}
}
作业:
创建3个Dog{name,age}对象,放入到ArrayList中,赋给List引用,用迭代器和增强for两种方式来遍历
重写Dog的toString方法,输出name和age
public class CollectionExercise {
public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add(new Dog("小黑",3));
list.add(new Dog("小白",7));
list.add(new Dog("小绿",5));
//使用增强for
for (Object dogs : list) {
System.out.println("dogs--->"+dogs);
}
//使用迭代器
Iterator iterator = list.iterator();
while (iterator.hasNext()){
Object next = iterator.next();
System.out.println("dogs-->"+next);
}
}
}
class Dog{
private String name;
private int age;
public Dog(String name, int age) {
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 +
'}';
}
}
List接口和常用方法
-
List接口是Collection接口的子接口
-
List的实现类中的元素是有序的,即添加顺序和取出元素的顺序是一样的,且可以重复
-
List集合中的每个额元素都有其对应的顺序索引,即支持索引
-
List容器的每个元素对应一个整数型的序号记载其在容器中的位置们可以根据序号存取容器中的元素(注意linkedList也是支持索引的)
public class ListMethod {
@SuppressWarnings({"all"})
public static void main(String[] args) {
List list = new ArrayList();
list.add("爱奇艺");
list.add("抖音");
// void add(int index, Object ele):在index位置插入ele元素
//在index = 1的位置插入一个对象
list.add(1, "北凉");
System.out.println("list=" + list);
// boolean addAll(int index, Collection eles):从index位置开始将eles中的所有元素添加进来
List list2 = new ArrayList();
list2.add("handsome");
list2.add("beautiful");
list.addAll(1, list2);
System.out.println("list=" + list);
// Object get(int index):获取指定index位置的元素
// int indexOf(Object obj):返回obj在集合中首次出现的位置
System.out.println(list.indexOf("handsome"));//1
// int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置
list.add("爱奇艺");
System.out.println("list=" + list);
System.out.println(list.lastIndexOf("爱奇艺")); //5
// Object remove(int index):移除指定index位置的元素,并返回此元素
list.remove(0);
System.out.println("list=" + list);
// Object set(int index, Object ele):设置指定index位置的元素为ele , 相当于是替换.
list.set(1, "奥里给");
System.out.println("list=" + list);
// List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex位置的子集合
// 注意返回的子集合 fromIndex <= subList < toIndex
List returnlist = list.subList(0, 2);
System.out.println("returnlist=" + returnlist);
}
}
练习常用方法
添加10个以上的元素(比如String “hello” ),在2号位插入一个元素"重庆交通大学",获得第5个元素,删除第6个元
素,修改第7个元素,使用迭代器遍历集合
要求:使用List的实现类ArrayList完成。
public class ListExercise {
public static void main(String[] args) {
List list = new ArrayList();
for (int i = 0; i < 12; i++) {
list.add("hello" + i);
}
System.out.println(list);
//在2号位插入一个元素"重庆交通大学"
list.add(1,"重庆交通大学");
System.out.println(list);
//获得第5个元素
System.out.println("获得第五个元素:"+list.get(4));
//删除第6个元素
list.remove(5);
System.out.println(list);
//修改第7个元素
list.set(6,"南岸");
System.out.println(list);
//使用迭代器遍历集合
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
System.out.println(next);
}
}
}
List排序练习
要求:使用List的实现类添加三本图书,并遍历,打印效果如下:
名字:xx 价格:xx 作者:xx
名字:xx 价格:xx 作者:xx
名字:xx 价格:xx 作者:xx
1、按价格由低到高排序:使用冒泡排序
2、要求使用ArrayList、LinkedList、和Vector三种集合实现
@SuppressWarnings({"all"})
public class ListExercise2 {
public static void main(String[] args) {
List list = new ArrayList();
//List list = new LinkedList();
//List list = new Vector();
list.add(new Books("红楼梦", "曹雪芹", 100));
list.add(new Books("西游记", "吴承恩", 10));
list.add(new Books("水浒传", "施耐庵", 19));
list.add(new Books("三国", "罗贯中", 80));
//遍历
for (Object books : list) {
System.out.println(books);
}
//如何对集合进行排序
System.out.println("=============================排序后");
sort(list);
for (Object o : list) {
System.out.println(o);
}
}
public static void sort(List list){
int size = list.size();
for (int i = 0; i < size - 1; i++) {
for (int j = 0; j < size - 1 - i; j++) {
Books book1 = (Books) list.get(j);
Books book2 = (Books) list.get(j + 1);
if (book1.getPrice()
ArrayList注意事项
1、ArrayList集合里面可以存放任意元素,可以为空,并且是多个空
2、ArrayList的底层是由数组来实现数据存储的
3、ArrayList基本等于Vector,除了ArrayList是线程不安全的(执行效率高),在多线程的情况下,不建议使用ArrayList
ArrayList底层结构和源码分析
1、ArrayList中维护了一个Object类型的数组elementData,用来存放我们的数据对象,
transient Object[] elementData; transient 表示瞬间,短暂的,被transient 修饰的属性不会被序列化
2、当创建ArrayList对象时,如果使用的是无参构造,则初始化elementData容量为0,第一次添加,则扩容 elementData为10、如需要再次扩容、则扩容elementData为当前容量的1.5倍。
3、如果使用的是指定大小的构造器,则初始化elementData容量为指定的大小,如果需要扩容,则直接扩容elementData为1.5倍。
源码分析:
public class ArrayListSource {
public static void main(String[] args) {
//使用无参构造器创建ArrayList对象
ArrayList list = new ArrayList();
//使用有参构造器创建ArrayList对象
//ArrayList list = new ArrayList(8);
for (int i = 1; i < 10; i++) {
list.add(i);
}
//使用for给list集合添加11-15数据
for (int i = 11; i <= 15; i++) {
list.add(i);
}
list.add(100);
list.add(200);
list.add(null);
}
}
断点debug
第一步:new ArrayList() 这个时候,它先执行了一个方法public ArrayList()
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
DEFAULTCAPACITY_EMPTY_ELEMENTDATA;是什么呐?
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
是一个空数组
进入for循环以后 对i进行装箱,执行add()方法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MKdSGveK-1650524395299)(C:UsersLYLAppDataRoamingTyporatypora-user-images1650192166881.png)]
1、先确定是否需要扩容,如果s == elementData.length为真,就走grow()方法size + 1
private Object[] grow() {
return grow(size + 1);
}
如果当前这个数minCapacity > elementData.length并且elementData不等于空再并且,minCapacity小于等于10,就调用grow()进行扩容
modCount++; 记录当前这个集合被修改的次数
oldCapacity等于原来空数组的值为0、newCapacity新的值等于原来的值加上原来的值除2,oldCapacity + (oldCapacity >> 1)可以理解为1.5倍扩容,如果新的值比最小的值还小或者相等,则就让最新的值等于我们的
DEFAULT_CAPACITY(10) ,所以第一次扩容并没有按我们的1.5倍扩容,如果新的值(10)比MAX_ARRAY_SIZE(大约为21亿)大就走hugeCapacity(minCapacity)否则就为新的值newCapacity(10);第一次扩容就是这样,扩容完了以后,但是还没有赋值所以10个空间全为null。
private Object[] grow(int minCapacity) {
return elementData = Arrays.copyOf(elementData,
newCapacity(minCapacity));
}
扩容使用的是Arrays.copyof(),他的好处是可以在扩容的时候保留原来的数,在原来的基础上扩容
i=2的时候
由于我们的s不等于0,所以直接不走grow()方法,用已有的空位即可,
然后我们直接走到11去,这时的空位已经没有了
和上面一样先装箱,这时的i=11
i=11的时候,这时modCount++;等于10,size=10,i=11,走add()
这时由箭头可以看到数组下标和数组长度相等,走grow()方法:
再次进行比较:和前面一样,newCapacity=15,minCapacity=11
newCapacity - minCapacity <= 0,返回false,直接走return,然后三元运算符,为真,则走newCapacity=15,扩容成功,然后再走Arrays.copyof()
使用有参构造器创建ArrayList对象
public class ArrayListSource {
public static void main(String[] args) {
//使用无参构造器创建ArrayList对象
//ArrayList list = new ArrayList();
//使用有参构造器创建ArrayList对象
ArrayList list = new ArrayList(8);
for (int i = 1; i <= 10; i++) {
list.add(i);
}
//使用for给list集合添加11-15数据
for (int i = 11; i <= 15; i++) {
list.add(i);
}
list.add(100);
list.add(200);
list.add(null);
}
}
打断点,debug模式
我们初始的容量为8,让他继续向下走,还是和上面一样,进行装箱,
再点下,modCount++等于0,然后走add()方法
s与数组长度不相等、然后赋值数组索引等于0,值等于1,size = 1
当i=9时:modCount++=8走add()方法
返回true走grow()方法;
再点,进入newCapacity()方法,扩容到原来的1.5倍变成12,(newCapacity - minCapacity <= 0返回false,直接进入return(),三元运算符,走newCapacity扩容为12
然后使用Arrays.copyof()保留原来的数组,再扩容
总结:
如果是有参构造就是1.5倍扩容、第一次是10。
如果是无参、从第二次开始按elementData1.5倍扩容
Vector
1、Vector底层也是一个对象数组,protected Object[] elementData;
2、Vector是线程同步的,即线程安全,Vector类的操作方法带有synchronized
3、在开发中,需要线程同步安全时,考虑使用Vector
扩容倍数:如果是无参,默认10。满了以后按两倍扩容,如果指定大小,则每次直接按2倍扩容
LinkedList
LinkedList底层结构
1、LinkedList底层实现了双向链表和双端队列特点
2、可以添加任意元素(元素可以重复)、包括null
3、线程不安全,没有实现同步
- LinkedList底层维护了一个双向链表
- LinkedList中维护了两个属性first和last分别指向首节点和尾节点
- 每个节点(Node对象)里面又维护了prev、next、item三个属性,其中通过prev指向前一个,通过next指向后一个节点。最终实现双向链表
- 所以LinkedList的元素的添加和删除,不是通过数组完成的,相对来说效率较高
模拟一个简单的双向链表
public class LinkedList01 {
public static void main(String[] args) {
//模拟一个简单的双向链表
Node jack = new Node("jack");
Node tom = new Node("tom");
Node cq = new Node("重庆");
//连接三个节点,形成双向链表
//jack -> tom -> cq
jack.next = tom;
tom.next = cq;
//cq -> tom -> jack
cq.pre = tom;
tom.pre = jack;
Node first = jack; //让first引用指向jack,就是双向链表的头结点
Node last = cq; //让last引用指向cq,就是双向链表的尾结点
//从头到尾进行遍历
while (true){ //cq的下一个就为空
if (first==null){
break;
}
//输出first信息
System.out.println(first);
first = first.next;
}
//从尾到头遍历
System.out.println("===========从尾到头遍历============");
while (true){
if (last == null){
break;
}
//输出last信息
System.out.println(last);
last = last.pre;
}
//要求:在tom和重庆之间插入贵州
//1.先创建一个Node节点
Node guizhou = new Node("贵州");
tom.next = guizhou;
guizhou.next = cq;
cq.pre = guizhou;
guizhou.pre = tom;
System.out.println("============添加元素============");
first = jack; //让first再次指向jack
//从头到尾进行遍历
while (true){ //cq的下一个就为空
if (first==null){
break;
}
//输出first信息
System.out.println(first);
first = first.next;
}
}
}
//定义一个Node类,Node对象,表示双向链表得一个节点
class Node{
public Object item; //真正存放数据
public Node next; //指向后一个结点
public Node pre; //指向前一个结点
public Node(Object name){
this.item = name;
}
public String toString(){
return "Node name=" + item;
}
}
ArrayList和LinkedList比较
如何选择ArrayList和LinkedList:
- 如果我们改查的操作多,选择ArrayList
- 如果我们增删操作多,选择LinkedList
- 在开发中,我们多数情况下是查询,所以大部分情况下会选择ArrayList
Set接口和常用方法
-
无序(添加和取出的顺序不一致),没有索引
-
不允许重复元素,最多包含一个null
-
实现类
-
Set接口的常用方法
- 和List接口一样,Set接口也是Collection的子接口,因此,常用方法和Collection接口一样。
-
Set接口的遍历方式
- 同Collection的遍历方式一样,因为Set接口是Collection接口的子接口。
1、可以使用迭代器
2、可以使用增强for
3、不能使用索引的方式来获取
HashSet
- 用set接口的实现类HashSet来讲解Set接口的方法
- set接口的实现类的对象(set接口对象),不能存放重复的元素,可以添加一个null
- set接口对象存放元素是无序的(即添加的顺序和取出的顺序不一致)
- 注意:取出的顺序虽然不是添加的顺序,但是取出的顺序一直是一样的(比如取两次,两次结果都是一样的)
@SuppressWarnings({"all"})
public class SetMethod {
public static void main(String[] args) {
Set set = new HashSet();
set.add("重庆");
set.add("贵州");
set.add("遵义"); //重复
set.add("重庆");
set.add(null);
set.add(null); //重复
System.out.println("set = "+set);
//遍历方式===>迭代器
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
System.out.println("next=:"+next);
}
//增强for循环
for (Object o : set) {
System.out.println("o=:"+o);
}
//普通for循环:由于set没有get()方法所以不能用普通for循环
//删除
set.remove(null);
}
}
HashSet实际上是HashMap,通过源码:
public HashSet() {
map = new HashMap<>();
}
可以存放null值,但是只能有一个null
HashSet不保证元素是有序的,取决于hash后,在确定索引的结构
不能有重复元素/对象,—>但是我们真的理解了吗?
@SuppressWarnings({"all"})
public class HashSet_ {
public static void main(String[] args) {
Set set = new HashSet();
//1.在执行add方法后,会返回一个Boolean值
//2.如果添加成功以后,返回true,否则返回false
System.out.println(set.add("john"));//true
System.out.println(set.add("lucy"));//true
System.out.println(set.add("john"));//false
System.out.println(set.add("jack"));//true
System.out.println(set.add("rose"));//true
System.out.println("set = :"+ set); //set = :[john, rose, lucy, jack]
set.remove("john");
System.out.println("set: " +set); //set: [rose, lucy, jack]
//添加对象
set = new HashSet(); // 将set置为空
System.out.println("set->"+set); // set->[]
set.add("lucy"); //添加成功
set.add("lucy"); //添加不了
set.add(new Dog("黄豆"));
set.add(new Dog("黄豆"));
System.out.println("set-->"+set); // set-->[Dot{name='黄豆'}, lucy, Dot{name='黄豆'}]
//经典面试题:
set.add(new String("ooo"));
set.add(new String("ooo"));
System.out.println("set---->"+set); // set---->[Dot{name='黄豆'}, lucy, Dot{name='黄豆'}, ooo]
}
}
class Dog{
private String name;
public Dog(String name) {
this.name = name;
}
@Override
public String toString() {
return "Dot{" +
"name='" + name + ''' +
'}';
}
}
HashSet数组链表模拟
分析HashSet底层是HashMap,HashMap的底层是(数组+链表+红黑树)
public class HashSetStructure {
public static void main(String[] args) {
//模拟一个HashSet的底层
//1.创建一个数组,数组的类型是Node[]
Node[] table = new Node[16]; //创建了一个有16容量的数组
//创建节点
Node john = new Node("john", null);
table[2] = john;
Node jack = new Node("jack", null);
john.next = jack; //将jack节点挂载到john
Node rose = new Node("rose", null);
jack.next = rose; //将rose节点挂载到jack
Node lucy = new Node("lucy", null);
table[3] = lucy; // 把lucy放到table表的索引为3的位置
System.out.println("table"+table);
}
}
class Node{ //节点,存储数据
Object item; //存放数据
Node next; //指向下一个节点
public Node(Object item, Node next) {
this.item = item;
this.next = next;
}
@Override
public String toString() {
return "Node{" +
"item=" + item +
", next=" + next +
'}';
}
}
可以通过debug发现我们的链表已经有了
- HashSet底层是HashMap
- 添加一个元素时,先得到hash值,然后转成一个索引值,然后通过table查看它该存放在哪个索引位置
- 找到存储数据表table,看这个索引位置是否已经存放的有元素
- 如果没有,直接加入
- 如果有,调用equals比较,如果相同,就放弃添加,如果不相同,则添加到最后
- 如果一条链表的元素个数到8TREEIFY_THRESHOLD(默认是8),并且table的大小>=MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树),如果我们的链表有了8个数,但是table的索引还没有到63,他就会对table扩容
扩容机制
@SuppressWarnings({"all"})
public class HashSetSource {
public static void main(String[] args) {
HashSet hashSet = new HashSet();
hashSet.add("java");
hashSet.add("php");
hashSet.add("java");
System.out.println("set= "+hashSet);
}
}
- HashSet底层是HashMap,第一次添加时,table数组扩容到16,临界值(threshold)是 * 加载因子(loadFactor)是0.75=12
- 如果table数组使用到了临界值12,就会扩容到16 * 2=32,新的临界值就是32 * 0.75 = 24,依次类推
- 在Java8中,如果一条链表的元素个数到达TREEIFY_THRESHOLD(默认是8),并且table的大小>=MIN_TREEIFY_CAPACITY(默认是64),就会进行树化(红黑树),否则任然采用数组扩容机制
我们可以通过下面的代码查看他的扩容机制,debug模式
@SuppressWarnings({"all"})
public class HashSetIncrement {
public static void main(String[] args) {
HashSet hashSet = new HashSet();
for (int i = 1; i <= 100 ; i++) {
hashSet.add(i);
}
}
}
然后通过下面的代码查看树化机制
@SuppressWarnings({"all"})
public class HashSetIncrement01 {
public static void main(String[] args) {
HashSet hashSet = new HashSet();
for (int i = 1; i <= 12; i++) {
hashSet.add(new A(i));
}
System.out.println("hashSet"+hashSet);
}
}
class A {
//n不一样,保证了equals不一样
private int n;
public A(int n) {
this.n = n;
}
@Override
public int hashCode() {
//A对象所有的hashCode都是100
return 100;
}
}
第一次进去的时候,我们的table肯定是为null,当加进入第一个数的时候,table变成16,我们的key=1落在了索引为4的结点;
当我们加到第八个的时候:他不会触发树化机制,因为我们的table还没有到达64
我们继续向下加:当加到第9个的时候 ,table扩容到了32,再继续向下加,当加到10个的时候,我们的table扩容到了64;
而且扩容后将我们的节点hash进行了重新的计算,然后存放在索引为36的位置
我们再继续向下点,此时树化的两个条件已经满足,我们发现由原来的Node节点,变成了TreeNode,表示已经树化了,里面有一堆我们不认识的属性
到这里我们要弄清楚的是:假如我们添加节点的时候只在一个索引位置形成了一个链表,我们的size是怎么增加的呐?是根据加在table上才算?还是加在table和加在链表上都算呐?我们来实验一下
@SuppressWarnings({"all"})
public class HashSetIncrement01 {
public static void main(String[] args) {
HashSet hashSet = new HashSet();
for (int i = 1; i <= 7; i++) {
hashSet.add(new A(i));
}
for (int i = 1; i <= 7; i++) {
hashSet.add(new B(i));
}
}
}
class B {
//n不一样,保证了equals不一样
private int n;
public B(int n) {
this.n = n;
}
@Override
public int hashCode() {
//B对象所有的hashCode都是200
return 200;
}
}
class A {
//n不一样,保证了equals不一样
private int n;
public A(int n) {
this.n = n;
}
@Override
public int hashCode() {
//A对象所有的hashCode都是100
return 100;
}
}
我们把A对象全部7个全部加入,然后再加入B我们知道当我们table用到了12个就会扩容,我继续添加B当我添加到5个的时候,到底我们的table会不会扩容?再点一下,发现table变成了32!
那就证明不管我们添加到table中还是添加到链表上,我们的size都会++,加到我们的阈值就会扩容
HashSet最佳实践
题目:定义一个Employee类,该类包括:private成员属性name,age
要求:创建3个Employee对象放入HashSet中,当name和age相同时,认为是相同员工,不能添加到HashSet集合中
@SuppressWarnings({"all"})
public class HashSetExercise {
public static void main(String[] args) {
HashSet hashSet = new HashSet();
//对三个对象来说,他们的hash值是不同的,就比如加入一个小猫的name为jack,然后一个小狗的名字也叫jack,对象不同,所以hash值不同
//Employee xiaomao,xiaogou; 可以这样理解
hashSet.add(new Employee("jack", 10));
hashSet.add(new Employee("tom", 11));
hashSet.add(new Employee("jack", 10));
//所以需要用equals方法比较两个对象是否相等,如果名字和年龄都相等,就返回相同的hash值
System.out.println(hashSet); // [Employee{name='tom', age=11}, Employee{name='jack', age=10}]
}
}
class Employee{
private String name;
private int age;
public Employee(String name, int age) {
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
//两个员工,如果名字和年龄都相同,返回相同的equals为true
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
return age == employee.age && Objects.equals(name, employee.name);
}
@Override
//两个员工,如果名字和年龄都相同,返回相同的hashCode
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + ''' +
", age=" + age +
'}';
}
}
通过重写hashcode和equals实现
LinkedHashSet
- 在LinkedHashSet中维护了一个hash表和双向链表(LinkedHashSet中有head和tail)
- 每一个节点有before和after属性,这样可以形成双向链表
- 在添加一个元素时,先求hash值,再求索引,确定该元素在table表中的位置,然后将添加的元素加入到双向链表(如果已经存在,不添加—>原理是和hashset一样)
- 有了双向链表以后我们遍历LinkedHashSet也能确保插入顺序和遍历顺序一致
@SuppressWarnings({"all"})
public class LinkedHashSetSource {
public static void main(String[] args) {
Set set = new LinkedHashSet();
set.add(new String("AA"));
set.add(456);
set.add(456);
set.add(new Customer("呐",1001));
set.add(123);
set.add("CQ");
System.out.println("set = "+set); //set = [AA, 456, Customer{name='呐', no=1001}, 123, CQ]
// LinkedHashSet加入顺序和取出元素的顺序一致
// LinkedHashSet 底层维护的是一个 LinkedHashMap(是HashMap的子类)
// LinkedHashSet 底层结构(数组+双向链表)
// 第一次添加时,直接将数组table扩容到16,存放的节点类型是LinkedHashMap$Entry
// 为什么我们的table是HashMap$Node类型,存放的元素确实LinkedHashMap$Entry?,这里就和多态数组有关了,说明他们之间肯定有继承或者实现关系
//before, after用来双向链表的连接,
}
}
class Customer{
private String name;
private int no;
public Customer(String name, int no) {
this.name = name;
this.no = no;
}
@Override
public String toString() {
return "Customer{" +
"name='" + name + ''' +
", no=" + no +
'}';
}
}
通过代码我们发现我们取出来的数据和存进去的数据的顺序是相同的,相同的值就存放了一个
before, after用来双向链表的连接
head用来指向第一个元素,tail
这样就看出了双向链表的头和尾
Map接口
1、Map与Collection并列存在,用于保存具有映射关系的数据:key-value(双列元素)
2、Map中key和value可以是任何引用类型的数据,会封装到HashMap$Node对象中
3、Map中的key不允许重复,原因和HashSet一样
4、Map中的value可以重复
5、Map的key可以为null,value也可以为null,注意key为null有且只有一个,value为null可以有多个
6、常用String类作为Map的key,也可以用基本类型
7、key和value之间存在单向一对一关系,即通过指定的key总能找到对应的value
HashMap
public class Map_ {
public static void main(String[] args) {
//使用实现类HashMap演示
//1、Map与Collection并列存在,用于保存具有映射关系的数据:key-value(双列元素)
//2、Map中key和value可以是任何引用类型的数据,会封装到HashMap$Node对象中
//3、Map中的key不允许重复,原因和HashSet一样
//4、Map中的value可以重复
//5、Map的key可以为null,value也可以为null,注意key为null有且只有一个,value为null可以有多个
//6、常用String类作为Map的key,也可以用基本类型
//7、key和value之间存在单向一对一关系,即通过指定的key总能找到对应的value
Map map = new HashMap();
// k-v键值对
map.put("no1","奥迪");
map.put("no2","雷克萨斯");
map.put("no1","宝马"); // 如果出现相同的k,那么最新添加的就会替换掉前面已有的
map.put("no3","宝马");
map.put(null,null);
map.put(null,"BMW");
map.put(null,"BC");//等价替换了上面两个相同k的键值对
map.put("no4",null);
map.put("no5",null);
map.put(1,"丁真");
map.put(new Object(),"一眼丁真");
System.out.println(map.get("no2"));
System.out.println("map = "+map);
}
}
Map接口中常用的方法:
public class MapMethod {
public static void main(String[] args) {
Map map = new HashMap();
map.put("星期一",new menu("蒜苔炒肉",7));
map.put("星期二","炒土豆");
map.put("星期三","炒土豆");
map.put("星期四","肉末茄子");
map.put("星期五",null);
map.put("星期六","盐焗肉");
map.put("星期天","不吃");
//remove:根据键删除映射关系
map.remove("星期一");
//get:根据键获取值
Object obj = map.get("星期四");
System.out.println(obj);
//size:获取元素个数
System.out.println(map.size());
//isEmpty:判断个数是否为0
System.out.println(map.isEmpty());
//clear:清除所有键值对
//map.clear();
System.out.println(map);
//containsKey:查找键是否存在
System.out.println(map.containsKey("星期天"));
}
}
class menu{
private String name;
private int num;
public menu(String name, int num) {
this.name = name;
this.num = num;
}
}
-
Map的遍历方式:可以根据上图中右边的理解去遍历,通过获得KeySet,将KeySet遍历后得到一个一个的key,然后通过key得到values,
-
也可以直接遍历values。
-
或者通过Set entrySet = map.entrySet();获得entrySet ,再将其向下转型,Map.Entry,得到一个对象,这个时候的对象就是HashMap$Node类,这个类有getKey和getValue方法,也可以获得
@SuppressWarnings({"all"})
public class MapFor {
public static void main(String[] args) {
Map map = new HashMap();
map.put("星期二","炒土豆");
map.put("星期三","炒土豆");
map.put("星期四","肉末茄子");
map.put("星期五",null);
map.put("星期六","盐焗肉");
map.put("星期天","不吃");
//第一组:先取出所有的key,通过key,取出对应的value
Set keySet = map.keySet();
//(1)增强for
System.out.println("--------第一种方式---------");
for (Object keyList : keySet) {
//先拿到所有的key(keySet),然后把它遍历出来keyList,再通过key得到相应的value
System.out.println(keyList+"--"+map.get(keyList));
}
//迭代器
System.out.println("--------第二种方式---------");
Iterator iterator1 = keySet.iterator();
while (iterator1.hasNext()) {
Object keyvalue = iterator1.next();
System.out.println(keyvalue+"--"+map.get(keyvalue));
}
//第三种方式
System.out.println("--------第三种方式---------");
Collection values = map.values();
for (Object o : values) {
System.out.println(o);
}
//第四种方式
System.out.println("--------第四种方式---------");
Iterator iterator2 = values.iterator();
while (iterator2.hasNext()) {
Object value2 = iterator2.next();
System.out.println(value2);
}
//第五种方式:通过EntrySet来获取k-v
System.out.println("--------第五种方式---------");
Set entrySet = map.entrySet();
Iterator iterator3 = entrySet.iterator();
while (iterator3.hasNext()) {
Object value3 = iterator3.next();
System.out.println(value3);
}
//第六种方式
System.out.println("--------第六种方式---------");
for (Object entry : entrySet) {
Map.Entry m = (Map.Entry) entry;
System.out.println(m.getKey()+"--"+m.getValue());
}
//第七种方式
System.out.println("--------第七种方式---------");
Iterator iterator4 = entrySet.iterator();
while (iterator4.hasNext()) {
Object value4 = iterator4.next();
Map.Entry n = (Map.Entry) value4;
System.out.println(n.getKey()+"--"+n.getValue());
}
}
}
我有一个疑问就是第五种,我是无意之间弄出来的,也能遍历出来,但是我的value3都没有转型,也可以遍历,也不知道为什么!
Map接口练习题
-
使用HashMap添加3个员工对象,要求
键:员工id
值:员工对象
-
并遍历显示工资>18000的员工(遍历方式最少两种)
员工类:姓名、工资、员工id
@SuppressWarnings({"all"})
public class MapExercise {
public static void main(String[] args) {
Map map = new HashMap();
Employee employee = null;
map.put(1,new Employee("甲",18888,1));
map.put(2,new Employee("乙",17888,2));
map.put(3,new Employee("丙",18888,3));
System.out.println("-----------第一种-------------");
Set set = map.keySet();
for (Object key : set) {
Employee employee1 = (Employee) map.get(key); //这里必须强转,不然得不到Employee的方法
if (employee1.getMoney()>18000){
System.out.println(employee1);
}
}
System.out.println("-----------第二种-------------");
Set entrySet = map.entrySet();
System.out.println(entrySet.getClass()); //class java.util.HashMap$EntrySet
Iterator iterator = entrySet.iterator();
while (iterator.hasNext()) {
Map.Entry next = (Map.Entry) iterator.next();//这里为什么要转型成为HashMap$Node,因为HashMap$Node有getKy和getValues方法,而
Employee value = (Employee) next.getValue();//这里直接输出value也可以达到遍历的效果,但是如果想要进行比较的话,就必须转型,不然取不出Money
if (value.getMoney()>18000){
System.out.println(value);
}
}
}
}
class Employee{
private String name;
private double money;
private int id;
public Employee(String name, double money, int id) {
this.name = name;
this.money = money;
this.id = id;
}
//这里省略get和set方法
@Override
public String toString() {
return "Employee{" +
"name='" + name + ''' +
", money=" + money +
", id=" + id +
'}';
}
}
- Map接口的常用实现类:HashMap、Hashtable和Properties
- HashMap是Map接口使用频率最高的实现类
- HashMap是以key-value对的方式来存储数据
- key不能重复,但是值可以重复,允许使用null键和null值
- 如果添加相同的key,则会覆盖原来的key-value,等用于修改(key不会替换,value会替换)
- 与HashSet一样,不保证映射的顺序,因为底层是以hash表的方式来存储的
- HashMap没有实现同步,因此是线程不安全的
Hashtable
- 存放的元素是键值对K-V
- Hashtable的键和值都不能为null,否则会抛出NullPointerException
- Hashtable使用方法基本上和HashMap一样
- Hashtable是线程安全的,hashMap是线程不安全的
Hashtable简单实用
public class HashTableExercise {
public static void main(String[] args) {
Hashtable table = new Hashtable();
table.put("奥迪",100);// ok
//table.put(null,100); // 异常 NullPointerException
//table.put("奥迪",null); // 异常 NullPointerException
table.put("保时捷",100); //ok
table.put("三轮车",100); //ok
table.put("三轮车",80); //替换
table.put("hello1",1);
table.put("hello2",1);
table.put("hello3",1);
table.put("hello4",1);
table.put("hello5",1);
table.put("hello6",1);
//System.out.println(table); // {三轮车=80, 保时捷=100, 奥迪=100}
//简单分析一下Hashtable底层
//1.底层维护的是一个数组,Hashtable$Entry[]初始化大小是11
//2.临界值threshold=8=11*0.75
//3.扩容:按照自己的扩容机制来进行即可。
}
}
当我们加到第9个的时候,数组就扩容了,扩容到了23,它有自己的扩容机制
扩容机制源码分析:当我们把断点打在第九个元素的时候,走进去发现了一个方法protected void rehash()
下面是这个方法的源码,可以发现,我们的newCapacity是等于我们旧的容量oldCapacity乘以2再加1的
newCapacity = (oldCapacity << 1) + 1;这就是Hashtable的扩容机制
int newCapacity = (oldCapacity << 1) + 1;
if (newCapacity - MAX_ARRAY_SIZE > 0) {
if (oldCapacity == MAX_ARRAY_SIZE)
// Keep running with MAX_ARRAY_SIZE buckets
return;
newCapacity = MAX_ARRAY_SIZE;
}
Entry,?>[] newMap = new Entry,?>[newCapacity];
线程安全(同步) 效率 允许null键null值 HashMap 不安全 高 可以 Hashtable 安全 较低 不可以
Properties
- Properties类继承自Hashtable类并且实现了Map接口,也是使用一种键值对的形式来保存数据
- 他的使用特点和Hashtable类似
- Properties还可以用于从xxx.properties文件中加载数据到Properties对对象并进行读取和修改
public class Properties_ {
public static void main(String[] args) {
Properties properties = new Properties();
properties.put("奥迪",100); //k-v
//properties.put(null,100); 抛出空指针异常
//properties.put("奥迪",null); 抛出空指针异常
properties.put("保时捷",100);
properties.put("奔驰",100);
properties.put("奔驰",80); // 被替换
System.out.println(properties); //{保时捷=100, 奔驰=80, 奥迪=100}
//通过k获取value
System.out.println(properties.get("奔驰")); //80
//删除
properties.remove("奥迪");
System.out.println(properties); // {保时捷=100, 奔驰=80}
//修改。 就是替换
properties.put("奔驰A级",300);
System.out.println(properties); // {保时捷=100, 奔驰=80, 奔驰A级=300}
}
}
在开发中如何选择集合实现类
在开发中,选择什么集合实现类,主要取决于业务操作特点,然后根据集合实现类特性进行选择
-
先判断存储的类型(一组对象[单列]或者一组键值对[双列])
-
一组对象[单列]:Collection接口
- 允许重复:List
- 增删多:LinkedList[底层维护了一个双向链表]
- 改查多:ArrayList[底层维护Object类型的可变数组]
- 不允许重复:Set
- 无序:HashSet[底层是HashMap,维护了一个哈希表即:(数组+链表+红黑树)]
- 排序:TreeSet 我们可以指定排序规则
- 插入和取出顺序一致:LinkedHashSet,维护的是数组+双向链表
-
一组键值对[双列]:Map
- 键无序:HashMap[底层是:数组+链表+红黑树]
- 键排序:TreeMap
- 键插入和取出顺序一致:LinkedHashMap
- 读取文件:Properties
TreeSet排序
- 当我们使用无参构造器创建TreeSet时,任然是无序的TreeSet treeSet = new TreeSet();
- 我们可以指定排序规则
public class TreeSet_ {
public static void main(String[] args) {
TreeSet treeSet = new TreeSet(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
//调用String的compareTo方法进行字符串首字母大小比较a>b>c>d>...>z
//return ((String) o1).compareTo((String) o2); //[ad, dhf, lkss, m]
//按照长度大小排序,如果需要从大到小排,只需要将o1和o2反过来即可
return ((String) o1).length()-((String) o2).length(); // [m, ad, dhf, lkss]从小到大
}
});
//添加元素
treeSet.add("lkss");
treeSet.add("dhf");
treeSet.add("ad");
treeSet.add("m");
//treeSet.add("dhf");//这个数据是加不进去的,当我们使用字符串大小比较的时候,两个字符相同是不能加入的
treeSet.add("b"); //这个数据是加不进去的,因为我们使用的是按字符串长度来排序,他会把我们的m当成一个元素
System.out.println(treeSet);
//1.构造器把传入的比较器对象,赋给了TreeSet的底层TreeMap的属性this。comparator
}
}
TreeMap排序
- 当我们使用无参构造器创建TreeSet时,任然是无序的TreeSet treeSet = new TreeSet();
- 我们可以指定排序规则
public class TreeMap_ {
public static void main(String[] args) {
//当我们使用无参构造器的时候,存放顺序还是乱的 treemap= {jack=杰克, kristina=克瑞斯提诺, smith=斯密斯, tom=汤姆}
//TreeMap treeMap = new TreeMap();
TreeMap treeMap = new TreeMap(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
//按照传入k首字母的大小进行排序abcdefghijk
return ((String)o1).compareTo((String) o2);//{jack=杰克, kristina=克瑞斯提诺, smith=斯密斯, tom=汤姆}
//按照k字符串长度排序
//return ((String)o1).length() - ((String)o2).length();//treemap= {tom=汤姆, jack=杰克, smith=斯密斯, kristina=克瑞斯提诺}
}
});
treeMap.put("jack","杰克");
treeMap.put("tom","汤姆");
treeMap.put("kristina","克瑞斯提诺");
treeMap.put("smith","斯密斯");
treeMap.put("smith","剑来");//smith=剑来,当传入的k相同时,他会替换前面一个相同的k
treeMap.put("jjj","斯密斯");//当我们传入相同的value的时候,它也会替换掉我们的k treemap= {jack=杰克, jjj=斯密斯, kristina=克瑞斯提诺, smith=剑来, tom=汤姆}
System.out.println("treemap= "+treeMap);
}
}
Conllections工具类
Conllections是一个操作Set、List、Map等集合的工具类
Conllections中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作
Conllections常用方法
public class Collections_ {
public static void main(String[] args) {
//使用ArrayList集合测试
List list = new ArrayList();
list.add("tom");
list.add("tom");
list.add("smith");
list.add("king");
list.add("milan");
System.out.println("初始集合list=> " + list); //初始集合list=> [tom, smith, king, milan]
//reverse(List):反转List中的元素的顺序
Collections.reverse(list);
System.out.println("reverse==" + list); //reverse==[milan, king, smith, tom]
//shuffle(List):对List集合元素进行随机排序
Collections.shuffle(list);
System.out.println("shuffle==" + list); //shuffle==[tom, milan, king, smith]
//sort(List):根据元素的自然顺序对指定List集合元素按升序排序(根据k的首字母大小排序)
Collections.sort(list);
System.out.println("sort==" + list);
//sort(List,Comparator):根据指定的Comparator
Collections.sort(list, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
return ((String) o1).compareTo((String) o2);
}
});
System.out.println("List,Comparator== " + list);
//swap(List,int,int):将指定list集合中的i处元素和j处元素进行交换
Collections.swap(list, 0, 1);
System.out.println("swap ==" + list);
//Object max(Collection): 根据元素的自然顺序,返回给定集合中的最大元素
System.out.println(Collections.max(list)); // tom 自然排序,首字母最靠前的
//Object max(Collection,Comparator):根据Comparator指定的顺序,返回给定集合中的最大元素
Object max = Collections.max(list, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
return ((String) o1).compareTo((String) o2);
}
});
System.out.println(max);//tom
//Object min(Collection)根据元素的自然顺序,返回给定集合中的最小元素
System.out.println(Collections.min(list)); //king
//Object min(Collection,Comparator)根据Comparator指定的顺序,返回给定集合中的最小元素
Object min = Collections.min(list, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
return ((String) o1).length() - ((String) o2).length();
}
});
System.out.println("指定方法中最小的=="+min);//tom
//int frequency(频率)(Collection,Object):返回指定集合中指定元素的出现次数
System.out.println(Collections.frequency(list, "tom")); // 2
//void copy (List dest,List src) :将src中的内容复制到dest中
ArrayList dest = new ArrayList();
for (int i = 0; i < list.size(); i++) {
dest.add(i); //注意,如果我们直接复制的话,会报错,我们需要先把dest集合的大小填充到和list一样才能copy
}
Collections.copy(dest,list);
System.out.println("复制后的集合:=="+dest);
//boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换List对象中所有的旧值
Collections.replaceAll(list, "tom", "AKA"); //把集合中的两个tom全部替换成AKA
System.out.println("替换后的集合list= "+list);
}
}
练习
题目:
- 封装一个新闻类,包含标题和内容属性,提供get、set方法,重写toString方法,打印对象时只打印标题;
- 只提供一个带参数的构造器,实例化对象时,只初始化标题,并且实例化两个对象
- 新闻1:在瓦罗蓝我是一个无恶不作的大海盗,而成为海盗的原因是为你保驾护航,这样,你才不会被大海吓到
- 新闻2:甲板上的脚印,稳稳的在船上蔓延;在碧海的波浪里,我如愿做一条飞鱼
- 将新闻对象添加到ArrayList集合中,并且进行倒序遍历
- 在遍历集合过程中,对新闻标题进行处理,超过15字的只保留前15个,然后再后面加“…”
- 在控制台打印遍历出经过处理的新闻标题
public class HomeWork1 {
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();
arrayList.add("在瓦罗蓝我是一个无恶不作的大海盗,而成为海盗的原因是为你保驾护航,这样,你才不会被大海吓到");
arrayList.add("甲板上的脚印,稳稳的在船上蔓延;在碧海的波浪里,我如愿做一条飞鱼");
int size = arrayList.size();
for (int i = size-1; i >= 0; i--) {
System.out.println(processTitle((String) arrayList.get(i)));
}
}
public static String processTitle(String title){
if (title==null){
return "";
}
if (title.length()>15){
return title.substring(0,15) + "..."; // [0,15)
}else {
return title;
}
}
}
class News{
private String title;
private String content;
public News(String title) {
this.title = title;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
@Override
public String toString() {
return "News{" +
"title='" + title + ''' +
'}';
}
}
2、
package com.like.homework;
import java.util.ArrayList;
import java.util.Iterator;
@SuppressWarnings({"all"})
public class HomeWork2 {
public static void main(String[] args) {
ArrayList list = new ArrayList();
Car car = new Car("宝马",4000000);
Car car1 = new Car("帕拉梅拉", 1000000);
list.add(car);
list.add(car1);
System.out.println(list);
//1.remove:删除指定元素
list.remove(car);
System.out.println("remove="+list); //remove=[Car{name='帕拉梅拉', price=1000000.0}]
//2.contains:查找元素是否存在
System.out.println(list.contains(car)); // false
//3.add添加单个元素
list.add(car);
System.out.println("add=" + list); // add=[Car{name='帕拉梅拉', price=1000000.0}, Car{name='宝马', price=4000000.0}]
//4.size:获取元素个数
System.out.println(list.size()); // 2
//5.isEmpty:判断是否为空
System.out.println(list.isEmpty()); // false
//6.clear:清空
list.clear();
//7.addAll添加多个元素
list.add(list); //可以添加自己
System.out.println(list);
//8.containsAll:查找多个元素是否都存在
System.out.println(list.containsAll(list)); // true
//9.removeAll:删除多个元素
list.removeAll(list); // 删除自己
//10.使用增强for和迭代器遍历所有的car,需要重写Car的同toString
for (Object cars : list){
System.out.println(cars);
}
System.out.println("==============使用迭代器=============");
Iterator iterator = list.iterator();
while (iterator.hasNext()){
Object next = iterator.next();
System.out.println(next);
}
}
}
class Car{
private String name;
private double price;
public Car(String name, double price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
@Override
public String toString() {
return "Car{" +
"name='" + name + ''' +
", price=" + price +
'}';
}
}
public class HomeWork3 {
public static void main(String[] args) {
HashMap hashMap = new HashMap();
hashMap.put("jack",650);
hashMap.put("tom",1200);
hashMap.put("smith",2900);
System.out.println(hashMap);
//将jack工资更改为2600
hashMap.put("jack",2600);
System.out.println("jack"+hashMap);
//为所有的员工工资加100
Set set = hashMap.keySet();
for (Object key : set) {
hashMap.put(key,(Integer) hashMap.get(key)+100);
}
System.out.println(hashMap);
//遍历所有的员工
Set entrySet = hashMap.entrySet();
Iterator iterator = entrySet.iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
Map.Entry entry = (Map.Entry) next;
System.out.println(entry.getKey() +"-"+ entry.getValue());
}
System.out.println("------------遍历money------------");
Collection values = hashMap.values();
for (Object value : values) {
System.out.println(value);
}
}
}
class Employee{
private String name;
private int money;
public Employee(String name, int money) {
this.name = name;
this.money = money;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + ''' +
", money=" + money +
'}';
}
}
试分析HashSet和TreeSet分别如何实现去重的?
(1)、HashSet的去重机制:hashCode()+equals(),底层先通过存入对象,进行运算得到一个 hash值,通过hash值得到对应的索引,如果发现table索引所在的位置,没有数据,就直接存放。如果有数据,就进行eauals比较[遍历比较],如果比较后,不相同,就加入,否则就不加入。
(2) 、TreeSet的去重机制:如果你传入了一个Comparator匿名对象!就使用实现的compare去重,如果方法返回0.就认为是相同的元素/数据,就不添加,如果你没有传入一个Comparator匿名对象,则以你添加的对象实现的Compareable接口的compareTo去重.
执行下面的代码会报错吗?
public class T {
public static void main(String[] args) {
TreeSet treeSet = new TreeSet();
treeSet.add(new Person());
}
}
class Person{
}
系统将会在第一次add时便进行报错:
原因在于当我们将数据存入TreeSet时,内部会自动进行排序,例如我们存入‘A‘,’C’, ‘B’, 打印输出时将显示’A’, ‘B’, ‘C’。因此如果我们存入的数据类型为自定义类型时,系统将不知道如何进行排序。
因此我们需要implement Comparable接口,并自己定义比较规则:
public class T {
public static void main(String[] args) {
TreeSet treeSet = new TreeSet();
treeSet.add(new Person());
System.out.println(treeSet);
}
}
class Person implements Comparable{
@Override
public int compareTo(Object o) {
return 0; //如果我们返回0的话就用于只能加一个数据进去,因为返回零就认为是相同的元素/数据,就不添加
}
}
implements Comparable就不会报错
一事精致,便能动人。
心心在一艺,其艺必工;心心在一职,其职必举。



