Collection集合框架学习笔记
笔记向
文章目录
- Collection集合框架学习笔记
- 前言
- collection
- collection 体系集合
- collection 存Student对象
- List
- **ArrayList 扩容规律**
- 我们看看LinkedList的Add方法源码
- **我们看看不同结构实现方式(ArrayList & LinkedList 的区别)**
- **泛型**
- 我们再来看看泛型方法
- 泛型集合
- **下面我们讲讲collection的另一个继承子接口 set**
- 我们看看set的实现类 HashSet
- 怎么能组织这种情况的发生>? 我们先看看hashSet的存储过程
- 如果我们想要解决这个现象,我们
- STEP1 要在实体类中重写hashcode方法,
- 使new 方法add的对象和变量方法add的对象,通过equals判断,重复,
- 从而阻止新的new 的对象添加进hashset,我们进再实体类中重写以下equals方法(如果不重写,默认是继承Object的)
- TreeSet
- 红黑树是一个二叉查找树(通过自旋实现实现分支平衡,提高查找的性能)
- 不可以,因为TreeSet 的数据结构是红黑树
- 所以TreeSet是自动实现排序的,但是我们add的是Student!!
- **TreeSet不知道拿Student的什么进行排序,所以就会 Student cannot be cast to java.lang.Comparable**
- 这样才能添加对象成功
- 上面提到了Comparable接口,这里补充一下接口使用
- 小案例
- MAP集合的实现类
- Hash案例
- hashMap源码分析
- 下面我们来看看HashMap的构造方法
- put方法
- 注意JDK1.8之前链表做的是头插入,JDK1.8之后是尾插入
- HashSet和HashMap的关系
- 总结
前言
养成总结的好习惯
提示:以下是本篇文章正文内容,下面案例可供参考
collection
●概念:对象的容器,定义了对多个对象进行操作的常用方法。可实现数组的功能。
●和数组区别:
●(1)数组长度固定,集合长度不固定.
●(2)数组可以存储基本类型和引用类型,集合只能存储引用类型
●位置: java. util. *;
collection 体系集合
因为接口不能new,只能实现,所以我们Collection c = new 的只能是实现类
这里collection的new的实现类是arrayList
方法:
●boolean add (0bject obj) //添加一一个对象。
●boolean addA1l (Collection c) //将一个集合中的所有对象添加到此集合中。.
●void clear() //清空此集合中的所有对象。
●boolean contains (Object o) //检查此集合中是否包含o对象
●boolean equals (Object o) //比较此集合是否与指定对象相等。
●boolean isEmpty() //判断此集合是否为空
●boolean remove (0bject o) //在此集合中移除o对象
●int size() //返回此集合中的元素个数。
●Object[] toArray() //将此集合转换成数组。
/**
* 1.添加 2.判断存在 3.删除 4.利用for迭代
* 5.利用iterator迭代(hasNext,next,remove)
* 6.isEmpty 7.toString
* 8.clear删除全部 (但是其实在内存中对象还存在)
*/
public class CollectionDemo {
public static void main(String[] args) {
Collection collection = new ArrayList();
collection.add("a");
collection.add("b");
collection.add("c");
System.out.println(collection.size());
if (collection.contains("a")){
System.out.println("存在a,删除!");
boolean a = collection.remove("a");
if (a) System.out.println("删除成功");
}
//利用for迭代
for(Object o:collection)System.out.println(o);
//利用iterator迭代器进行迭代
Iterator iterator = collection.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
System.out.println("读取出一个删掉一个");
iterator.remove();
}
System.out.println(collection.isEmpty());
}
}
collection 存Student对象
public class CollectionDemo2 {
public static void main(String[] args) {
Collection<Student> collection = new ArrayList<>();
Student s1 = new Student("wang",12),
s2 = new Student("zi",13),
s3 = new Student("qi",14);
collection.add(s1);
collection.add(s2);
collection.add(s3);
System.out.println(collection.size());
System.out.println(collection.toString());
//下面这样再new一个判断是判断不到的,每new一个对象,就相当于再堆上创建一个新对象
if (collection.contains(new Student("qi",14))){//只能用s1,s2,s3这些变量进行判断
System.out.println("存在qi");
}
for (Student student: collection) {
System.out.println(student);
}
//迭代器版本
Iterator<Student> iterator = collection.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
}
看看collection的子接口
List
1.有序,有下标,元素可以重复
2.方法(因为list接口继承collection, 那么list中也会包含collection中的方法)
void add(int index,Object o) //在index的位置插入对象
boolean addAll(int index, Collection c)//将在一个集合中的元素添加到此集合中的index位置
●Object get(int index) //返回集合中指定位置的元素。
Object set(int index,Object o)//根据index下标设置值,返回的是原先的被覆盖的值
●List subList (int fromIndex, int toIndex) //返回from Index和toIndex之间的集合元
listIterator() 这是list的另外加的一个迭代器
listIterator(int index) 这是list的另外加的一个迭代器 从列表指定位置开始
subList(int fromList,int toIndex) 返回列表中指定的fromIndex(包括)toIndex(不包括)之间的部分list
/**
* List的特点
* 有下标,可以重复
*/
public class ListDemo1 {
public static void main(String[] args) {
List list = new ArrayList();
list.add("a");
list.add(1,"b");
list.add(2,"c");
System.out.println(list.toString());
Object a1 = list.set(0, "a1");
System.out.println(a1);
System.out.println(list.size());
System.out.println(list.toString());
//删除
list.remove(0);
System.out.println(list.size());
System.out.println(list.toString());
//foreach遍历
for (Object s:list) {
System.out.println(s);
}
//for下标遍历
//...
//iterator迭代器遍历
Iterator iterator = list.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
//ListIterator 这个是list特有的list迭代器
//这个功能要比iterator强,支持任意方向迭代,且可以添加删除修改元素
/**
* 1.hasNext() 2.hasPrevious() 3.next() 4.previous()
* 5.nextIndex(),6previousIndex() 7.remove()
* 8.add() 9.set()
*/
//正向
ListIterator listIterator = list.listIterator();
while (listIterator.hasNext()){
System.out.println(listIterator.nextIndex()+""+listIterator.next());
}
//逆向
while (listIterator.hasPrevious()){
System.out.println(listIterator.previousIndex()+""+listIterator.previous());
}
//判断
if (list.contains("a"))System.out.println("has a");
System.out.println(list.indexOf("a"));
}
}
List存基本类型会自动装箱
/**
* 因为list也是一种collection集合
* 集合是不存基本类型的,只存对象
* 所以存入的int会被自动装箱转为Integer
*/
public class ListDemo2 {
public static void main(String[] args) {
List list = new ArrayList();
list.add(10);//int会被自动装箱转为Integer
list.add(20);
list.add(30);
list.add(40);
list.remove(0);//如果remove参数为int list的remove是根据下标,而不是根据值
list.remove(new Integer(20));//传入一个对象;或者像下面
list.remove((Object) 30);//转型为Obj格式也行
System.out.println(list.toString());
list.add(10);
list.add(20);
list.add(30);
list.add(40);
List list1 = list.subList(0, 3);//是一个左闭又开的区间
System.out.println(list1.toString());
}
}
我们来看看List的实现类
ArrayList (重点!)
#使用的很多
ArrayList其中是有个数组的,数组结构实现,查询快,增删速度慢(如果在中间插入,后面的元素需要移动..)
JDK1.2版本增加,运行效率快,但是线程不安全
'对比另一个List的实现类Vector'
数组结构实现,查询快,增删速度慢
JDK1.0版本出现,运行效率慢,但是线程安全!
'对比LinkedList'
链表结构实现,查询慢,增删快!
/**
* arrayList的使用
* 存储结构:数组 查找遍历速度快, 增删速度慢
*/
public class arrayListDemo1 {
public static void main(String[] args) {
ArrayList<Student> arrayList = new ArrayList<>();
Student s1 = new Student("a",1),
s2 = new Student("b",2),
s3 = new Student("c",3);
//1.添加
arrayList.add(s1);
arrayList.add(s2);
arrayList.add(s3);
System.out.println(arrayList.size());
System.out.println(arrayList.toString());
//2.删除
arrayList.remove(0);//或者通过s1变量删除..new一个对象时删除不了的
//3.遍历 与list相同
//1.普通的前向iterator
//2.双向的增强版listIterator
//查找
System.out.println("查找");
System.out.println(arrayList.indexOf(s2));
System.out.println(arrayList.get(0));
}
}
我们来看看ArrayList的源码
我们看到确实ArrayList时继承List接口没错
我们注意array中几个重要的常量和变量!
private static final int DEFAULT_CAPACITY = 10;//默认的容量大小是10
transient Object[] elementData; // 是用来存放元素的数组 ArrayList的构造结构是数组
private int size;//实际的元素个数
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};//定义一个默认的空数组
private static final Object[] EMPTY_ELEMENTDATA = {};//定义一个空数组
下面来看构造方法
//1.无参构造
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
//存放数据的数组等于 DEFAULTCAPACITY_EMPTY_ELEMENTDATA,默认空数组(上面的final定义的)
//注意!!如果没有向集合中添加任何元素,那么默认情况下容量为0,
//那么默认容量10是怎么来的?..我们分析以下add方法👇
}
add()的源码
//1.我们知道ArrayList a = new ArrayList();这个空参构造出来,默认数组长度是0;是一个空数组!那么默认10的长度从何而来>??
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!,添加个数,添加要给元素那么size++
elementData[size++] = e;
return true;
}
//2.我们再进入ensureCapacityInternal方法,这里用到了一个ensureExplicitCapacity和calculateCapacity
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
//calculateCapacity中,将一开始的size + 1也就是0+1=1和DEFAULT_CAPACITY=10进行对比取max返回
// 就是再这里默认设置为了10的空间
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
//ensureExplicitCapacity中 上面通过取max,传来的参数为10,那么modCount修改次数就0++
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)//elementData一开始为空,那么这里就是10-0=10
grow(minCapacity);//grow增长(10)??我进进去grow看看
}
grow源码 (这既是数组进行扩容的实际代码)
//数组进行扩容
private void grow(int minCapacity) {//这个参数minCapacity==10
// overflow-conscious code
int oldCapacity = elementData.length;//长度为0
int newCapacity = oldCapacity + (oldCapacity >> 1);//还是0
if (newCapacity - minCapacity < 0)//0-10==-10<0 成立
newCapacity = minCapacity;//那么就将10赋值给newCapacity
if (newCapacity - MAX_ARRAY_SIZE > 0)//10-MAX_ARRAY_SIZE(这个值非常大..)>0不成立
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);//!!将这个新的数组(长度为10),传给这个 elementData
}
#所以ArrayList没有添加元素之前是0,
'一旦添加任意一个元素,那么长度就是10!'
长度一旦超过10就开始扩容~~~~~~~
#假如我们已经放入10个元素了,假如说size==10,那么ensureCapacityInternal(10+1)继续进入下一层进行扩容
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
#进入后跳转到ensureExplicitCapacity(11)
private void ensureExplicitCapacity(int minCapacity) {#minCapacity的值为11
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0) #11-10=1>0成立进入grow(11)
grow(minCapacity);
}
#进入grow(11)
private void grow(int minCapacity) {#11
// overflow-conscious code
int oldCapacity = elementData.length;#10
int newCapacity = oldCapacity + (oldCapacity >> 1);#右移一位相当于除以2,现在new=10+(10/2)=15
if (newCapacity - minCapacity < 0)#15-11=4>0不成立
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)#15-很大的一个值 肯定是个负值所以也不成立
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);#那么扩容从10到15
}
'所以当插入第11个元素的时候,arrayList就扩容到了15'
ArrayList 扩容规律
//从以下两条语句我们可以看出扩容规律!
int oldCapacity = elementData.length;#10
int newCapacity = oldCapacity + (oldCapacity >> 1);#右移一位相当于除以2,现在new=10+(10/2)=15
//因为右移一位相当于整除2
//所以每次扩容的规模就相当于 x 1.5倍!!!
10--->15--->22--->(int)(22*1.5)->>>
那问题又来了,我们添加的数据是怎么放进ArrayList的,
我们回头看看add方法
public boolean add(E e) {
ensureCapacityInternal(size + 1); // 这句话就是将0+1扩容到了10的默认长度
elementData[size++] = e;//这一句就是将元素一个个放到数组中的代码
return true;
}
下面我们来看看Vector
Vector在JDK1.0就已经出现
1.'数组结构实现的',所以查找快,但是增删慢
2.JDK1.0版本出现,运行效率'慢','线程安全'
//看看构造方法
Vector() 构造一个空向量,时期内部数据数组的大小为'10',其标准容量增量为0;
add()
addAll(index,Collection) 将collection中的指定位置的元素添加进vector
addAll(Collection)将collection中所有元素添加进vector
contains()
elements()返回此向量的组件的枚举,(里面有两个方法如下)
hasMoreElements()
nextElement()
/**
* 存储结构:数组
* jdk1.0,线程安全,运行效率低
*/
public class VectorDemo {
public static void main(String[] args) {
Vector vector = new Vector();
vector.add("a");
vector.add("b");
vector.add("c");
System.out.println(vector.size());
//删除跟之前一样,remove(index或者根据值),clear是清空
//遍历可以使用index,或者foreach,迭代器iterator
//下面看看特有的方法枚举器enumerate
Enumeration elements = vector.elements();
while(elements.hasMoreElements()){
System.out.println(elements.nextElement());
}
//判断
if (vector.contains("a"))System.out.println("has a");
if (vector.isEmpty())System.out.println("empty");
//vector其他方法
vector.get(0);
vector.elementAt(0);
vector.firstElement();
vector.lastElement();
}
}
LinkedList (重要)
#linkedList
其中数据结构是一个'双向链表'--->增删快,查询慢
/**
* 数据结构:双向链表
*/
public class LinkedListDemo {
public static void main(String[] args) {
LinkedList<Student> linkedList = new LinkedList<>();
Student s1 = new Student("wan1",1);
Student s2 = new Student("wan2",2);
Student s3 = new Student("wan3",3);
//1.添加数据
linkedList.add(s1);
linkedList.add(s2);
linkedList.add(s3);
System.out.println(linkedList.size());
System.out.println(linkedList.toString());
//2.删除
// linkedList.remove(s1);
// linkedList.remove(0);
//3.遍历操作
//1.foreach
for (Student student : linkedList) {
System.out.println(student);
}
//fori
for (int i = 0; i < linkedList.size(); i++) {
System.out.println(linkedList.get(i));
}
//iterator
Iterator<Student> iterator = linkedList.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
//注意这里还有个增强的迭代器 可以正向也可以反向,还可以添加元素
ListIterator<Student> studentListIterator = linkedList.listIterator();
while (studentListIterator.hasNext()){
System.out.println(studentListIterator.next());
}
while (studentListIterator.hasPrevious()){
System.out.println(studentListIterator.hasPrevious());
}
//判断 contains isEmpty
//获取 indexOf get
}
}
我们来看看LinkedList的源码
//先看常量和变量
transient int size = 0; //初始大小0
transient Node<E> first; //头节点
transient Node<E> last; //尾节点
public LinkedList() { } //构造方法为空
我们看看LinkedList的Add方法源码
public boolean add(E e) {
linkLast(e); //将添加的元素连接的Last 也就是最后的位置,进入该方法👇
return true;
}
'核心代码!非常关键!'
void linkLast(E e) {
final Node<E> l = last; //我们先看看这个Node有啥👇 这里先将没添加之前的last赋给L
final Node<E> newNode = new Node<>(l, e, null); //然后这里将之前的L和插入的值传进Node构造方法👇
last = newNode; // 将尾指针指向newNode
if (l == null)
first = newNode; //如果之前linkedList就是个空的,那么将头指针指向这个唯一的节点
else
l.next = newNode;// 如果不为空,把刚刚的尾节点(现在成了倒数第二个)的next指向尾节点
size++; //长度++
modCount++; //修改次数++
}
private static class Node<E> {
E item; //当前元素
Node<E> next; //指向下个元素
Node<E> prev; //指向上个元素
//由此看出是一个双向的指针!
Node(Node<E> prev, E element, Node<E> next) { //构造方法 前一个元素,当前元素,下一个元素
this.item = element;
this.next = next;
this.prev = prev;
}
}
我们看看不同结构实现方式(ArrayList & LinkedList 的区别)
1.区别就是ArrayList 是一个数组;//数组的物理地址是连续的
LinkedList 是一个双向链表;//链表的物理地址是分散的,用一个个指针相连
泛型
泛型是在JDK1.5引入的一种新特性,本质就是参数化的类型,把类型当作参数进行传递
常见形式有泛型类,泛型接口,泛型方法
语法:<T,...>
T称为类型占位符,表示一种引用类型,所以我们用的时候就要把这个类型传过去 像是List<Student>
E表示element元素
k表示一个key键
v就是value 其实就是一个字母,写什么都行,但是还是按规则来
好处:
1.提高代码的重用性
2.防止类型转换异常,提高代码的安全性
'注意::!!
1.一定要注意泛型只能是引用类型,int基本类型不可以!
2.不同的泛型对象不能相互赋值,比如一个泛型是String 一个是Integer,不可以相互赋值
我们自己定义一个泛型MyGeneric
/**
* 泛型类 : 类名<T,..>
* T表示的就是一个数据类型占位符,需要传进来一个引用类型,可以写多个用逗号隔开
*/
public class MyGeneric<T> {
//使用这个泛型T创建对象
//1.创建变量
//注意!!在这个泛型类中,我们只能创建变量,但是不能实例化new对象,因为这个T是什么类型,我们是不知道的,也不知道是否有无参构造,所以new不出来
T t;
//2.添加方法
public void show(T t){
System.out.println(t);
}
//3.使用泛型作为方法的返回值
public T getT(){
return t;
}
}
使用这个泛型
public class TestGeneric {
public static void main(String[] args) {
//使用泛型类创建对象
MyGeneric<Student> studentMyGeneric = new MyGeneric<>();
studentMyGeneric.t = new Student("wang",1);
Student t = studentMyGeneric.getT();
System.out.println(t);
studentMyGeneric.show(new Student("wang2",2));
/******/
MyGeneric<Integer> myGeneric = new MyGeneric<>();
//一定要注意泛型只能是引用类型,int基本类型不可以!
myGeneric.t = 1;
System.out.println(myGeneric.getT());;
myGeneric.show(2);
}
}
我们再来看看泛型接口!
/**
* 泛型接口 : 接口名<T> T类型
* 注意!!不能使用泛型创建,静态常量,new 什么的都不行!
*/
public interface MyGenricInterface<T> {
String name = "wang";
T server(T t);
}
然后进行泛型接口的实现,有两种方法,
1.在实现类implements的时候就指明T的类型
public class MyGenricInterfaceImpl implements MyGenricInterface<String> {
@Override
public String server(String s) {
return s;
}
public static void main(String[] args) {
MyGenricInterfaceImpl myGenricInterface = new MyGenricInterfaceImpl();
System.out.println(myGenricInterface.server("hello"));
}
}
2.使用实现类的时候再指定T的类型
public class MyGenricInterfaceImp2<T> implements MyGenricInterface<T> {
@Override
public T server(T t) {
return t;
}
public static void main(String[] args) {
MyGenricInterfaceImp2<String> myGenricInterface = new MyGenricInterfaceImp2<>();
System.out.println(myGenricInterface.server("hello"));
}
}
我们再来看看泛型方法
泛型方法的语法是 在类返回值之前加<T>
/**
* 泛型方法,语法: <T> 放在方法返回值类型前面
*/
public class MyGenericMethod {
public void show(){
System.out.println("普通方法");
}
//泛型方法
public <T> void show(T t){
System.out.println("泛型方法"+t);
}
//也可以将方法的返回值设置为T
public <T> T showWhitReturn(T t){
System.out.println("泛型方法"+t);
return t;
}
}
看看泛型方法的调用
//测试泛型方法
MyGenericMethod method = new MyGenericMethod();
method.show("hello");//这里的泛型类型就不用传,以传递的数据类型决定
System.out.println(method.showWhitReturn(1));//返回Integer
这样用下来我们就可以真正体会到泛型的类,接口,方法的好处
1.提高了代码的重用性
确实,如果一个方法是 public <T> T get(T t){return t;}
'如果不用泛型的话需要对各种类型的情况进行一次重写..降低工作效率'
2.防止类型抓换异常,提高代码的安全性
在泛型集合里可以体现,下面会讲
泛型集合
默认的时候是Object,如果不指定很容易出现错误!
像下面这个例子..又放String 又放Integer
创建的时候就规定好是什么类型!!省的之后类型转换
ArrayList<String> a = new ArrayList<>();
Link基础就这样讲完了…
下面我们讲讲collection的另一个继承子接口 set
1.特点:无序,无下标,元素不可以重复('List的特点完全相反')
2.方法完全继承自Collection的方法,set中并没有定义其他的抽象方法,都是继承来的
'所以就是一个不包含重复元素的collection
3.实现类:
1.HashSet['重点']:基于HashCode实现元素不重复
当存入元素的哈希码相同时,会调用equals进行确认,如果时true,则拒绝后者存入
2.TreeSet:基于排列顺序实现元素不重复
下面看看set的使用
/**
* set接口的使用
* 1.无顺序(添加顺序和遍历顺序不一致) 2.不能重复,3.无下标
*/
public class SetDemo {
public static void main(String[] args) {
Set<String> set = new HashSet<>();
set.add("b");
set.add("a");
set.add("b");//存入相同的值,是存不进去的
set.add("c");
System.out.println(set.size());
System.out.println(set.toString());
//删除
//set.remove("a");
//遍历 foreach
for (String s : set) {
System.out.println(s);
}
//迭代器
Iterator<String> iterator = set.iterator();
while (iterator.hasNext())System.out.println(iterator.next());
//判断 isEmpty 和 contains
if (set.contains("b"))System.out.println("yes b");
}
}
我们看看set的实现类 HashSet
1.HashSet['重点']:基于HashCode实现元素不重复
当存入元素的哈希码相同时,会调用equals进行确认,如果时true,则拒绝后者存入
'存储结构:哈希表(数组+链表+(在jdk1.8之后又加入了红黑树))!!'
hashSet的使用
/**
* hashset 集合的使用
* 存储结构:哈希表(数组+链表+(在jdk1.8之后又加入了红黑树))
*/
public class HashSetDemo {
public static void main(String[] args) {
HashSet<String> hashSet = new HashSet<>();
hashSet.add("b");
hashSet.add("c");
hashSet.add("a");
System.out.println(hashSet.toString());
System.out.println(hashSet.size());
//删除 hashSet("a")
//foreach
for (String s : hashSet) {
System.out.println(s);
}
//iterator
Iterator<String> iterator = hashSet.iterator();
while (iterator.hasNext())System.out.println(iterator.next());
//contains
System.out.println(hashSet.contains("a"));
System.out.println(hashSet.isEmpty());
}
}
//我们再看一个HashSet的案例
public class HashSetDemo2 {
public static void main(String[] args) {
HashSet<Student> hashSet = new HashSet<>();
Student s1 = new Student("w1",1);//已经添加一次这个对象了
Student s2 = new Student("w2",2);
Student s3 = new Student("w3",3);
hashSet.add(s1);
hashSet.add(s2);
hashSet.add(s3);
hashSet.add(new Student("w1",1));//这里又new了一个对象,问能不能添加进hashSet?
System.out.println(hashSet.toString());
}
}
答案是能!
因为这个new出来的新的没有名字的对象 跟前面的s1不是一个对象.
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Um6yn08Q-1614316722267)(collection.assets/image-20210225122808322.png)]
怎么能组织这种情况的发生>? 我们先看看hashSet的存储过程
1.根据'hashCode'计算保存的位置(在数组中的位置)
那么这个位置为空,那么就直接保存;
如果不为空,那么就执行第二步
2.如果放的地方已经存在一个元素了,就再执行equals方法判断'已存在的对象'和'新加入的对象'是不是一个对象
,如果equals方法为ture,那么就是相同的对象
如果equals方法为false 那么就形成数组后的链表
如果我们想要解决这个现象,我们
STEP1 要在实体类中重写hashcode方法,
//对姓名和年龄作为hashCode的依据进行相加返回
@Override
public int hashCode() {
int n1 = this.name.hashCode();
int n2 = this.age;
return n1 + n2;
}
//这样同一个姓名+年龄的Student就会是一样的HashCode,
'但是这样还是能存进来,因为使用equals方法进行对比,返回值为false,毕竟在堆上是两个不同的对象,只是属性相同'
使new 方法add的对象和变量方法add的对象,通过equals判断,重复,
从而阻止新的new 的对象添加进hashset,我们进再实体类中重写以下equals方法(如果不重写,默认是继承Object的)
//Object类中的源码
public boolean equals(Object obj) {
return (this == obj);
}
所以我们再Student类中,应该对他进行重写
@Override
public boolean equals(Object obj) {
if(this == obj){
return true;//如果相等就直接返回true
}
if (obj == null){
return false;//为null就直接false
}
if (obj instanceof Student){ //注意这个判断instanceof,判断obj是否为Student类的实例,返回布尔值
//instanceof 严格来说是Java中的一个双目运算符,用来测试一个对象是否为一个类的实例
Student student = (Student)obj;
if (this.name == student.getName() && this.age == student.getAge()){
return true;
}
}
return false;
}
}
//现在相同信息的对象,跟去hashcode计算,然后进行equals的对比,返回的是true,那么说明相同,于是就拒绝存入hashset
//这样的话不管是添加,删除,判断 我们都可以用new了个新对象堆已存在hashset中对象进行判断
还有一个问题就是我们看到hashCode()的源码
会经常看到一个数组 31
/* 下面这个是hashCode的一个实现实现类的方法 */
@Override public int hashCode() {
int hash = 7;
hash = 31 * (hash + name.hashCode());
hash = 31 * (hash + styleClassSet.hashCode());
hash = 31 * (hash + styleClassSet.hashCode());
hash = (id != null) ? 31 * (hash + id.hashCode()) : 0;
hash = 31 * (hash + pseudoClassState.hashCode());
return hash;
}
//为什么要使用31 呢???
原因
1.因为31是素数,设置初始值为31 ,然后在31的基础上进行加值,能够减少哈希散列冲突
2.31能够提高执行的效率 因为 31*i 等价于 (i<<5)-i; 比如i如果为2那么62 = (2<<5)-2 = 2的6次方-2
(1)位运算能够加快执行效率
(2)位运算<<左移动相当于*2,>>右移动相当于整除2
接着介绍另一个set继承子接口的实现类
TreeSet
1.数据结构:红黑树
2.基于排列顺序实现元素不重复
3.实现了SortedSet接口,堆集合元素自动排序
4.元素对象的类型必须实现Comparable接口,指定排序规则
5.通过CompareTo方法确定是否为重复元素
红黑树是一个二叉查找树(通过自旋实现实现分支平衡,提高查找的性能)
/**
* TreeSet的使用
* 存储结构:红黑树
*/
public class TreeSetDemo {
public static void main(String[] args) {
TreeSet<String> treeSet = new TreeSet<>();
treeSet.add("2aaa");
treeSet.add("aaa");
treeSet.add("3bbb");
treeSet.add("bbb");
treeSet.add("ccc");
treeSet.add("1ccc");
System.out.println(treeSet.size());
//[1ccc, 2aaa, 3bbb, aaa, bbb, ccc]
//set是无序的指的是不按照插入顺序,但是会自动排序
System.out.println(treeSet.toString());
for (String s : treeSet) {
System.out.println(s);
}
System.out.println("----");
Iterator<String> iterator = treeSet.iterator();
while (iterator.hasNext())System.out.println(iterator.next());
System.out.println(treeSet.contains("abc"));
}
}
我们看看TreeSet插入普通的Student对象行不行?
/**
* TreeSet的使用
* 存储结构:红黑树
*/
public class TreeSetDemo2 {
public static void main(String[] args) {
TreeSet<Student> treeSet = new TreeSet<>();
Student s1 = new Student("wang1",1);
Student s2 = new Student("wang2",2);
Student s3 = new Student("wang3",3);
treeSet.add(s1);
treeSet.add(s2);
treeSet.add(s3);
System.out.println(treeSet.size());
}
}
不可以,因为TreeSet 的数据结构是红黑树
所以TreeSet是自动实现排序的,但是我们add的是Student!!
TreeSet不知道拿Student的什么进行排序,所以就会 Student cannot be cast to java.lang.Comparable
//所以TreeSet的要求是元素必须实现Comparable接口!!!
public class Student implements Comparable<Student>{
@Override
public int compareTo(Student o) {
//先比姓名,如果姓名一样,再比年龄
int n1 = this.getName().compareTo(o.getName());
int n2 = this.getAge() - o.getAge();
return n1 == 0?n2 : n1;//如果姓名一样则 返回n2,否则直接返回n1
}
}
这样才能添加对象成功
上面提到了Comparable接口,这里补充一下接口使用
'使用匿名类实现comparable方法,创建集合的时候我们就将比较规则告诉treeSet'
/**
* TreeSet
* Comparable接口实现:定制比较(比较器)
*/
public class TreeSetDemo3 {
public static void main(String[] args) {
//创建集合的时候我们就将比较规则告诉treeSet 通过这个匿名的接口实现类
TreeSet<Student> treeSet = new TreeSet<>(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
//先比较年龄再比较姓名
int n1 = o1.getAge() - o2.getAge();
int n2 = o1.getName().compareTo(o2.getName());
return n1 == 0?n2:n1 ;
}
});
}
}
//再写一个案例,加入要求使用TreeSet对一堆字符串实现,按照字符串长度进行排序
/**
* TreeSet
* Comparable接口实现:字符串按照长度进行排序
*/
public class TreeSetDemo4 {
public static void main(String[] args) {
//创建集合的时候我们就将比较规则告诉treeSet 通过这个匿名的接口实现类
TreeSet<String> treeSet = new TreeSet<>(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
//先比较年龄再比较姓名
int n1 = o1.length() - o2.length(); //定制按照长度进行排序!
return n1 ;
}
});
treeSet.add("a");
treeSet.add("ab");
treeSet.add("abb");
treeSet.add("dabb");
System.out.println(treeSet.toString());
}
}
我们来看看map的体系结构
Map父接口
1.特点,存储一对键值对,无序,无下标,键不可以重复,值可以重复
2.方法
1.put(k,v)
2.get(k)
3.keySet()'重要:返回所有的key
4.values()返回包含所有值的collection集合
5.set<Map.Entry<k,v>> 键值匹配set集合
6.containsKey()
7.containsValue()
8.entrySet()'重要:返回此映射中包含映射关系的Set视图'
9.size
10.isEmpty
11.clear
...
小案例
/**
* Map接口的使用,
* 特点:1.存储键值对,2.键不能重复,值可以重复 3.无序(插入顺序),但是自动根据key排序
*/
public class MapDemo {
public static void main(String[] args) {
Map<String, String> map = new HashMap<>();
//1.添加
map.put("b","b1");
map.put("c","c1");
map.put("a","a1");
map.put("b","b2");//注意添加重复的key的时候后面添加的value替换前面的value
System.out.println(map.size());
System.out.println(map.toString());
//2.删除
//map.remove("b");
//3.遍历
//使用keySet集合---key值转为set进行遍历(不包含value,所以不能用iterator进行遍历value,value只能map.get获取)
Set<String> set = map.keySet();
for (String s : set) { //可以写成for(String s:map.keySet())
System.out.println(s+map.get(s));
}
System.out.println("---------------");
//使用entrySet()进行遍历(entries,就是键值对)[效率要高于keySet,一次性获取key和set,keySet()只是获取了key]
Set<Map.Entry<String, String>> entries = map.entrySet();
for (Map.Entry<String, String> entry : entries) {
System.out.println(entry);//直接输出键值对
System.out.println(entry.getKey()+"--"+entry.getValue());//也可以get键值对的key和value
}
}
}
MAP集合的实现类
HashMap
HashMap['重点']
1. jdk1.2版本出现,线程不安全,运行效率快,允许用null作为key或value
2. '默认初始容量是16!!默认加载因子是0.75(加载因子就是比如容量为100,如果元素满75个便开始扩容)'
(arrayList默认是10,扩容方案是满了之后1.5倍扩容)
3. 方法跟map相同
4. '存储结构,哈希表(数组+链表+(JDK1.8之后加入红黑树))'
Hash案例
/**
* 存储结构,哈希表(数组+链表+(JDK1.8之后加入红黑树))
*/
public class HashMapDemo {
public static void main(String[] args) {
HashMap<User,String> hashMap = new HashMap<>();
//1.添加元素
User u1 = new User("wang1",1);
User u2 = new User("wang2",2);
User u3 = new User("wang3",3);
hashMap.put(u1,"1");
hashMap.put(u2,"2");
hashMap.put(u3,"3");
//问题来了,通过new对象还是破坏了键的唯一性,在堆中是不同的对象,所以加进来了,我们要通过user信息判断唯一性
//在hashset说过,hash表是通过hashCode和equals进行判断重复
//我们还是要重写hashCode和equals(通过姓名和stuNo判断重复)
hashMap.put(new User("wang3",3),"4");//重写之后这个4将会覆盖之前的3
System.out.println(hashMap.size()+"个,"+hashMap.toString());
}
}
tips:'通过Alt+ins快捷键可以很快的创建hashCode和equals关于name和stuNo的重写判断'
//3.删除
// hashMap.remove(u1);
//遍历
Set<User> users = hashMap.keySet();
for (User user : users) {
System.out.println(user+","+hashMap.get(user));
}
//entrySet.
Set<Map.Entry<User, String>> entries = hashMap.entrySet();
for (Map.Entry<User, String> entry : entries) {
System.out.println(entry.getKey()+","+entry.getValue());
}
//判断
if (hashMap.containsKey(new User("wang1",1) ))System.out.println("yes");
}
hashMap源码分析
//重要常量
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 默认初始容量大小 aka 16
static final int MAXIMUM_CAPACITY = 1 << 30; // 最大的容量大小..2的30次方
static final float DEFAULT_LOAD_FACTOR = 0.75f; // 加载因子0.75 (容量如果100,当大于75个的时候,则开始扩容)
static final int TREEIFY_THRESHOLD = 8;//JDK1.8加入的,因为加入了红黑树,链表长度大于8,则红黑树替代链表
static final int MIN_TREEIFY_CAPACITY = 64;//JDK1.8因红黑树而加入,数组长度大于64启动红黑树替代链表
/*上面这个8的意思是:在hash表中,同过hashCode判断加入数组下标位置,通过equals判断是否相同元素,
如果不相同则加入数组后的链表, 如果链表长度大于8,且数组长度大于64,则用红黑树替代链表,提高执行效率!*/
static final int UNTREEIFY_THRESHOLD = 6;//链表长度减少到小于6的时候,用链表替代回红黑树
transient Node<K,V>[] table;//哈希表数组
transient int size;//元素个数
static class Node<K,V> implements Map.Entry<K,V> {//看到我们存的键值对就是一个个Node,
final int hash;
final K key;
V value;
Node<K,V> next;//是个单向的链表,指向next
....}
下面我们来看看HashMap的构造方法
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // 无参构造就是设定了一个构造因子0.75
}
//但是我们没有添加元素的时候,hashMap的table = null , size = 0
//当添加第一个元素的时候才会将数组创建到size=16
//所以我们如果创建了很多集合,还没又到添加元素的时候,是可以节省空间的,因为没添加的时候创建出来的数组就是null和0
put方法
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);//hash(key)后面你在分析,是一个产生hash值的东西
}
// 进入putVal
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;//这里其实是三行代码..就是创建变量
if ((tab = table) == null || (n = tab.length) == 0)//因为一开始table=nul所以是成立的,并给tab赋值
n = (tab = resize()).length;//执行这一句
//进入resize()
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;//将此时的table赋值给oldTab
int oldCap = (oldTab == null) ? 0 : oldTab.length;//oldTab 的oldCap =0
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) {//不满足 直接取看else
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && //这里注意!!!扩容方式是 X2
oldCap >= DEFAULT_INITIAL_CAPACITY) //newCap = oldCap << 1就相当于x2
newThr = oldThr << 1; // double threshold
}
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
else { // 第一次创建的oldTab的大小等于0走这里
newCap = DEFAULT_INITIAL_CAPACITY;//newCap新的空间创建为16
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);//扩容边界就是16*0.75取int=12
}
if (newThr == 0) {//不进去
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];//关键!将默认16容量的数组传给newTab
table = newTab;//newTab转给table..现在数组大小就是16了
//在返回上一层putVal方法里
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;//刚才在这里如果是第一次put,那么就将大小resize到16,扩容阈=12
if ((p = tab[i = (n - 1) & hash]) == null)//但是现在table数组中还是没有值的,tab[i]所以这里为null
tab[i] = newNode(hash, key, value, null);//在这个tab[i]创建一个新的Node节点
else { ... }//不用看
++modCount;//操作数++
if (++size > threshold)//size+1,如果大于扩容边界的时候,那么执行resize
resize(); //扩容,扩容规则就是x2 每次都是下次的两倍
' 16->32->64....(扩容与还是一样0.75这样判断)
' 回顾:arrayList是10->15->22 每次扩容1.5倍
注意JDK1.8之前链表做的是头插入,JDK1.8之后是尾插入
HashSet和HashMap的关系
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable
{
static final long serialVersionUID = -5024744406713321676L;
private transient HashMap<E,Object> map;//我们看到...hashSet下面其实就是一个hashMap!
//所以说只是用set的方法..底层其实都是map的方法
//1.无参构造
public HashSet() {
map = new HashMap<>();
}
//2.size
public int size() {
return map.size();
}
//3.add就是map的put,也就是说HashSet就用的是HashMap的Key保存数据!
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
看看Map的另一个实现类(了解)
HashTable
JDK1.0,线程安全,运行效率慢,不允许null作为key或者value
哈希表结构(数组+链表)
现在已经不怎么用了!
但是'HashTable的子类Properties'还是经常用到
Properties 是一个HashTable的子类
'hashtable的子类,要求key和value都是String,通常用语配置文件的读取
跟流有关.
看看Map的另一个实现类
TreeMap
1.结构:红黑树 (讲TreeSet也是一个红黑树,记住只要是tree..就是红黑树)
2.实现了Sortedap接口,(Map的子接口),可以对key自动排序
#案例'如果直接讲一个没有实现comparable接口的user插入treeMap,那么就会报错!'
public class TreeMapDemo {
public static void main(String[] args) {
TreeMap<User, String> treeMap = new TreeMap<>();
User u1 = new User("wang1",1);
User u2 = new User("wang2",2);
User u3 = new User("wang3",3);
treeMap.put(u1,"1");
treeMap.put(u2,"2");
treeMap.put(u3,"3");
System.out.println(treeMap.size());
System.out.println(treeMap.toString());
}
}
原理同treeSet当时是一样的
因为数据结构是一个排序的二叉树(红黑树)如果我们将User作为Key的话,'程序不知道我们用什么依据进行排序
那么就'无法创建树'
所以我们要像之前一样
1.让user类实现Comparable接口并重写compare对比方法
public class User implements Comparable<User>{
...
@Override
public int compareTo(User o) {
int n1 = this.getName().compareTo(o.getName());
int n2 = this.getStuNo()-o.getStuNo();
return n1 == 0 ? n2 : n1;
}
...
}
2.或者直接在创建树的时候就告诉树,User的对比方法
public class TreeMapDemo {
public static void main(String[] args) {
TreeMap<User, String> treeMap = new TreeMap<>(new Comparator<User>() {
@Override
public int compare(User o1, User o2) {
int n1 = o1.getName().compareTo(o2.getName());
int n2 = o1.getStuNo()-o2.getStuNo();
return n1 == 0 ? n2 : n1;
}
});
User u1 = new User("wang1",1);
User u2 = new User("wang2",2);
User u3 = new User("wang3",3);
treeMap.put(u1,"1");
treeMap.put(u2,"2");
treeMap.put(u3,"3");
System.out.println(treeMap.size());
System.out.println(treeMap.toString());
}
}
TreeSet 和 TreeMap有什么关系???
//我们看看TreeSet的源码.发现treeSet的源码其实就是TreeMap
public TreeSet() {
this(new TreeMap<E,Object>());
}
//add就是map中的put
//TreeSet就是将值存进的TreeMap的value (用键的位置)保证了set值的唯一不重复性
public boolean add(E e) {
return m.put(e, PRESENT)==null;
}
Collection工具类的使用
下面讲讲Collections工具类的时候(并补充了Arrays工具类实现集合和数组之间的转换问题)
/**
* Collection工具类的使用
*/
public class CollectionUtilDemo {
public static void main(String[] args) {
ArrayList<Integer> arrayList = new ArrayList<>();
arrayList.add(20);
arrayList.add(5);
arrayList.add(10);
arrayList.add(30);
arrayList.add(6);
//sort
System.out.println("排序前");
System.out.println(arrayList.toString());
Collections.sort(arrayList);
System.out.println("排序后");
System.out.println(arrayList.toString());
//binarySearch二分查找的前提是先排序
int i = Collections.binarySearch(arrayList, 30);//返回的是下标
System.out.println(i);
//copy复制
ArrayList<Integer> arrayListCopy = new ArrayList<>();
//这个copy方法有瑕疵.必须要求两个数组一样大的前提才能copy
for (Integer integer : arrayList) {
arrayListCopy.add(0);
}
Collections.copy(arrayListCopy,arrayList);
System.out.println(arrayListCopy.toString());
//翻转reverse
Collections.reverse(arrayList);
System.out.println(arrayList);
//shuffle打乱顺序
Collections.shuffle(arrayList);
System.out.println(arrayList);
//补充
//1.list集合转为数组
Integer[] integers = arrayList.toArray(new Integer[0]);//给定长度小于list那么按list的size为准
System.out.println(integers.length);
System.out.println(Arrays.toString(integers));
//2.数组转为集合(Arrays工具包) 记得基本类型不支持(int不行Integer才行!)
// 但是这个集合是一个受限集合,不能add和remove,
// 因为数组长度是固定的,转过来list还是不能添加
List<Integer> integers1 = Arrays.asList(integers);
System.out.println(integers.toString());
}
}
总结
提示:这里对文章进行总结:
例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。