集合框架的使用

目录

1、目录

2、为什么使用集合

3、集合的架构

4、List集合

4.1 List集合-ArrayList

4.1.1 创建集合对象

4.1.2 添加的操作

4.1.3 删除的操作

4.1.4 修改的操作

4.1.5 查询操作

4.1.6 ArrayList底层源码

4.2 List集合-LinkedList

4.2.1 添加操作

4.2.2 删除操作

4.2.3 修改操作

4.2.4 查询操作

4.2.5LinkedList的底层源码

5、Set集合

5.1 HashSet集合

5.1.1特点

5.1.2 创建HashSet对象

​5.1.3 添加元素

5.1.4 删除元素

5.1.5 修改

5.1.6 hashSet的遍历

5.1.7 hashSet的源码

5.2 TreeSet集合

5.2.1 TreeSet的特点

5.2.2 TreeSet对元素进行排序的方式

5.2.3 例子

6. Map接口及其实现类

6.1 Map接口特点

6.2 创建Map对象

6.3 添加

6.4 删除

6.5 修改

6.6 查询

6.7 遍历

6.8 HashMap得底层原理

6.8.1JDK1.7 和 JDK1.8的区别

6.8.2总结

JDK1.8 HashMap原理


1、目录

1. 为什么使用集合

2. 集合架构有哪些

3. 集合的架构

4. List集合

  • ArrayList集合
  •  LinkedList集合

5. Set集合 

  • HashSet
  • TreeSet

6. Map集合

  • HashMap
  • TreeMap

2、为什么使用集合

2.1 我们原来学习过数组,但是数组有缺点(一旦声明之后,长度就不可变了)同时,声明数组时的数据类型也决定了该数组存储的数据的类型;而且,数组存储的数据是有序的、可重复的,特点单一。 但是集合提高了数据存储的灵活性,Java 集合不仅可以用来存储不同类型不同数量的对象,还可以保存具有映射关系的数据。

2.2 我们是否可以定义一个长度改变的容器。---当然可以。

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("数组长度有误请重新定义数组长度");
        }
        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官网 基于数组 根据不同的数据结构 创建了多个类 而这些类统称 为集合框架

以后 我们在说集合框架时 就表示多个类。

3、集合的架构

4、List集合

ArrayList  底层体现出来的数据结构是 数组
Linkedlist 底层体现出来的数据结构是 链表

List特点:

有序集合(也称为序列 )。 该界面的用户可以精确控制列表中每个元素的插入位置。 用户可以通过整数索引(列表中的位置)访问元素,并搜索列表中的元素。

可以简单的理解成和数组的使用方式差不多 ,  存储到List中的数据有顺序的 并且可以通过索引编号对其进行操作

public interface List<E> extends Collection<E> 
public interface Collection<E> extends Iterable<E>

List接口中声明了一些 常用的增删改查的方法 

4.1 List集合-ArrayList

4.1.1 创建集合对象

List list = new ArrayList(); //创建一个集合对象 如果没有指定集合容器的长度默认为10

List list1 = new ArrayList(15); //创建一个长度15的集合对象

4.1.2 添加的操作

//添加 (可以添加任意类型)
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);

4.1.3 删除的操作

//删除操作
list.remove(2);//删除下标为2的元素
System.out.println(list);
list.clear();//清空集合中的元素
System.out.println(list);

4.1.4 修改的操作

//修改操作
list.set(1,"张大三");//下标为1的内容修改为张大三
System.out.println(list);

4.1.5 查询操作

//查询
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);

//遍历集合中的元素 
//(1)for循环
for(int i=0;i<list.size();i++){
    Object o1 = list.get(i);
    System.out.println(o1);
}
//(2)foreach循环
for (Object o3:l){
     System.out.println(o3);
}

4.1.6 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的底层就是基于数组实现的。

4.2 List集合-LinkedList

它是一个链表结构。具有List的特征,底层以链表结构实现,可以进行头尾元素的添加删除。

4.2.1 添加操作

//添加
linkedList.add("java01"); //添加元素
linkedList.addFirst("java02"); //添加到头部
linkedList.addLast("java03");//追加到尾部
linkedList.addFirst("java04"); //追加到头部
linkedList.addLast("java05");//追加到尾部
System.out.println(linkedList);

4.2.2 删除操作

