一、集合框架
1.1 目录
1. 为什么使用集合?
2. 集合架构有哪些?
3. List集合
4. ArrayList集合
5. LinkedList集合。
6. Set集合
7. HashSet集合
8. TreeSet集合。
9. Map
10.HashMap集合。
11.TreeMap集合。
1.2 为什么使用集合?
1. 数组.
思考: 数组有缺陷?--定容【定数组定义好,他们的长度就无法改变.】如果需要改变数组的长度,代码就变得很复杂。
2.我们是否可以定义一个长度可改变的容器。---当然可以。
3.手写可变长度的容器。
//自定义可变长度的容器类。
public class MyArray {
private Object [] arr; //声明一个Object类型的数组
private int size;//表示数组的下标 因为他们是类成员变量 在创建对象时都有默认值。
public MyArray(){ //无参构造函数
this(3); //本类中其他的构造函数 this本类的对象。 如果在构造方法中this()表示调用本类的其他构造函数
}
public MyArray(int initSize){ //有参构造函数--表示数组的长度
if(initSize<0){ //长度不合法
throw new RuntimeException("sorry 数组的长度有误。");
}
arr=new Object[initSize];
}
//把元素o放入数组arr
public void addData(Object o){
//判断你的数组是否已满
if(size>=arr.length){
//扩容--(1)容器的长度变长 (2)把原来容器中的元素复制到新的容器中
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;
}
}
我们自己可以手写一个可变的容器,那么别人也可以手写可变的容器。
java官网 基于数组 根据不同的数据结构 创建了多个类 而这些类统称 为集合框架。
以后 我们在说集合框架时 就表示多个类。
1.3 集合的架构
1.4 List集合-ArrayList
1.4.0 创建集合对象
List list = new ArrayList(); //创建一个集合对象 如果没有指定集合容器的长度默认为10
List list1 = new ArrayList(15);
1.4.1 添加的操作
//添加 (1)可以添加任意类型
list.add("java01");
list.add("java02");
list.add(15.5);
list.add(18);
list.add(true);
list.add(new Date());
System.out.println(list);
list.add(2,"hello"); //下标为2的位置添加元素 并把后面的元素进行唯一
System.out.println(list); //打印一个对象时默认调用的为toString()
List list2=new ArrayList();
list2.add("a");
list2.add("b");
list.addAll(list2);//添加多个元素 把list2中的每个元素一一添加到list中
System.out.println(list);
1.4.2 删除的操作
//删除操作
list.remove(2);//移除下标为2的元素
System.out.println(list);
list.clear();//清空集合中的元素.
System.out.println(list);
1.4.3 修改的操作
//修改操作
list.set(1,"刘德华");
System.out.println(list);
1.4.4 查询操作
List list = new ArrayList();
list.add("java01");
list.add("java02");
list.add("java03");
list.add("java02");
//查询的方法
Object o = list.get(1);//根据下标获取元素
System.out.println(o);
int size = list.size();//获取集合中元素的个数。
System.out.println(size);
boolean f = list.contains("java05");//判断元素是否在集合中
System.out.println(f);
int index = list.indexOf("java05");//查询元素在集合中第一次出现的位置
System.out.println(index);
//遍历集合中的元素 for循环
for(int i=0;i<list.size();i++){
Object o1 = list.get(i);
System.out.println(o1);
}
1.4.5 ArrayList底层源码
java
从构造方法来入手。new ArrayList(22) 底层声明了一个Object类型的数组 名字elementData
Object[] elementData
public 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.5 LinkedList
它是一个链表结构。
1.5.1 添加
//添加
linkedList.add("java01"); //追加尾部
linkedList.addFirst("java02"); //添加到头部
linkedList.addLast("java03");//追加到尾部
linkedList.addFirst("java04"); //追加到头部
linkedList.addLast("java05");//追加到尾部
System.out.println(linkedList);
1.5.2 删除操作
//删除操作
linkedList.removeFirst();//移除头部元素
System.out.println(linkedList);
linkedList.remove(2);//移除指定位置的元素
System.out.println(linkedList);
linkedList.removeLast();//移除尾部的元素
System.out.println(linkedList);
1.5.3 修改操作
//修改操作
linkedList.set(1,"java11");
System.out.println(linkedList);
1.5.4 查询操作
//查询操作
int size = linkedList.size();//求长度
boolean empty = linkedList.isEmpty();//是否为空
boolean b = linkedList.contains("java01");//判断元素是否在集合中
Object o = linkedList.get(1);//根据下标获取指定位置的元素
Object first = linkedList.getFirst();//获取第一个元素
System.out.println(first);
Object last = linkedList.getLast();
System.out.println(last);
1.回顾
1. 集合: 理解为容器--它的长度可以变大的,而且它存储的都是对象类型。
2. List:----->它是一个接口,该集合中的元素可以重复,而且是有序的(有下标)。
ArrayList--->它的底层是数组结构,它的查询效率高,但是它的添加和删除效率低--因为它要牵涉到数据的迁移。
常见方法: add();size();indexOf(); contains(); get();remove();isEmpty();
LinkedList--->底层双向链表结构,它增加和删除效率高,但是它的查询效率低。
常见的方法: 它的方法和ArrayList中的方法比较相似,它也有自己特有的方法:
addFirst() addLast() getFirst() getLast()
2. 正文
1. LinkedList的底层源码。 2. Set集合 3. HashSet 4. TreeSet 5. Map集合 6. HashMap 6. TreeMap
3.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查询效率低。因为它要一个节点一个节点的往后找。
4.Set集合
4.1 HashSet集合
4.1.1 创建HashSet对象。
public class Test02 { public static void main(String[] args) { HashSet hashSet= new HashSet(); HashSet hashSet1 = new HashSet(16);//初始容器的大小 //loadFactor:--->0.7f 表示负载因子 当空间使用70%时 要求扩容 HashSet hashSet2 = new HashSet(16,0.7f); } }
4.1.2 添加元素
//添加操作 hashSet.add("java01"); hashSet.add("java02"); hashSet.add("java04"); hashSet.add("java03"); hashSet.add("java02"); HashSet set2=new HashSet(); set2.add("刘德华"); set2.add("张学友"); set2.add("黎明"); hashSet.addAll(set2); //把set2中的每个元素添加到hashset中 System.out.println(hashSet); //元素不能重复 而且无序
4.1.3 删除
//删除 hashSet.remove("黎明"); // hashSet.clear();//清空容器集合 System.out.println(hashSet);
4.1.4 修改
//修改操作 boolean empty = hashSet.isEmpty(); //判断是否为空 System.out.println(empty); boolean b = hashSet.contains("刘德华");//判断元素是否在容器中 System.out.println(b);
4.1.5 hashSet的遍历
(1)通过foreach遍历
//遍历--- foreach for(Object o: hashSet){ System.out.println(o); }(2)通过迭代器来遍历
//迭代器遍历 Iterator iterator = hashSet.iterator();//获取迭代器对象 有序:有下标 while (iterator.hasNext()){//判断是否指定能够移动 Object next = iterator.next();//指定移动并获取当前的元素 System.out.println(next); }
4.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时给大家说原理。
4.2 TreeSet集合。
TreeSet中的方法和HashSet中的方法一模一样 只是他们的实现不一样。 TreeSet 基于TreeMap 实现。TreeSet可以实现有序集合,但是有序性需要通过比较器实现。
例子: 存储String类型。
TreeSet treeSet=new TreeSet(); treeSet.add("java05"); treeSet.add("java03"); treeSet.add("java04"); treeSet.add("java01"); treeSet.add("java02"); treeSet.add("java04"); System.out.println(treeSet);
存储一个对象类型:
public class Test04 { public static void main(String[] args) { TreeSet treeSet=new TreeSet(); treeSet.add(new Student("王俊凯",17)); treeSet.add(new Student("赵晓普",16)); treeSet.add(new Student("赵俊涛",16)); treeSet.add(new Student("闫克起",15)); System.out.println(treeSet); } }
通过运行我们发现出现如下的错误:
发现: TreeSet中的元素必须实现Comparable接口 方可放入TreeSet
解决办法有两个:
第一个: 让你的类实现Comparable接口
package com.ykq; import java.util.TreeSet; public class Test04 { public static void main(String[] args) { TreeSet treeSet=new TreeSet(); //TreeSet不允许重复元素 treeSet.add(new Student("王俊凯",17)); treeSet.add(new Student("赵晓普",16)); treeSet.add(new Student("赵俊涛",16)); treeSet.add(new Student("闫克起",15)); System.out.println(treeSet); } } class Student implements Comparable{ private String name; private Integer age; @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", age=" + age + '}'; } public Student(String name, Integer age) { this.name = name; this.age = age; } public Student() { } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } //排序:---返回如果大于0 表示当前元素比o大 如果返回-1 当前添加的元素比o小 返回0表示相同元素。 @Override public int compareTo(Object o) { Student student= (Student) o; System.out.println(this+"===================>"+o); if(this.age>student.age){ return 1; } if(this.age<student.age){ return -1; } return 0; } }
第二种: 在创建TreeSet时指定排序的对象。
我们之前 创建过TreeSet对象。 TreeSet treeSet=new TreeSet(); 但是在创建对象时 并没有为其指定排序得规则,那么就要求该集合得元素有排序规则。 如果元素得类已经创建完成,不能修改该类得源码,这时我们又想把该类得对象放入得TreeSet容器中。 这时就需要你在创建TreeSet时指定排序得规则。 public class MyComparator implements Comparator { //需要比对得两个对象 @Override public int compare(Object o1, Object o2) { Student s1= (Student) o1; Student s2= (Student) o2; if(s1.getAge()>s2.getAge()){ return 1; }else if(s1.getAge()<s2.getAge()){ return -1; }else { return 0; } } } public class Demo01 { public static void main(String[] args) { //Comparator<? super E> comparator //为TreeSet容器指定了排序规则 TreeSet treeSet=new TreeSet(new MyComparator()); treeSet.add(new Student(18,"王亚波")); treeSet.add(new Student(17,"潘世浩")); treeSet.add(new Student(19,"刘晓东")); treeSet.add(new Student(19,"娄明阳")); System.out.println(treeSet); } }
1. 回顾
1. 上节课-----集合 2. list---> LinkedList: 底层基于双向链表,增加和删除效率比较高(只需要改变链表得指向),它得查询效率慢(每次查询数据 从头查询或者从尾查询)。 3. Set----> 无序,不可重复。 HashSet---->底层依据于HashMap。 TreeSet---->基于二叉树,它是一个有序得集合,要求里面得元素必须实现Comparable接口。 按照该接口得方法进行排序以及去重。
2. 正文
1. Map 属于key---value键值对元素。 HashMap 2. 泛型 3. IO File对象。
3. Map 属于键值对模式
map中得每个元素属于键值对模式。 如果往map中添加元素时 需要添加key 和 value. 它也属于一个接口,该接口常见得实现类有: HashMap.
3.1 如何创建Map对象
//默认初始化大小为16 负载因子为0.75 Map map=new HashMap(); //初始化大小 Map map2=new HashMap(16); //初始化大小 负载因子 Map map3=new HashMap(16,0.78f);
3.2 添加操作
//默认初始化大小为16 负载因子为0.75 Map map=new HashMap(); //添加操作 key: name value: 高景霞 map.put("name","高景霞"); //注意: 要求map得key必须唯一。 map.put("age",18); map.put("name","李赛"); //因为key不能重复,所以后者会把前者覆盖 Map m1=new HashMap(); m1.put("k1","v1"); m1.put("k2","v2"); map.putAll(m1); //把m1中得每个元素 添加到map中 map.putIfAbsent("age",28) ;//如果指定得key存在,则不放入map中,如果不存在则放入map中 System.out.println(map);
3.3 删除操作
//删除操作 map.remove("age2");//根据指定得key移除元素 System.out.println(map); map.clear(); //清空map容器 System.out.println(map);
3.4 修改操作
//修改操作 map.replace("name","刘德华");//替换元素 System.out.println(map);
3.5 查询
public static void main(String[] args) { Map map=new HashMap(); map.put("k1","v1"); map.put("k4","v4"); map.put("k2","v2"); map.put("k3","v3"); //查询操作 boolean f = map.containsKey("k5");//判断map是否存在指定得key Object v = map.get("k5"); //根据指定的key获取对应得value值 System.out.println(v); Set keys = map.keySet();//返回该map中所有得key System.out.println(keys); //遍历map. for(Object k:keys){ Object value= map.get(k);// System.out.println(k+"================>"+value); } }
3.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使用得数据结构: 数组+链表+红黑树 而且链表得插入模式为尾部插入。
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;
}