目录
分析: LinkedList查询效率低。因为它要一个节点一个节点的往后找。
为什么要用集合
1.相对于数组,数组有缺陷》》》定容【数组一旦定义好,他们的长度就无法改变】,如果改变数组的长度就会很复杂。
2.可以定义一个长度可以改变的容器
手写可变长度的容器
public class MyArray {
public Object [] arr;
public int size;//数组下标
public MyArray(){
}
public MyArray(int intSize){
if (intSize<0){
throw new ArrayIndexOutOfBoundsException("长度不合法");
}
arr = new Object[intSize];
}
public void addDate(Object a){
//扩容
if (size>= arr.length){
Object[] newArr = Arrays.copyOf(arr, size * 2);
arr=newArr;
}
//添加数组元素
arr[size]=a;
size++;
}
public Object getDate(int index){
if (index<0){
throw new ArrayIndexOutOfBoundsException("下标越界");
}
Object a = arr[index];
return a;
}
}
===========================================================
public class TestArray {
public static void main(String[] args) {
MyArray my = new MyArray(5);
my.addDate("java01");
my.addDate("java02");
my.addDate("java03");
Object date = my.getDate(0);
System.out.println(date);
for (int i = 0; i <my.size; i++) {
Object o = my.getDate(i);
System.out.println(o);
}
}
}
java官网 基于数组 根据不同的数据结构 创建多个类 而这些类统称为 集合框架
以后我们在说集合框架时,就表示多个类
集合架构
List集合
ArrayList
创建集合对象
//创建集合对象
List list = new ArrayList();//默认集合长度为10
//初始化集合空间 长度为 2
List list1 = new ArrayList(2);
集合添加操作
//增加操作 集合可增加任意类型
list.add(100);
list.add(1.52525);
list.add("张三");
list.add(true);
list.add(new Date());
//在下标为 2 的位置添加元素,并把后面元素进行移位
list.add(2,"李四");
System.out.println(list);
//添加多个元素 将list1的元素添加到list中
list1.add("a");
list1.add("b");
list.addAll(list1);
System.out.println(list);
集合删除操作
//删除操作
list.remove(2);
System.out.println(list);
//清空集合元素
list.clear();
System.out.println(list);
集合修改操作
//修改操作
list.set(1,"赵六");
System.out.println(list);
集合查询操作
//查询操作
Object o = list.get(2);//根据下标获取元素
System.out.println(o);
//获取元素中的个数
int size = list.size();
System.out.println(size);
//判断元素是否在集合中
boolean zs = list.contains("张三");
System.out.println(zs);//返回true/false
//查询元素在集合中第一次出现的位置
int ls = list.indexOf("李四");
System.out.println(ls);//返回元素下标 没有返回-1
//遍历集合中的元素 for循环方式
for (int i = 0; i < list.size(); i++) {
Object o1 = list.get(i);
System.out.println(o1);
}
System.out.println("================");
//遍历集合中的元素 foreach循环方式
for (Object o2 : list
) {
System.out.println(o2);
}
ArrayList底层源码
从构造方法来入手。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的底层就是基于数组实现的。
LinkedList
它是一个链表结构。
LinkedList添加操作
//添加
linkedList.add("java01"); //追加尾部
linkedList.addFirst("java02"); //添加到头部
linkedList.addLast("java03");//追加到尾部
linkedList.addFirst("java04"); //追加到头部
linkedList.addLast("java05");//追加到尾部
System.out.println(linkedList);
LinkedList删除操作
//删除操作
linkedList.removeFirst();//移除头部元素
System.out.println(linkedList);
linkedList.remove(2);//移除指定位置的元素
System.out.println(linkedList);
linkedList.removeLast();//移除尾部的元素
System.out.println(linkedList);
LinkedList修改操作
//修改操作
linkedList.set(1,"java11");
System.out.println(linkedList);
LinkedList查询操作
//查询操作
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);
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查询效率低。因为它要一个节点一个节点的往后找。
Set集合
HashSet集合
创建HashSet集合对象
//创建HashSet对象 默认长度为16 0.7f 表示负载因子:负载因子默认0.75f
HashSet hashSet1 = new HashSet(10,0.7f);
HashSet hashSet2= new HashSet();
HashSet hashSet3 = new HashSet(16);//初始容器的大小
HashSet添加元素
//添加操作
hashSet.add("java01");
hashSet.add("java02");
hashSet.add("java04");
hashSet.add("java03");
hashSet.add("java02");
hashSet.add("刘德华");
System.out.println(hashSet);//元素无序 不重复
HashSet hashSet1 = new HashSet();
hashSet1.add("刘德华");
//判断是否能添加
boolean b = hashSet.addAll(hashSet1);
boolean b1 = hashSet1.addAll(hashSet);
System.out.println(b);
System.out.println(b1);
//将hashSet元素添加到hashSet1中
hashSet1.addAll(hashSet);
System.out.println(hashSet1);
HashSet删除操作
//删除操作
hashSet.remove("java04");
// hashSet.clear();//清空操作
System.out.println(hashSet);
HashSet查询操作
HashSet没有修改操作
//查询操作
boolean empty = hashSet.isEmpty();//判断是否为空
System.out.println(empty) ;
boolean l = hashSet.contains("刘德华"); //判断元素是否在集合中 返回true/false
System.out.println(l);
//遍历---foreach/迭代器
// for (Object o:hashSet
// ) {
// System.out.println(o);
// }
//迭代器
Iterator iterator = hashSet.iterator();//获取迭代器对象
while (iterator.hasNext()){//判断迭代器指针能够移动
Object next = iterator.next();//指针移动并获取当前元素
System.out.println(next);
}
迭代
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
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 Demo04 {
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
String类型是已经用了Comparable接口
解决办法有两个:
第一个: 让你的类实现Comparable接口
public class Demo04 {
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 treeSet=new TreeSet();
在创建对象时 并没有为其指定排序得规则,那么就要求该集合得元素有排序规则。 如果元素得类已经创建完成,不能修改该类得源码,这时我们又想把该类得对象放入得TreeSet容器中。 这时就需要你在创建TreeSet时指定排序得规则。
public class Demo01 {
//避免修改程序 可以增加类
public static void main(String[] args) {
TreeSet treeSet = new TreeSet(new MyComparator());//为TreeSet提供指定排序规则
treeSet.add(new Student("张三",12));
treeSet.add(new Student("李四",12));
treeSet.add(new Student("王五",13));
treeSet.add(new Student("赵六",14));
System.out.println(treeSet);
}
}
class Student{
private String name;
private int age;
public Student() {
}
public Student(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 "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
===================================================
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;
}
}
}
Map
属于键值对模式
map中得每个元素属于键值对模式。
如果往map中添加元素时 需要添加key 和 value. 属于一个接口
该接口常见得实现类有: HashMap.
创建Map对象
public class Demo02 {
public static void main(String[] args) {
//默认空间16 负载因子0.75f
Map map = new HashMap();
//初始大小
Map map1 = new HashMap(18);
// 初始大小 初始负载因子
Map map2 = new HashMap(12,0.3f);
}
添加操作
//添加
map.put("k1","v1");
map.put("k4","v2");
map.put("k3","v3");
map.put("k4","v4"); //key 不能重复 后者会覆盖前者 value值相同不影响
map1.put("f1","v1");
map.putAll(map1);
map.putIfAbsent("k5","12");//如果key存在则不放入 key不存在则放入
System.out.println(map);
删除操作
//删除操作
map.remove("k4");//删除key
System.out.println(map);
//map.clear();//清空操作
修改操作
//修改操作
map.replace("k1","123");
System.out.println(map);
查询操作
//查询操作
boolean k1 = map.containsKey("k1");//根据key查询是否存在
System.out.println(k1);
boolean b = map.containsValue("324");//根据value查询是否存在
System.out.println(b);
Object k11 = map.get("k1");//根据key获取对应的值
System.out.println(k11);
Set set = map.keySet();
System.out.println(set);//获取所有的key
//遍历
for (Object k:set
) {
Object o = map.get(k);
System.out.println(k+" "+o);
}
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个,则把链表转换为红黑二叉树。
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;
}