//删除操作
linkedList.removeFirst();//移除头部元素
System.out.println(linkedList);

linkedList.remove(2);//移除指定位置的元素
System.out.println(linkedList);

linkedList.removeLast();//移除尾部的元素
System.out.println(linkedList);

4.2.3 修改操作

//修改操作
linkedList.set(1,"java11");//根据下标修改指定位置元素
System.out.println(linkedList);

4.2.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);

4.2.5 LinkedList的底层源码

==================== 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查询效率低。因为它要一个节点一个节点的往后找。

5、Set集合

5.1 HashSet集合

5.1.1特点:

  • HashSet是Set接口的实现类 
  • 线程不安全

5.1.2 创建HashSet对象

HashSet  hashSet= new HashSet();//默认大小
HashSet  hashSet1 = new HashSet(16);//初始化容器的大小
//loadFactor:--->0.7f 表示负载因子 当空间使用70%时 要求扩容
HashSet hashSet2 = new HashSet(16,0.7f);

 5.1.3 添加元素

//添加(元素不能重复 而且无序)
hashSet.add("java01");
hashSet.add("java02");
hashSet.add("java04");
hashSet.add("java03");
hashSet.add("java02");

//批量添加
HashSet set2=new HashSet();
set2.add("白敬亭");
set2.add("汪苏泷");    
hashSet.addAll(set2); //把set2中的每个元素添加到hashset中
System.out.println(hashSet);

 5.1.4 删除元素

//删除
hashSet.remove("白敬亭");//删除指定元素
System.out.println(hashSet);

hashSet.clear();//清空集合容器
System.out.println(hashSet);

5.1.5 修改

//查询
boolean empty = hashSet.isEmpty(); //判断是否为空
System.out.println(empty);

boolean b = hashSet.contains("白敬亭");//判断元素是否在容器中
System.out.println(b);

5.1.6 hashSet的遍历

(1)通过foreach遍历

//foreach遍历
for(Object o: hashSet){
    System.out.println(o);
}

(2)通过迭代器来遍历

迭代器(iterator)有时又称游标(cursor)是程序设计的软件设计模式,可在容器对象(container,例如链表或数组)上遍访的接口,设计人员无需关心容器对象的内存分配的实现细节。

HashSet类中没有提供根据集合索引获取索引对应的值的⽅法,

因此遍历HashSet时需要使⽤Iterator迭代器。Iterator的主要⽅法如下

返回类型

方法

描述

boolean

hasNext()

如果有元素可迭代

Object

next()

返回迭代的下⼀个元素

 //迭代器遍历
Iterator iterator = hashSet.iterator();//获取迭代器对象
while (iterator.hasNext()){//判断是否指针能够移动
Object next = iterator.next();//指针移动并获取当前的元素
System.out.println(next);
}

5.1.7 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。

5.2 TreeSet集合

5.2.1 TreeSet的特点

TreeSet中的方法和HashSet中的方法一模一样 只是他们的实现不一样。
TreeSet 基于TreeMap 实现。TreeSet可以实现有序集合,但是有序性需要通过比较器实现。

有序

不重复

添加、删除、判断元素存在性效率比较高

线程不安全

5.2.2 TreeSet对元素进行排序的方式:


1) 如果是基本数据类型和String类型,无需其它操作,可以直接进行排序。

2) 对象类型元素排序,需要实现Comparable接口,并覆盖其compareTo方法。

3) 自己定义实现了Comparator接口的排序类,并将其传给TreeSet,实现自定义的排序规则。

5.2.3 例子

