美文网首页
【javase08~集合框架及其内部原理】

【javase08~集合框架及其内部原理】

作者: 昵称该起什么好呢 | 来源:发表于2019-01-17 03:25 被阅读0次

【部分内容来自网络,侵删】

集合框架

在集合框架的类继承体系中,最顶层有两个接口:

  1. Collection表示一组纯数据
  2. Map表示一组key-value对

Collection框架


cc.jpg

Map框架


ss.jpg

List

List是一个接口,用于保存一系列数据。List表示允许有重复元素的集合。

ArrayList

底层通过数组实现,查询快,插入慢

public class Demo2 {
    public static void main(String[] args) {
        List<String> array = new ArrayList<String>();
        
        array.add("aaa");
        array.add("bbb");
        array.add("ccc");
        array.add("ddd");
        array.add("eee");
        array.add("fff");
        array.add("ggg");
        array.add("hhh");
        array.add("iii");
        array.add("jjj");
        array.add("kkk");
        array.add("lll");
        array.add("mmm");
        
        System.out.println(array.get(10));
        
        array.remove(12);
        System.out.println(array);
        
        Iterator<String> iter = array.iterator();
        while(iter.hasNext()){
            System.out.println(iter.next());
        }

        System.out.println(array.contains("aaaa"));
    }
    
}
LinkedList

底层通过链表实现,插入快,查找慢

public class Demo2 {
    public static void main(String[] args) {
        List<String> array = new LinkedList<String>();
        
        array.add("aaa");
        array.add("bbb");
        array.add("ccc");
        array.add("ddd");
        array.add("eee");
        array.add("fff");
        array.add("ggg");
        array.add("hhh");
        array.add("iii");
        array.add("jjj");
        array.add("kkk");
        array.add("lll");
        array.add("mmm");
        
        System.out.println(array.get(10));
        
        array.remove(12);
        System.out.println(array);
        
        Iterator<String> iter = array.iterator();
        while(iter.hasNext()){
            System.out.println(iter.next());
        }
        
        System.out.println(array.contains("aaaa"));
    }
    
}

Set

Set是一个接口,用于保存一系列数据。Set表示不允许有重复元素的集合。

HashSet

无序set,底层通过hash表实现。Set通过hashCode和equals方法来保证对象是同一个。
hash表结构如下,


hh.gif
public class Demo2 {
    public static void main(String[] args) {
        Set<String> set = new HashSet<String>();
        
        
        set.add("aaaa");
        set.add("bbb");
        set.add("aaaa");
        set.add("cccc");
        set.add("eeee");
        set.add("fff");
        
        for(String s: set){
            System.out.println(s+ " " + s.hashCode());
        }
        
        Set<User> set2 = new HashSet<User>();
        set2.add(new User("sjl", 12));
        set2.add(new User("sjl", 14));
        set2.add(new User("sjl", 12));
        set2.add(new User("song", 12));
        set2.add(new User("song", 15));
        
        for(User s: set2){
            System.out.println(s + " " + s.hashCode());
        }
        
        
        
        
    }
    
}

class User {
    String name;
    int age;
    public User(String name, int age) {
        super();
        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 int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + age;
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        return result;
    }
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        User other = (User) obj;
        if (age != other.age)
            return false;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        return true;
    }
    @Override
    public String toString() {
        return "User [name=" + name + ", age=" + age + "]";
    }
}
LinkedHashSet

相比较于HashSet,LinkedHashSet是有序的。数据将按照插入顺序进行存放。
LinkedHashMap源码分析

/*
 *
 *before和after这两个字段,是用来给迭代时使用的,相当于一个双向链表,
 *before:指向前一个entry元素。after:指向后一个entry元素
 *
 */
static class Entry<K,V> extends HashMap.Node<K,V> {
        Entry<K,V> before, after;
        Entry(int hash, K key, V value, Node<K,V> next) {
            super(hash, key, value, next);
        }
}
ll.png
public class Demo2 {
    public static void main(String[] args) {

        Set<String> set = new LinkedHashSet<String>();
        
        
        set.add("aaaa");
        set.add("bbb");
        set.add("aaaa");
        set.add("cccc");
        set.add("eeee");
        set.add("fff");
        
        for(String s: set){
            System.out.println(s+ " " + s.hashCode());
        }
    }
    
}
TreeSet

