目录
1.集合框架
1.1为什么要使用集合框架?
①.数组定容具有局限性
②.集合数组是一个长度可变的容器
③.自定义一个长度可变的数组容器:
import java.util.Arrays;
/**
* @Description:
* @Author:LouFangRui
* @Date:2022/4/15 18:48
*/
//自定义一个长度可变的数组容器
public class MyArray {
//声明一个Object类型的数组
private Object [] arr;
private int size;
//无参构造
public MyArray() {
//规定数组长度
this(3);//调用的是本类中其他的构造函数
}
//有参构造
//表示数组的长度
public MyArray(int size) {
//判断数组长度是否合法
if (size<0){
//如不合法抛出一个异常
throw new RuntimeException("数组长度有误");
}
//合法更新数组
arr=new Object[size];
}
//把数组元素o放进数组arr
public void Add(Object o){
//判断数组是否为空
if (size>=arr.length){
//如果数组已满,则进行扩容
Object [] newArr= Arrays.copyOf(arr,size*2);
arr=newArr;
}
//如果数组不满,则进行数据添加
arr[size]=o;
size++;
}
//根据下标获取数组中的元素
public Object getData(int index){
if (index>=size){
throw new ArrayIndexOutOfBoundsException("下标越界");
}
Object o=arr[index];
return o;
}
}
创建测试类,测试我们手写的容器
/**
* @Description:
* @Author:LouFangRui
* @Date:2022/4/15 19:04
*/
public class Test {
public static void main(String[] args) {
MyArray a1 = new MyArray(3);
a1.Add("我是0");
a1.Add("我是1");
a1.Add("我是2");
a1.Add("我是3");
a1.Add("我是4");
//获取指定元素
Object getData=a1.getData(1);
System.out.println(getData);
}
}
java官网基于数组根据不同的数据结构创建了多个类,而这些类统称为集合框架。
1.2 集合框架架构
1.3 List集合-ArrayList
1.3.1 创建集合对象
//创建一个集合对象如果没有固定长度,那么ArrayList集合容器的默认容器长度为10
List list=new ArrayList();
//当然我们也可以自己定义长度
List list1=new Arraylist(15);
1.3.2 添加操作
//集合框架,增删改查
//创建集合数组对象
List list1=new ArrayList(3);
//添加数据到数组(数据类型不固定,可以存储各种数据类型,自动扩展数组长度)
list1.add("0");
list1.add(1);
list1.add(2.0);
list1.add(true);
list1.add(new Date());
System.out.println(list1);
//再下标为2的位置上添加数据,后面的数据会自动位移
list1.add(2,"我是添加的数据");
System.out.println(list1);
//添加多个元素到数组list1中
List list2=new ArrayList(2);
list2.add("a");
list2.add("b");
list2.add("c");
list1.addAll(list2);
System.out.println(list1);
ArrayList集合数组可以同时储存不同类型的数据(对象类型数据),并且在超出固定数组长度时自动继续扩展。
1.3.3 删除操作
//删除
//删除下标为3的元素
list1.remove(3);
System.out.println(list1);
list1.removeAll(list2);
System.out.println(list1);
//清空集合中的元素
list2.clear();
System.out.println(list2);
1.3.4 修改操作
//修改操作
//list.set(要修改的数据下标,"要修改的内容")
list1.set(1,"我是修改过后的1");
System.out.println(list1);
1.3.5 查询操作
//查询操作
//创建一个集合数组对象
List list3=new ArrayList();
list3.add(0);
list3.add(1);
list3.add(2);
list3.add(3);
//查询的方法
//根据下标获取元素
Object pos=list3.get(1);
System.out.println(pos);
//获取集合元素的个数
int num=list3.size();
System.out.println(num+"个元素");
//判断元素是否在集合中
boolean a=list3.contains(0);
System.out.println(a);
//查询元素在集合中第一次出现的位置
int posFirst=list3.indexOf(2);
System.out.println("第一次出现的下标为:"+posFirst);
//遍历集合中的元素
for (int i=0;i<list3.size();i++){
Object num2=list3.get(i);
System.out.println(num2);
}
System.out.println("==================================");
//for each循环
for (Object num3:list3){
System.out.print(num3);
}
1.3.6 ArrayList底层源码
从构造方法来入手。new ArrayList(22) 底层声明了一个Object类型的数组 名字elementData
Object[] elementDatapublic ArrayList(int initialCapacity) {
if (initialCapacity > 0) { //大于0
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) { //等于初始化为一个空数组
this.elementData = EMPTY_ELEMENTDATA;
} else { //抛出一个异常
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}==========add("java01")======E理解为Object类型================
public boolean add(E e) {
ensureCapacityInternal(size + 1); // 扩容
elementData[size++] = e; //把元素赋值给数组的相应位置
return true;
}
==========indexOf("java02") 判断元素在集合中第一次的位置=============
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i])) //和数组中的每个元素内容进行比对
return i; //返回元素在集合中位置
}
return -1;
}===========size() 请求数组的长度======================
public int size() {
return size;
}============contain("java05")判断元素是否在集合中==============
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
===============get(1) 获取指定位置的元素========
public E get(int index) {
rangeCheck(index); //判断指定的位置是否合法return elementData(index);
}E elementData(int index) {
return (E) elementData[index];
}============toString() 为什么不打印对象的引用地址
[java01, java02, java03, java02]因为重写了Object里面的toString方法。
public String toString() {
Iterator<E> it = iterator();
if (! it.hasNext())
return "[]";StringBuilder sb = new StringBuilder();
sb.append('[');
for (;;) {
E e = it.next();
sb.append(e == this ? "(this Collection)" : e);
if (! it.hasNext())
return sb.append(']').toString();
sb.append(',').append(' ');
}
}
通过对ArrayList方法的底层代码分析:底层就是对数组的操作。
ArrayList的底层就是基于数组实现的。
1.4 LinkedList链表结构集合
LinkedList的使用放方式与ArrayList使用方式无差,但是源码运行原理不同。
1.4.1 添加
//创建链表集合对象
//链表集合数组不可固定数组长度,也可存储对象类型的元素
LinkedList linkedList=new LinkedList();
//添加数组元素
linkedList.add("我是1号");//添加到尾部
linkedList.addFirst("我是2号");//添加到头部
linkedList.addLast("我是3号");//添加到尾部
linkedList.addFirst("我是4号");//添加到头部
linkedList.addLast("我是5号");//添加到尾部
linkedList.add("我是6号");//默认添加到尾部
System.out.println(linkedList);
1.4.2 删除
//删除操作
//删除下标为1的元素
linkedList.remove(1);
System.out.println(linkedList);
//删除头部元素
linkedList.removeFirst();
System.out.println(linkedList);
//删除尾部元素
linkedList.removeLast();
System.out.println(linkedList);
1.4.3 修改
//修改操作
linkedList.set(1,"我是修改后的3号");
System.out.println(linkedList);
1.4.4 查询
//查询操作
//求集合长度
int size=linkedList.size();
System.out.println("数组长度为"+size);
//集合元素是否为空
boolean empty=linkedList.isEmpty();
System.out.println(empty);
//判断指定元素是否在集合中
boolean ys=linkedList.contains("我是修改后的3号");
System.out.println(ys);
//根据下标获取指定元素的位置
int pos=linkedList.indexOf("我是5号");
System.out.println(pos);
//根据下标获取指定元素
Object str=linkedList.get(2);
System.out.println(str);
//获取第一个元素
Object frist=linkedList.getFirst();
System.out.println(frist);
//获取最后一个元素
Object last=linkedList.getLast();
System.out.println(last);
小结:
1. 集合:理解为容器,它的长度可以扩展,而且它存储的元素都是对象类型。
2. List:它是一个接口,该集合中的元素可以重复,而且是有序的(有下标)。
3. ArrayList:底层是数据结构,它查询的效率高,但是删除和添加的效率低,因为涉及到数据的迁移。 常见方法: add(); size(); indexOf(); contains(); get(); remove(); isEmpty();
4. LinkedList:底层是双向链表结构,它增加和删除的效率高,但是查询效率低,它也有自己的方法:addFirst() addLast() getFirst() getLast()
2.LinkedList的底层源码
1.凡是查询源码 ,我们都是从类的构造方法入手:
/**
* Constructs an empty list.
*/
public LinkedList() {
}
该类的构造方法内是空的,没有任何的代码。 但是该类中有三个属性。
transient int size = 0; //索引
transient Node<E> first; //第一个元素对象
transient Node<E> last; //表示最后一个元素对象。
================ add的源码=====E:理解为Object类型==========================。
public boolean add(E e) {
linkLast(e);
return true;
}
void linkLast(E e) {
final Node<E> l = last;
//上一个节点 数据 下一个节点
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
==================Node的源码 内部类=======================================
private static class Node<E> { //<E>泛型--object
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;
}
}
1、==================== get(1)-----获取元素========================
public E get(int index) {
checkElementIndex(index); //检查index下标是否正确。
return node(index).item; //李四Node对象
}
========================node(index)=============================
Node<E> node(int index) {
//>> 位运算二进制运算 ----- size >> 1 一半的意思size/2
if (index < (size >> 1)) { //前半部分
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else { //后半部分
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
分析: LinkedList查询效率低。因为它要一个节点一个节点的往后找。
3.Set集合
3.1 HashSet集合
3.1.1 创建HashSet对象
//HashSet如果没有指定容器大小,那么默认为16 负载因子为0.75
//创建HashSet对象
HashSet hashSet=new HashSet();
HashSet hashSet1 = new HashSet(16);//初始容器的大小
//loadFactor:--->0.7f 表示负载因子 当空间使用70%时 要求扩容
HashSet hashSet2 = new HashSet(16,0.7f);
举例:
3.1.2 添加元素
//添加操作
//HashSet集合是无序的,且元素不能重复
//创建HaSet集合
HashSet hashSet=new HashSet();
hashSet.add("我是1号");
hashSet.add("我是2号");
hashSet.add("我是3号");
hashSet.add("我是4号");
hashSet.add("我是5号");
hashSet.add("我是5号");
HashSet hashSet2=new HashSet();
hashSet2.add("我是添加的元素1");
hashSet2.add("我是添加的元素2");
hashSet2.add("我是添加的元素3");
//把hashset2的每个元素添加到hashset中
hashSet.addAll(hashSet2);
//输出集合
System.out.println(hashSet);
3.1.3 删除
//删除操作(因为set集合是无序的,不能通过下标删除元素,只能删除元素对象)
hashSet.remove("我是添加的元素1");
//清除集合中的所有元素
//hashSet.clear();
System.out.println(hashSet);
3.1.4 查询
//查询操作(因为集合元素是无序的,所以不能通过元素查询元素下标
// 只能查询元素是否存在与集合之中并返回布尔值)
boolean a=hashSet.contains("我是2号");
System.out.println(a);
//判断集合是否为空
boolean b=hashSet.isEmpty();
System.out.println(b);
3.1.5 hashSet的遍历
1.通过for each遍历
//for each遍历集合元素
for (Object c:hashSet){
System.out.println(c);
}
2.通过迭代器遍历
//迭代器遍历集合元素
//获取迭代器对象 有序 有下标
Iterator iterator=hashSet.iterator();
//判断指针是否能够移动
while (iterator.hasNext()){
//移动指针并获取当前元素
Object d=iterator.next();
//输出当前元素
System.out.println(d);
}
3.1.6 hashSet的源码
从构造函数说起:
/**
* Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
* default initial capacity (16) and load factor (0.75).
*/
public HashSet() {
map = new HashMap<>();
}
在创建一个HashSet的对象时,底层创建的是HashMap。我们说hashset的底层原理时,我们就在后HashMap的原理就行。 讲HashMap时给大家说原理。
3.2 TreeSet集合
TreeSet中的方法和HashSet中的方法一模一样 只是他们的实现不一样。
TreeSet 基于TreeMap 实现。TreeSet可以实现有序集合,但是有序性需要通过比较器(compare)实现。
例:储存String类型
public class Test3 {
public static void main(String[] args) {
//创建TreeSet对象
//TreeSet集合可以实现有序集合,但是有序性需要通过compare方法(比较器)实现
TreeSet treeSet=new TreeSet();
treeSet.add("我是1号");
treeSet.add("我是5号");
treeSet.add("我是6号");
treeSet.add("我是4号");
treeSet.add("我是2号");
treeSet.add("我是3号");
System.out.println(treeSet);
}
}
储存对象类型:
public class Test4 {
public static void main(String[] args) {
//创建TreeSet对象(不允许有重复元素)
TreeSet treeSet =new TreeSet();
treeSet.add(new Student("小新",5));
treeSet.add(new Student("妮妮",4));
treeSet.add(new Student("正南",6));
treeSet.add(new Student("阿呆",3));
treeSet.add(new Student("风间",5));
//输出集合
System.out.println(treeSet);
}
}
通过运行我们发现出现如下的错误:
发现: TreeSet中的元素必须实现Comparable接口 方可放入TreeSet
解决方法有两个:
第一个:让你的类是实现Comparable接口
package com.new151.Test4_16;
import java.util.TreeSet;
/**
* @Description:
* @Author:LouFangRui
* @Date:2022/4/17
*/
public class Test4 {
public static void main(String[] args) {
//创建TreeSet对象(不允许有重复元素)
TreeSet treeSet =new TreeSet();
treeSet.add(new Student("小新",5));
treeSet.add(new Student("妮妮",4));
treeSet.add(new Student("正南",6));
treeSet.add(new Student("阿呆",3));
treeSet.add(new Student("风间",5));
//输出集合
System.out.println(treeSet);
}
}
class Student implements Comparable{
private String name;
private int age;
//构造函数(无参)
public Student() {
}
//构造函数(有参)
public Student(String name, int age) {
this.name = name;
this.age = age;
}
//setter、getter方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
//重写toString方法
@Override
public String toString() {
return "student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
//排序:返回值如果大于0,则表示当前元素比o(最开始比较的元素)大
// 如果返回值为-1,则表示当前元素比o小
// 如果返回值为0,则表示为两个元素相同
@Override
public int compareTo(Object o){
Student student1=(Student) o;
System.out.println("================================"+o);
if (this.age>student1.age){
return 1;
}if (this.age<student1.age){
return -1;
}
return 0;
}
}
第二种:在创建TreeSet时指定排序的规则
TreeSet treeSet=new TreeSet(); 但是在创建对象时 并没有为其指定排序得规则,那么就要求该集合得元素有排序规则。 如果元素得类已经创建完成,可以增加类,但是尽量避免修改该类得源码,这时我们又想把该类得对象放入得TreeSet容器中。 这时就需要你在创建TreeSet时指定排序得规则。
//实现排序接口
public class MyComparator implements Comparator{
//重写Comparator方法
@Override
public int compare(Object o1, Object o2) {
Students s1=(Students)o1;
Students s2=(Students)o2;
System.out.println(s1+"====="+s2);
if (s1.getAge()>s2.getAge()){
return 1;
}else if (s1.getAge()<s2.getAge()){
return -1;
}else {
return 0;
}
}
}
自定义排序方法使用:
public class Test1 {
public static void main(String[] args) {
//为TerrSet容器制定排序方法
TreeSet treeSet=new TreeSet(new MyComparator());
treeSet.add(new Students("海绵宝宝",5));
treeSet.add(new Students("章鱼哥",1));
treeSet.add(new Students("蟹老板",2));
treeSet.add(new Students("派大星",3));
treeSet.add(new Students("珊迪",4 ));
System.out.println(treeSet);
}
}
//创建学生类
class Students{
//属性私有化
private String name;
private int age;
//无参构造
public Students() {
}
//有参构造
public Students(String name, int age) {
this.name = name;
this.age = age;
}
//setter、getter方法
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;
}
//重写toString方法
@Override
public String toString() {
return "Students{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
小结:
1.Lis:
LinkedList:底层基于双向链表,增加和删除的效率比较高(只需改变链表指向),但是它的查询效率较低(因为每次查询数据需要从头到尾逐个查询)。
2.Set:
HaSet:底层依据与HashMap
TreeSet: 基于二叉树,它是一个有序的集合,要求里面的元素必须实现Comparable接口,按照接口的方法进行排序以及去重。
4.Map键值对模式
map中的每个元素都属于键值对模式,如果map中添加元素,需要添加key和value,map也属于一个接口,该接口常见的实现类有:HashMap。
4.1 创建Map对象
//创建map对象
//集合默认长度为16,负载因子为0.75
//初始化大小 负载因子
//Map map1=new HashMap(16,0.7f);
Map map = new HashMap();
4.2 添加操作
//添加操作(无序添加)
//map的Key是唯一的,如果Key相同,则后者覆盖前者的value
//map.put(添加)("key:name","value:野原新之助");
map.put("name","野原新之助");
map.put("age",5);
map.put("name2","樱田妮妮");
map.put("age2",6);
System.out.println(map);
Map map2=new HashMap();
map2.put("k1","a");
map2.put("k2","b");
map2.put("k3","c");
map2.put("k4","d");
map2.put("k5","e");
System.out.println(map2);
//把map2的全部元素添加到map中
map.putAll(map2);
//如果指定的key存在,则不放入map中,如果不存在则放入
map.putIfAbsent("age",6);
System.out.println(map);
4.3 删除操作
//删除操作
//因为是无序集合,所以只能通过指定的key删除元素
map.remove("k1");
System.out.println(map);
//清空集合全部元素
/*map2.clear();
System.out.println(map2);*/
4.4 修改操作
//修改操作
//根据指定的Key修改元素
map.replace("name2","风间徹");
System.out.println(map);
4.5 查询操作
//查询操作
//根据指定的Key查询元素的value
Object a=map.get("name");
System.out.println(a);
//判断map中是否存在指定的key(布尔值)
boolean b=map.containsKey("name");
System.out.println(b);
//判断map中是否存在指定的value(布尔值)
boolean c=map.containsValue("风间徹");
System.out.println(c);
//返回map中所有的key
Set set=map.keySet();
System.out.println(set);
//返回map指定key的value
Object d=map.get("name2");
System.out.println(d);
//for each 遍历输出集合所有的value
for (Object e:set){
Object f=map.get(e);
System.out.println("Key:"+e+"<=======>value:"+f);
}
4.6 HashMap的底层原理
jdk1.7 和 jdk1.8他们是有区别的。
jdk1.7使用得数据结构: 数组+链表 而且链表插入模式为头部插入(造成死循环)。
jdk1.8使用得数据结构: 数组+链表+红黑树 而且链表得插入模式为尾部插入。从构造函数入口:
/**
* Constructs an empty <tt>HashMap</tt> with the default initial capacity
* (16) and the default load factor (0.75).
*/
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
小结:
JDK1.8 HashMap原理
Hashmap得原理,存储元素使用得put(key,value),根据key得hash计算出相应得哈希值,根据相应得算法求出该元素在数组中得位置, 如果求出得哈希值相同,则称为哈希冲突,会根据equals来判断元素是否一致,如果equals不同,则存入单向链表上, 如果哈希碰撞得个数超过8个,则把链表转换为红黑二叉树。JDK1.7和JDK1.8 HashMap得区别。
JDK1.7使用得数据结构: 数组+链表 而且链表插入模式为头部插入(造成死循环)。
JDK1.8使用得数据结构: 数组+链表+红黑树 而且链表得插入模式为尾部插入。
String哈希操作(hashCode)
public class Test2 {
public static void main(String[] args) {
String a="k1";
System.out.println(a.hashCode());
String b="k2";
System.out.println(b.hashCode());
System.out.println(b);
}
}
底层代码:
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)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
//如果key得hash值相同,判断key得equals是否相同,替换原来得元素
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
// 判断链表得长度是否超过8个
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
// 把链表转换为红黑树结构
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}