存储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 Test {
    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接口

public class Test {
    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=new TreeSet(); 但是在创建对象时 并没有为其指定排序得规则,那么就要求该集合得元素有排序规则。 如果元素得类已经创建完成,不能修改该类得源码,这时我们又想把该类得对象放入得TreeSet容器中。 这时就需要你在创建TreeSet时指定排序得规则。 


public class Test {
    public static void main(String[] args) {
        TreeSet treeSet = new TreeSet(new MyComparator());//为TreeSet指定了排序规则
        treeSet.add(new Student("张三",18));
        treeSet.add(new Student("李四",17));
        treeSet.add(new Student("王五",20));
        treeSet.add(new Student("赵六",19));
        System.out.println(treeSet);
    }
}
 
 
 
public class MyComparator implements Comparator {//需要重写接口中的抽象方法
    @Override
    public int compare(Object o1, Object o2) {
        System.out.println("=====调用了compare方法=====");
        //先判断两个要比较的数据是否是相同类型的
        if ((o1 instanceof Student) && (o2 instanceof Student)) ;
        {
            Student s1 = (Student) o1;
            Student s2 = (Student) o2;
            System.out.println(o1 + "======" + o2);
            if (s1.getAge() > s2.getAge()) {
                return 1;
            } else if (s1.getAge() < s2.getAge()) {
                return -1;
            } else {
                return 0;
            }
        }
    }
}

总结:

 1.保证数据逻辑有序 2.不重复

 实现有序的两种方式

 a. 自定义类型实现Comparable接口,实现里面的compareTo方法

 b. 自定义一个比较器对象实现Comparator接口,实现里面的commpare方法

6. Map接口及其实现类

6.1 Map接口特点:

以键值对方式存储数据(Collection是单值集合)
键不能重复,键重复时,后面的数据会覆盖前面的数据
可以存储null
键值对数据无序
map中得每个元素属于键值对模式。 如果往map中添加元素时 需要添加key 和 value. 它也属于一个接口,该接口常见得实现类有: HashMap.

6.2 创建Map对象

//默认初始化大小为16 负载因子为0.75
Map map=new HashMap();
//初始化大小
Map map2=new HashMap(16);
//初始化大小  负载因子
Map map3=new HashMap(16,0.78f);

6.3 添加

//添加 key:name    value:张三
map.put("name","张三");//key必须唯一不能重复
map.put("name","李四");//因为key不能重复,所以后者会把前者覆盖
map.put("age",18);
map.put("sex","男");

//批量添加
Map m = new HashMap();
m.put("k1","v1");
m.put("k2","v3");
map.putAll(m);//把m中的每个元素添加到map中

map.putIfAbsent("age","19");//如果指定的key存在则不放入map中,如果不存在则放入
System.out.println(map);

6.4 删除

//删除操作
map.remove("age2");//根据指定得key移除元素
System.out.println(map);
map.clear(); //清空map容器
System.out.println(map);

6.5 修改

//修改操作
map.replace("name","白敬亭");//替换元素
System.out.println(map);

6.6 查询

boolean f = map.containsKey("k1");//判断map是否存在指定的key
System.out.println(f);

Object v = map.get("k1");//判断指定的key获取对应的value值
System.out.println(v);

Set keys = map.keySet();//返回该map中所有的key
System.out.println(keys);

6.7 遍历

for(Object k:keys){
    Object value= map.get(k);//
    System.out.println(k+"------"+value);
}

6.8 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
    }

总结:

(1)JDK1.8 HashMap原理

Hashmap的原理,存储元素使用得put(key,value),根据key得hash计算出相应得哈希值,根据相应得算法求出该元素在数组中得位置, 如果求出得哈希值相同,则称为哈希冲突,会根据equals来判断元素是否一致,如果equals不同,则存入单向链表上, 如果哈希碰撞得个数超过8个,则把链表转换为红黑二叉树。

(2)HashMap的put()和get()的实现

1) map.put(key,value)实现原理

第一步:首先将k,v封装到Node对象当中(节点)。

第二步:它的底层会调用K的hashCode()方法得出hash值。

第三步:通过哈希表函数/哈希算法,将hash值转换成数组的下标,下标位置上如果没有任何元素,就把Node添加到这个位置上。如果说下标对应的位置上有链表。此时,就会拿着k和链表上每个节点的k进行equals。如果所有的equals方法返回都是false,那么这个新的节点将被添加到链表的末尾。如其中有一个equals返回了true,那么这个节点的value将会被覆盖。

2) map.get(key) 实现原理

第一步:先调用k的hashCode()方法得出哈希值,并通过哈希算法转换成数组的下标。

第二步:通过上一步哈希算法转换成数组的下标之后,在通过数组下标快速定位到某个位置上。重点理解如果这个位置上什么都没有,则返回null。如果这个位置上有单向链表,那么它就会拿着参数K和单向链表上的每一个节点的K进行equals,如果所有equals方法都返回false,则get方法返回null。如果其中一个节点的K和参数K进行equals返回true,那么此时该节点的value就是我们要找的value了,get方法最终返回这个要找的value。

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;
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值