TreeSet将数据按照自然顺序进行存放。底层使用树进行实现(红黑树,一种自平衡的二叉树)。

二叉树的遍历方法

  1. 前序遍历 按照中->左->右进行遍历


    qq.jpg
  2. 中序遍历 按照左->中->右进行遍历


    zz.jpg
  3. 后序遍历 按照左->右->中进行遍历


    hh.jpg
  4. 层次遍历(广度优先)


    bb.jpg

前序遍历,中序遍历,后序遍历均可以使用递归的方式实现,这种方式代码逻辑最简单,此外还可以使用左路径压栈的方式进行遍历。

首先看下二叉查找树,满足:

  1. 空树是一个二叉搜索树
  2. 左子树所有节点的值均小于根节点的值
  3. 右子树所有节点的值均大于根节点的值
  4. 左右子树都是二叉搜索树
  5. 中序遍历序列为升序

如下就是一颗二叉搜索树:


12.jpg

二叉搜索树的查找原理:
1、从根结点开始查找,如果树为空,就返回NULL。
2、如果树不空,就让数据X和根结点的数据Data作比较。
3、如果X的值大于根结点的Data,就往右子树中进行搜索;如果X的值小于根结点的Data,就往左子树中进行搜索。
4、如果X的值等于Data,就表示查找完成,返回该结点。

二叉搜索树的插入原理与查找原理类似:
将要插入的结点x,与根节点进行比较,若小于则去到左子树进行比较,若大于则去到右子树进行比较,重复以上操作直到找到一个空位置用于放置该新节点

二叉搜索树的删除原理:
假如要删除某节点f的子节点p,

  1. 如果p是叶子节点,直接删除


  2. 如果p只有左子树或右子树,则直接用p.left或者p.right替换


  3. p既有左子树left,又有右子树right,找到右子树的最小节点,用该节点替换p的值。再根据前面两种情况,删除右子树的最小节点即可


极端情况下的二叉搜索树会退化为链表,存在性能问题。
AVL树是一种自平衡的二叉树,满足如下特征:

  1. AVL树是一个二叉搜索树
  2. AVL的左右子树高度差的绝对值不超过1
  3. 空树,AVL的左右子树都是AVL树。

可以通过旋转操作来保持输的平衡,可以参考这篇文章
https://blog.csdn.net/qq_25806863/article/details/74755131

插入方式 描述 旋转方式
LL 在a的左子树根节点的左子树上插入节点而破坏平衡 右旋转
RR 在a的右子树根节点的右子树上插入节点而破坏平衡 左旋转
LR 在a的左子树根节点的右子树上插入节点而破坏平衡 先左旋后右旋
RL 在a的右子树根节点的左子树上插入节点而破坏平衡 先右旋后左旋
  1. LL


    ll.png
//原7的左孩子5变为父结点,7变为其右孩子,而原5的右子树变为7的左子树
private BinaryNodeInterface<T> rotateRight(BinaryNodeInterface<T> nodeN){
    BinaryNodeInterface<T> nodeL = nodeN.getLeftChild();//获取7的左孩子也就是5
    nodeN.setLeftChild(nodeL.getRightChild());//原节点5的右孩子设为7的左孩子
    nodeL.setRightChild(nodeN);//将7设为节点5的右孩子
    return nodeL;//返回节点5
}
  1. RR


    rr.png
//原10右孩子13变为父结点,10变为其左孩子,而原13的左子树将变为10的右子树
private BinaryNodeInterface<T> rotateLeft(BinaryNodeInterface<T> nodeN){
    BinaryNodeInterface<T> nodeR = nodeN.getRightChild();//获取10的右孩子也就是13
    nodeN.setRightChild(nodeR.getLeftChild());//将10的左孩子设为13的右孩子
    nodeR.setLeftChild(nodeN);//将10设为13的左孩子
    return nodeR;//返回节点13
}
  1. LR


    LR.png
//在5节点按照RR型向左旋转一次之后,二叉树在7节点仍然不能保持平衡,这时还需要再向右旋转一次。
private BinaryNodeInterface<T> rotateLeftRight(BinaryNodeInterface<T> nodeN){
    BinaryNodeInterface<T> nodeL = nodeN.getLeftChild();//获取7的左节点5
    nodeN.setLeftChild(rotateLeft(nodeL));//将5节点左旋并返回子树的新的根节点6,将6设为7的左节点
    return rotateRight(nodeN);//将7节点进行右旋
}
  1. RL


    RL.png
//在13节点按照RR型向右旋转一次之后,二叉树在10节点仍然不能保持平衡,这时还需要再向左旋转一次
private BinaryNodeInterface<T> rotateRightLeft(BinaryNodeInterface<T> nodeN){
    BinaryNodeInterface<T> nodeR = nodeN.getRightChild();//获取10的右节点13
    nodeN.setRightChild(rotateRight(nodeR));//将13节点进行右旋,并返回子树的新的根节点11,将11设为10的右节点
    return rotateLeft(nodeN);//将10节点进行左旋
}

红黑树是是一个自平衡的二叉搜索树,满足如下性质:

  1. 红黑树是一个平衡二叉树
  2. 节点是红色或黑色
  3. 根节点是黑色,NULL节点是黑的
  4. 每个叶节点是黑色的。
  5. 每个红色节点的两个子节点都是黑色
  6. 从根节点到每个叶子结点的所有路径都包含相同数目的黑色节点

红黑树不像AVL树一样,永远保持绝对平衡,而是一种相对平衡,查询效率略低于AVL树,但插入,删除要高于AVL树
理解红黑树的关键在于下面两个函数(TreeMap源码):

    /** From CLR */
    private void fixAfterInsertion(Entry<K,V> x) {
        x.color = RED;//只能插入红色节点

        while (x != null && x != root && x.parent.color == RED) {
            if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
                Entry<K,V> y = rightOf(parentOf(parentOf(x)));
                if (colorOf(y) == RED) {
                    setColor(parentOf(x), BLACK);
                    setColor(y, BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    x = parentOf(parentOf(x));
                } else {
                    if (x == rightOf(parentOf(x))) {
                        x = parentOf(x);
                        rotateLeft(x);
                    }
                    setColor(parentOf(x), BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    rotateRight(parentOf(parentOf(x)));
                }
            } else {
                Entry<K,V> y = leftOf(parentOf(parentOf(x)));
                if (colorOf(y) == RED) {
                    setColor(parentOf(x), BLACK);
                    setColor(y, BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    x = parentOf(parentOf(x));
                } else {
                    if (x == leftOf(parentOf(x))) {
                        x = parentOf(x);
                        rotateRight(x);
                    }
                    setColor(parentOf(x), BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    rotateLeft(parentOf(parentOf(x)));
                }
            }
        }
        root.color = BLACK;
    }
    /** From CLR */
    private void fixAfterDeletion(Entry<K,V> x) {
        while (x != root && colorOf(x) == BLACK) {
            if (x == leftOf(parentOf(x))) {
                Entry<K,V> sib = rightOf(parentOf(x));

                if (colorOf(sib) == RED) {
                    setColor(sib, BLACK);
                    setColor(parentOf(x), RED);
                    rotateLeft(parentOf(x));
                    sib = rightOf(parentOf(x));
                }

                if (colorOf(leftOf(sib))  == BLACK &&
                    colorOf(rightOf(sib)) == BLACK) {
                    setColor(sib, RED);
                    x = parentOf(x);
                } else {
                    if (colorOf(rightOf(sib)) == BLACK) {
                        setColor(leftOf(sib), BLACK);
                        setColor(sib, RED);
                        rotateRight(sib);
                        sib = rightOf(parentOf(x));
                    }
                    setColor(sib, colorOf(parentOf(x)));
                    setColor(parentOf(x), BLACK);
                    setColor(rightOf(sib), BLACK);
                    rotateLeft(parentOf(x));
                    x = root;
                }
            } else { // symmetric
                Entry<K,V> sib = leftOf(parentOf(x));

                if (colorOf(sib) == RED) {
                    setColor(sib, BLACK);
                    setColor(parentOf(x), RED);
                    rotateRight(parentOf(x));
                    sib = leftOf(parentOf(x));
                }

                if (colorOf(rightOf(sib)) == BLACK &&
                    colorOf(leftOf(sib)) == BLACK) {
                    setColor(sib, RED);
                    x = parentOf(x);
                } else {
                    if (colorOf(leftOf(sib)) == BLACK) {
                        setColor(rightOf(sib), BLACK);
                        setColor(sib, RED);
                        rotateLeft(sib);
                        sib = leftOf(parentOf(x));
                    }
                    setColor(sib, colorOf(parentOf(x)));
                    setColor(parentOf(x), BLACK);
                    setColor(leftOf(sib), BLACK);
                    rotateRight(parentOf(x));
                    x = root;
                }
            }
        }

        setColor(x, BLACK);
    }

以上就是TreeSet的底层实现,TreeSet默认按照数据的自然序进行排序,但也可以指定自定义的comparator或者让类实现Comparable接口

public class Demo2 {
    public static void main(String[] args) {
        Set<Integer> set = new TreeSet<Integer>();
        set.add(142);
        set.add(129);
        set.add(23);
        set.add(67);
        set.add(17);
        set.add(321);
        set.add(66);
        
        System.out.println(set);
        
    }
    
}

指定自定义排序如下:

public class Demo2 {
    public static void main(String[] args) {
        Set<String> set = new TreeSet<String>(new MyComparator());
        
        set.add("fortran");
        set.add("Hello");
        set.add("Spring");
        set.add("DjangoWeb");
        set.add("zip");
        set.add("C++");
        set.add("web server");
        
        System.out.println(set);
        
    }
    
}

class MyComparator implements Comparator<String>{

    public int compare(String o1, String o2) {
        if(o1.length() != o2.length()){
            return o1.length() - o2.length();
        } else {
            return o1.compareTo(o2);
        }
    }
    
}
Iterator和ListIterator主要区别
  1. iterator()方法在set和list接口中都有定义,但是ListIterator()仅存在于list接口中(或实现类中);
  2. ListIterator有add()方法,可以向List中添加对象,而Iterator不能
  3. ListIterator和Iterator都有hasNext()和next()方法,可以实现顺序向后遍历,但是ListIterator有hasPrevious()和previous()方法,可以实现逆向(顺序向前)遍历。Iterator就不可以。
  4. ListIterator可以定位当前的索引位置,nextIndex()和previousIndex()可以实现。Iterator没有此功能。
  5. 都可实现删除对象,但是ListIterator可以实现对象的修改,set()方法可以实现。Iierator仅能遍历,不能修改。

以上介绍的都是单路查找树,下面所要给的是多路查找树
1、2-3树

  1. 2节点包含一个元素和2个孩子(或者没有孩子)
  2. 3节点包含一大一小两个元素和三个孩子(或者没有孩子),两个元素按照大小顺序排列好
  3. 2-3树的所有叶子结点都在同一层次


    23.jpg
    231.png

2、2-3-4树

  1. 2节点包含一个元素和2个孩子(或者没有孩子)
  2. 3节点包含一大一小两个元素和三个孩子(或者没有孩子),两个元素按照大小顺序排列好
  3. 4节点包含小中大三个元素和四个孩子(或者没有孩子)
  4. 2-3-4树的所有叶子结点都在同一层次


    234.png

3、B树
2-3树和2-3-4树都是B树的特例,B树是一种多路平衡查找树,如果B中节点最大的孩子数目为m,称为m阶树

  1. 树中每个结点最多有m棵子树,即最多m-1个关键字
  2. 若根结点不是终端节点,则至少有两棵子树
  3. 除根节点之外的所有非叶节点至少有[m/2]棵子树
  4. 所有非叶结点的结构如下


    111.jpg
  5. 所有的叶子结点出现在同一层次上


    bb.png

B树的查找原理:

  1. 首先将关键字key和节点中的关键字比较,如果等于某个关键字则查找成功
  2. 如果和所有关键字都不相等,则看key处于哪个范围内,然后去对应的指针所指向的子树中查找


    b1.jpg

B树的插入原理:


c1.jpg
c2.jpg
c3.jpg
c4.jpg

4、B+树
B+树是B树的变种,有着比B树更高的查询效率。与B树相比有如下的不同:

  1. 有k个子树的中间节点包含有k个元素(B树中是k-1个元素),每个元素不保存数据,只用来索引,所有数据
    都保存在叶子节点。
  2. 所有的叶子结点中包含了全部元素的信息,及指向含这些元素记录的指针,且叶子结点本身依关键字的大小
    自小而大顺序链接。
  3. 所有的中间节点元素都同时存在于子节点,在子节点元素中是最大(或最小)元素。
111111.jpg

关于B树和B+树的区别及在数据库中的应用可以查看这篇文章https://blog.csdn.net/zhuanzhe117/article/details/78039692

集合的三种遍历方法
class Test {
    public static void main(String[] args) {
        
        Set<String> set = new HashSet<String>();
        set.add("Hello");
        set.add("World");
        set.add("Java");
        //1. iterator遍历
        Iterator<String> iter = set.iterator();
        while(iter.hasNext()){
            System.out.println(iter.next());    
        }
        
        //2. for
        List<String> list = new ArrayList<String>();
        list.add("C++");
        list.add("Python");
        list.add("go");
        System.out.println(list instanceof RandomAccess);
        for(int i = 0; i < list.size(); i++){
            System.out.println(list.get(i));
        }
        
        
        //3. for-each
        System.out.println(set instanceof RandomAccess);
        for(String s:set){
            System.out.println(s);  
        }
    }
}

Map

Map用户存放一系列无序k-v对的集合。

HashMap

底层采用hash表实现,原理见HashSet

LinkedHashMap

底层采用hash表+链表实现,原理见LinkedHashSet

TreeMap

底层采用红黑树实现,原理见TreeSet

class Test {
    public static void main(String[] args) {
        
        Map<Integer, String> map = new HashMap<Integer, String>();
        
        map.put(1, "Hello");
        map.put(2, "Java");
        map.put(3, "Spring");
        map.put(4, "JavaScript");
        map.put(5, "Python");
    }
}
map的两种遍历方法
class Test {
    public static void main(String[] args) {
        
        Map<Integer, String> map = new HashMap<Integer, String>();
        
        map.put(1, "Hello");
        map.put(2, "Java");
        map.put(3, "Spring");
        map.put(4, "JavaScript");
        map.put(5, "Python");
        
        //1. Entity
        Set<Map.Entry<Integer, String>> entry =  map.entrySet();
        for(Map.Entry<Integer, String> e:entry){
            System.out.println("key="+e.getKey() + ", value="+e.getValue());    
        }
        
        
        //2. key
        Set<Integer> keySet = map.keySet();
        for(int k:keySet){
            System.out.println("key=" + k + ", value=" + map.get(k));   
        }
        
    }
}
可变参数
class Test {
    public static void main(String[] args) {
        testMutableArgs(1,2,3,4,5);
    }
    
    public static void testMutableArgs(int... nums){
    
        for(int i = 0; i < nums.length; i++){
            System.out.println(i);  
        }
        
    }
}

相关文章

  • 【javase08~集合框架及其内部原理】

    【部分内容来自网络,侵删】 集合框架 在集合框架的类继承体系中,最顶层有两个接口: Collection表示一组纯...

  • Java集合框架

    参考:JAVA集合框架中的常用集合及其特点、适用场景、实现原理简介 > https://blog.csdn.net...

  • 从源码解析LinkedList集合

    上篇文章我们介绍了ArrayList类的基本的使用及其内部的一些方法的实现原理,但是这种集合类型虽然可以随机访问数...

  • Java集合框架概述

    前言 Java集合框架概述; 主要总述Java集合框架的设计理念, 组成和基本接口(及其区别等) 博客同步至个人博...

  • 集合系列(一):集合框架概述

    集合系列(一):集合框架概述 Java 集合是 Java API 用得最频繁的一类,掌握 Java 集合的原理以及...

  • SDWebImage内部实现及其原理

    SDWebImage的知名度就不用说了,github上近10k的star,国内外太多的App使用其进行图片加载。简...

  • java学习笔记6

    迭代器的原理及源码解析 A:迭代器原理迭代器原理:迭代器是对集合进行遍历,而每一个集合内部的存储结构都是不同的,所...

  • SpringBoot系列之日志框架介绍及其原理简介

    SpringBoot系列之日志框架介绍及其原理简介 1、常用日志框架简介 市面上常用日志框架:JUL、JCL、jb...

  • java基础知识总结(三)

    本文主要介绍:1.集合框架2.泛型3.内部类 1.集合框架集合是一个对象容器,用来存储其它的多个对象。他们的关系如...

  • 2017年初BAT的JAVA面试题汇集

    Java基础● 集合类以及集合框架;HashMap与HashTable实现原理,线程安全性,hash冲突及处理算法...

网友评论

      本文标题:【javase08~集合框架及其内部原理】

      本文链接:https://www.haomeiwen.com/subject/jbemrqtx.html