集合的原理使用学习


一、框架

在程序中,将一些功能封装,提供给其他人使用的功能叫做框架。其作用是用来简化开发过程以及代码。

二、集合

实现了类似数组的功能,将多个对象进行操作的方法进行了定义和封装,在开发时只需要调用即可,简化了数组的使用。

与数组的区别:

  • 数组长度固定,集合长度不固定。
  • 数组可以存储基本数据类型和引用数据类型,而集合只能存储引用数据类型。

注意:如果集合存储基本数据类型,会自动转换成包装类。

三、集合框架的体系

集合框架常用的主要有两个体系:Collection和Map

3.1 Collection体系

Collection作为整个体系的父接口,能够存储多种类的对象,特点是无序,无下标,可迭代每个元素。其中有两个常用的子接口。

  • List,该接口增加下标功能,特点是有序,有下标,且元素可重复。常用实现类如下:
  • ArrayList,底层使用数组实现
  • LinkedList,底层使用链表实现
  • Set,该接口特点是无序,无下标,且元素不能重复。
  • HashSet,特点是使用HashMap的key实现。
  • TreeSet,特点是使用tree实现,所以可以排序。

3.2 Collection体系常用方法

add: 添加对象

addAll: 将另一个集合中的所有对象添加到当前集合中。

clear: 清空集合中所有的对象。

contains:判断集合是否包含某个对象

isEmpty:判断集合是否为空

remove:删除一个对象。

size: 得到集合中元素的个数。

toArray:将集合转换成数组。

四、List接口相关

4.1 基本特点

List接口继承自Collection接口,特点是:有序、有下标、元素可以重复,相当于Collection接口,添加一些与下标相关的方法。

add(index,obj): 向指定下标添加元素。

get(index): 获得指定下标的元素。

4.2 有序,无序,排序

有序是会记住元素添加时的顺序存放。

无序是指添加元素的顺序与最终存放的顺序无关,顺序与用户的理解也无关。

排序是指可以根据程序员要求的特点来进行排序。

4.3 常用实现类

ArrayList (Vector) 、LinkedList

Vector与ArrayList具备几乎相同的结构以及完全相同的用法,区别在于ArrayList不安全,Vector是线程安全的,但是性能非常低下,几乎不会被使用,后面会有其他的性能相对较高的安全的集合来代替。

4.4数组和链表

  • 数组的特点:连续空间,长度固定。增删慢,遍历速度较快。
  • 链表的特点:随机空间,长度不固定,增删快,遍历慢。
    • 链表如果操作首尾,比操作中间更快。
    • 链表有单向和双向之分,单向只能向一个方向遍历,双向可以向两边遍历。

4.5 ArrayList的用法

public class TestMain1 {
    //ArrayList用法
    //使用数组原理实现的集合
    public static void main(String[] args) {
        //创建一个集合
        ArrayList list = new ArrayList();
        //添加元素
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("d");
        //通过下标获取元素
        System.out.println(list.get(2));
        //通过下标遍历元素
        //size方法获得集合中元素的个数
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
        //通过foreach遍历
        for (Object object:list) {
            String temp = (String) object;
            System.out.println(temp);
        }
        //使用迭代器循环
        Iterator iterator = list.iterator();
        while (iterator.hasNext()){
            String next = (String) iterator.next();
            System.out.println(next);
        }

        //删除
        list.remove(1);//通过下标删除
        list.remove("d");//直接删除元素
        Object[] objects = list.toArray();//将集合转换成数组
    }
}

注意:Vector与ArrayList用法完全相同。

4.6 LinkedList的基本用法

public class TestMain2 {
    //ArrayList用法
    //使用数组原理实现的集合
    public static void main(String[] args) {
        //创建一个集合
        LinkedList list = new LinkedList();
        //添加元素
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("d");
        //通过下标获取元素
        System.out.println(list.get(2));
        //通过下标遍历元素
        //size方法获得集合中元素的个数
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
        //通过foreach遍历
        for (Object object:list) {
            String temp = (String) object;
            System.out.println(temp);
        }
        //使用迭代器循环
        Iterator iterator = list.iterator();
        while (iterator.hasNext()){
            String next = (String) iterator.next();
            System.out.println(next);
        }

        //删除
        list.remove(1);//通过下标删除
        list.remove("d");//直接删除元素
        Object[] objects = list.toArray();//将集合转换成数组

        //除了上面通用的用法,还有一些特殊的用法
        list.addFirst("1");//在链表最前面添加
        list.addLast("2");//在链表最后面添加
        list.removeFirst();//删除最前面的元素
        list.removeLast();//删除最后面的元素
    }
}

4.7 ArrayList底层实现

1、当创建一个无参的对象时,默认数据为一个空数组,长度为0

2、当创建的无参对象(空数组)第一次添加元素时,会将长度指定为10,添加元素时,会进行一次扩容

3、如果元素的个数没有超过数组的空间时,不会扩容。如果添加的下一个元素超过了数组空间大小时,会进行扩容。:说明扩容的临界点是当数组元素已满时进行。

4、当进行扩容时,会扩容原来大小的1.5倍。如果扩容1.5倍时超出了范围,扩容1个空间,保障至少能添加元素。

5、当新的空间大小超出MAX_ARRAY_SIZE时,指定为Integer.MAX_VALUE,如果超过了Integer.MAX_VALUE时,则报错,

//默认大小
private static final int DEFAULT_CAPACITY = 10;
//存储数据的
Object[] elementData;
//数组中元素的数量
private int size;

//无参构造方法
 public ArrayList() {
     this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

//通过指定长度来创建集合
public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            //通过指定长度来创建数组
            this.elementData = new Object[initialCapacity];
            //如果长度等于0,就创建一个空数组
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            //如果是负数就报错。
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }
//添加元素
 public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //将新添加的元素赋值到数组中,并size加1
     	elementData[size++] = e;
        return true;
 }

 private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
 }

private static int calculateCapacity(Object[] elementData, int minCapacity) {
    	//当通过无参构造方法创建对象数组时,默认为空数组时
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            //将数组大小设置为10
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
}

 private void ensureExplicitCapacity(int minCapacity) {
        modCount++;//修改次数

        // overflow-conscious code
     	//当数组元素已经达到数组所容纳的元素上限时
        if (minCapacity - elementData.length > 0)
            //扩容
            grow(minCapacity);
    }

//扩容
private void grow(int minCapacity) {
        // overflow-conscious code
    	//旧的空间大小,原来数组的长度
        int oldCapacity = elementData.length;
        //新的空间大小,原来数组的1.5倍
    	int newCapacity = oldCapacity + (oldCapacity >> 1);
        //当新的空间-需要的最小空间(size+1)小于0时,新空间直接赋值为最小空间(size+1)
   		if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
    	//当新的空间如果大于2147483639时
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            //指定新的空间为巨大的空间
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
    	//创建一个新的大小数组,并将原来的数据复制到新数组中
        elementData = Arrays.copyOf(elementData, newCapacity);
}

 private static int hugeCapacity(int minCapacity) {
     	//保障的最小空间都超出了范围,此时直接报错
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
     	//如果需要保障的最小空间2147483639,直接指定大小为最大值(2147483647),否则指定为最大数组大小。
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
 }

4.8 LinkedList的原理

1、LinkedList是一个双向链表

2、内置一个Node节点类,来进行链接

3、会保存链表的首尾节点

4、添加元素时,会将元素保存为一个节点(Node),并设置节点相应的前后节点,以及根据情况修改LinkedList的首尾节点。

//LinkedList的内部类,用来保存一个节点
private static class Node<E> {
     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;
     }
 }

transient Node<E> first;//第一个节点

transient Node<E> last;//最后一个节点

// 无参构造方法
public LinkedList(){
    
}

//添加,默认在尾部添加
 public boolean add(E e) {
     linkLast(e);
     return true;
 }
//
 void linkLast(E e) {
     //记住最后一个节点
     final Node<E> l = last;
     //创建一个新的节点,将上一个节点指定为原来的最后一个节点,下一个节点指定为null
     final Node<E> newNode = new Node<>(l, e, null);
     //将自己指定为当前LinkedList的尾部(最后一个节点)
     last = newNode;
     //如果上一个节点为空,则当前节点也设置为第一个节点(首部)
     if (l == null)
         first = newNode;
     else
         //如果上一个节点不为空,则将原来的最后一个节点的下一个节点指定为当前节点
         l.next = newNode;
     size++;
     modCount++;
 }

五、泛型的使用

5.1泛型

泛型时对类型的一种约束。作用是在使用时会自动转成相应的类型,还会对不是该类型的地方进行检查,会有相应的编译错误提示。

5.2泛型集合

在集合创建时使用泛型(推荐使用),会在相机和中添加元素时对元素进行检查,如果类型不一致会出现编译错误的提示,在访问时(获取)会自动转型,避免强制转换的麻烦。

5.3泛型的语法

泛型分为类泛型和方法泛型

  • 类泛型
  • 在类定义时使用泛型(可以使用多个),类中的任意位置都可以使用该定义的泛型。
//T是一个可变化的类型,用户在使用时传入的是什么类型,就会变化什么类型
//如果用户没有传入,则就是Object类型
//在当前类中,只能当为Object使用,因为不确定用户会传入什么类型
//T也可以使用任意字母代替
//多个类型之间可以使用逗号隔开
public class MyClass<T,P> {
    public T test(T object){
        T t;
        return null;
    }

    public T test1(T obj1,P obj2){
        return null;
    }
}

public class TestMain1 {
    public static void main(String[] args) {
        MyClass<String, Integer> c = new MyClass<>();
        String test = c.test("2");
        String s = c.test1("2", 4);
    }
}
  • 方法泛型
  • 是指在类上面不定义泛型,在方法上定义泛型
public class MyClass1 {
    //在方法上定义并使用泛型
    public <T> T test(T obj){
        T t;
        return null;
    }
    //在方法中定义的泛型,在其他方法中不能使用
//    public T test1(){
//
//    }
}

public class TestMain1 {
    public static void main(String[] args) {
        MyClass<String, Integer> c = new MyClass<>();
        String test = c.test("2");
        String s = c.test1("2", 4);

        MyClass1 c1 = new MyClass1();
        //由于在方法中定义了泛型,且要求传入参数与返回值类型一致,所以也不需要强制转换,会自动检查类型
        String str2 = c1.test("2");
    }
}

经典面试题

为什么说Java中的泛型是伪(假)泛型?

1、Java中的泛型仅对传入的参数进行编译检查,不会对真的对参数进处理。

2、Java中的泛型仅对对象使用时进行强制转换,不会真的对对象进行转型处理。

3、在对象真正使用的过程中,仍旧时Object类型。

5.4泛型中的super和extends

当作为参数时,传入的参数根据extends和super的不同,可以传入更宽泛的类型。

?extends B 表示可以传入B类或者B类的子类 ,称为上界限定符

?super B 表示可以传入B类或者B类的父类,称为下界限定符

在使用时,上界不存,下界不取

使用上界时,该集合不能存储数据,可以取值

使用下界时,集合不能取值,但是可以存储数据。

public class TestMain2 {
    public static void main(String[] args) {

        ArrayList<C> list = new ArrayList<>();
        addAll(list);
    }
    //? extends B 表示B类或者B类的子类 上界限定符
    //? super B 表示B类或者B类的父类 下界限定符
    //相当于直接写B类,类型会宽泛一些。
    //上界不存,下届不取
    public static void addAll(ArrayList<? extends B> c){
        //添加元素
//        c.add(new B());
//        for (B b:c){
//            System.out.println(b);
//        }
    }
}

六、Collections工具类

Collections针对集合的帮助类,相当于Arrays是针对数组的帮助类。

主要是对集合的排序、打乱、截取等操作。

 public class TestMain3 {
     public static void main(String[] args) {
         //sort
         ArrayList<Integer> list = new ArrayList<>();
         list.add(23);
         list.add(35);
         list.add(12);
         list.add(7);
         list.add(40);
         list.add(14);
         Collections.sort(list);//对集合进行排序
         System.out.println(list);
         Collections.reverse(list);
         System.out.println(list);
         Collections.shuffle(list); //随机打乱一个集合
         System.out.println(list);

         //如果已知元素,可以使用asList方法将其转换成集合,但是不能强转成ArrayList,因为他是List接口
         //一般用作循环使用。
         List<Integer> list1 = Arrays.asList(23, 35, 12, 8, 10, 45, 13);
     }
 }

七、Set集合

无序,无下标,元素不能重复

7.1 HashSet

通过HashCode实现元素不可重复

当存入的元素hashCode相同时,会使用equals方法来判断是否相同元素,如果不相同,则可以存入。

注意:

HashSet判断不同对象的步骤是:

1、先判断hashCode是否相同,如果不同,直接认定是不同对象,如果相同,进行第二步。

2、判断equals是否相同,如果不同,才认定是不同对象,如果相同,认定为相同对象,会直接放弃添加。所以,在使用HashSet时,如果要存入实体类的对象,一定要对该实体类进行重写equals和hashCode方法。

注意: HashSet底层使用的HashMap,创建HashSet时会创建一个HashMap,每次添加元素时,就是向HashMap的key上面进行添加。

7.2 LinkedHashSet

LinkedHashSet继承自HashSet.

HashSet一般情况下使用HashMap作为底层,而LinkedHashSet使用的LinkedHashMap作为底层,在HashMap的基础上添加了链表的结构,来保存了添加的顺序。

public class TestMain2 {
    public static void main(String[] args) {
        LinkedHashSet<String> let = new LinkedHashSet<>();
        let.add("b");
        let.add("a");
        let.add("c");
        for (String s : let) {
            System.out.println(s);
        }
    }
}

7.3 TreeSet

实现了Sorted接口,自动排序,根据自定的方式(基于Comparable接口)排序。

==注意:==使用TreeSet来存储元素时一定要指定排序的方式,否则会报错。

二叉树:

一个节点,最多只能有两个直接的子节点。

TreeSet进行排序的规则要求实现Comparable接口,如果是自定义的类,要将对象放入到TreeSet中,必须实现该接口,否则会出现ClassCastException。

public class Student implements Comparable<Student> {
    private String name;
    private int age;

    public Student(String name){
        super();
        this.name = name;
    }
    public Student(String name,int age){
        super();
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    @Override
    public boolean equals(Object o) {
//        if (this == o) return true;
//        if (!(o instanceof Student)) return false;
//        Student student = (Student) o;
//        return name.equals(student.name);
        return true;
    }

    @Override
    public int hashCode() {
//        return Objects.hash(name);
        return 1;
    }

    @Override
    public int compareTo(Student o) {
//        if (this.age-o.age <0){
//            return -1;
//        }else if (this.age-o.age >0){
//            return 1;
//        }
        return this.age-o.age ;
    }
}
public class TestMain3 {
    public static void main(String[] args) {
        TreeSet<Integer> set = new TreeSet<>();
        set.add(20);
        set.add(15);
        set.add(30);
        set.add(18);
        set.add(12);
        set.add(14);
        set.add(10);
        set.add(30);//与前面添加的30相等,放弃添加,保证元素不重复。
        System.out.println(set);

        //要使用TreeSet来添加自定义类的对象,需要实现比较接口。
        TreeSet<Student> set1 = new TreeSet<>();
        set1.add(new Student("张三",20));
        set1.add(new Student("李四",18));
        set1.add(new Student("王五",19));
        set1.add(new Student("赵六",22));
        System.out.println(set1);

        ArrayList<Student> list = new ArrayList<>();
        list.add(new Student("张三",20));
        list.add(new Student("李四",18));
        list.add(new Student("王五",19));
        list.add(new Student("赵六",22));
        Collections.sort(list);
        System.out.println(list);
    }
}

上面用的Collections.sort方法进行排序时,如果是自定义对象,也需要实现Comparable接口

public class TestMain3 {
    public static void main(String[] args) {
        ArrayList<Student> list = new ArrayList<>();
        list.add(new Student("张三",20));
        list.add(new Student("李四",18));
        list.add(new Student("王五",19));
        list.add(new Student("赵六",22));
        Collections.sort(list);
        System.out.println(list);
    }
}

==注意:==Comparable接口中的comparableTo方法,返回负数会放到树的左边,证书放到树的右边,返回0,表示元素相等,会放弃添加。

八、Map体系

8.1 Map结构

Map是一个顶层接口,常用的实现类有HashMap、TreeMap等。

Map接口特点是:使用键值(key,value)对来存放数据。key键要求不能重复,value值可以重复。

//常用方法,
put(Object key,Object value);//添加
get(Object key);//获取元素
Set keySet();//返回所有的键集
Collection Values();//返回所有的值

注意: map存取元素时,需要使用key来访问,key可以是任意类型,但是推荐使用字符串。当使用相同的key存放时,会覆盖原来的内容。

8.2 HashMap基本使用

public class TestMain5 {
    //HashMap的用法
    public static void main(String[] args) {
        //创建对象
        HashMap<String,Student> map = new HashMap<>();
        //添加元素
        map.put("a",new Student("1","张三",20));
        map.put("a",new Student("2","李四",21));
        map.put("a",new Student("3","王五",22));
        //获取元素
        Student stu = map.get("b");
        System.out.println(stu);
        //遍历元素
        //1、借助遍历key来遍历值
        //得到keys键集
        Set<String> set = map.keySet();
        for (String key:set){
            System.out.println("key为:"+key+",对应的值为"+map.get(key));
        }
        //2、直接获取值集合
        Collection<Student> colls = map.values();
        for (Student s:colls) {
            System.out.println("值为:"+s);
        }
        //3、在遍历时获得键值
        Set<Map.Entry<String, Student>> entrySet = map.entrySet();
        for (Map.Entry<String, Student> entry:entrySet){
            System.out.println("key为:"+entry.getKey()+",对应的值为:"+entry.getValue());
        }
        //删除元素
        map.remove("a");
        int size = map.size();//得到元素的个数
        System.out.println(size);
    }
}

经典面试题:

HashMap与HashTable的区别:(相同点:使用方法基本一样)

1、HashMap性能较高,但是线程不安全,HashTable线程安全,性能极低,基本不使用。

2、HashMap键和值都可以为null,但是键不能重复,会覆盖。HashTable键和值都不能为null。

8.3 HashMap的底层原理

既有比较快的查找性能,又有比较好的增删性能。

采用数组+链表+红黑树(JDK1.8之后)的结构。

1、创建HashMap时,指定了扩容因子为0.75

2、在第一次添加元素时,才会创建数组,大小为16,设置临界点为12

3、添加元素时,如果key相同,会覆盖值,会根据key的hash计算,并与数组长度求模,得到该元素存放的下标,如果该下标有元素,就直接放到链表最后一个元素,如果此时链表数量大于等于7,进行树化,变化为红黑树。

==注意:==红黑树的变化条件有两个:1、数组长度大于64,2、链表上的元素大于8

4、当元素添加到临界点时,再添加元素时,会进行扩容,扩容为原来的两倍,临界点重新设置为数组大小的*0.75

5、当扩容时,数组大小大于2^30,则指定int的最大值

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 初始大小 16
static final int MAXIMUM_CAPACITY = 1 << 30;//最大空间2^30次方
static final float DEFAULT_LOAD_FACTOR = 0.75f;//扩容因子
static final int TREEIFY_THRESHOLD = 8;//变成树的临界值
static final int UNTREEIFY_THRESHOLD = 6;//变成链表的临界值
static final int MIN_TREEIFY_CAPACITY = 64;//变成树的最小容量,单条链表(桶)上的元素超过8个

//map中的链表结构(单向链表)
static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;//哈希值
    final K key;
    V value;
    Node<K,V> next;

    Node(int hash, K key, V value, Node<K,V> next) {
        this.hash = hash;
        this.key = key;
        this.value = value;
        this.next = next;
    }

    public final K getKey()        { return key; }
    public final V getValue()      { return value; }
    public final String toString() { return key + "=" + value; }

    public final int hashCode() {
        return Objects.hashCode(key) ^ Objects.hashCode(value);
    }

    public final V setValue(V newValue) {
        V oldValue = value;
        value = newValue;
        return oldValue;
    }

    public final boolean equals(Object o) {
        if (o == this)
            return true;
        if (o instanceof Map.Entry) {
            Map.Entry<?,?> e = (Map.Entry<?,?>)o;
            if (Objects.equals(key, e.getKey()) &&
                Objects.equals(value, e.getValue()))
                return true;
        }
        return false;
    }
}

transient Node<K,V>[] table;//map中存放数据的数组,每个元素都是一个链表

//hash计算
static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
//无参构造
public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
//
public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    //如果是第一次添加元素(table为null),或者table中的元素数量为0
    if ((tab = table) == null || (n = tab.length) == 0)
        //设置n为16
        n = (tab = resize()).length;
    //hash值来模运算,如果原来的数组的首元素为空,直接赋值
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {
        Node<K,V> e; K k;
        //如果首元素不为空,则判断key的hash与equals是否相同
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
           //相同则覆盖
            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);
                  //如果当前桶上元素数量大于等于7,转换成红黑树
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                //key相同则停止循环
                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;
    //如果size大于临界点
    if (++size > threshold)
        resize();//扩容
    afterNodeInsertion(evict);
    return null;
}

final Node<K,V>[] resize() {
    //记住原来的数据
    Node<K,V>[] oldTab = table;
    //如果原来的数组为空,长度即为0,否则为原来的长度
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    //记住原来的空间大小
    int oldThr = threshold;
    //新的大小暂时为0
    int newCap, newThr = 0;
    //如果原来的大小大于0
    if (oldCap > 0) {
        //如果原来的大小大于最大大小,直接赋值为最大值
        if (oldCap >= MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
        //如果原来的大小扩容1倍后小于最大大小并且原来的大小等于16
        //d大小扩容1倍
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                 oldCap >= DEFAULT_INITIAL_CAPACITY)
            //临界点扩容1倍
            newThr = oldThr << 1; // double threshold
    }
    //原来的临界点大于0
    else if (oldThr > 0) // initial capacity was placed in threshold
        newCap = oldThr;//新的空间等于老的临界点
    else {               // zero initial threshold signifies using defaults
        newCap = DEFAULT_INITIAL_CAPACITY;//新的空间大小设置为16
       //新的临界点为12
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }
    //处理极限值
    if (newThr == 0) {
        float ft = (float)newCap * loadFactor;
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                  (int)ft : Integer.MAX_VALUE);
    }
    threshold = newThr;//将计算的临界值保存到属性中
    @SuppressWarnings({"rawtypes","unchecked"})
    //创建新的数组大小
    Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    table = newTab;//保存新的数组到属性中
    //如果原来的数组元素不为空,则重新计算位置
    if (oldTab != null) {
        for (int j = 0; j < oldCap; ++j) {
            Node<K,V> e;
            if ((e = oldTab[j]) != null) {
                oldTab[j] = null;
                if (e.next == null)
                    newTab[e.hash & (newCap - 1)] = e;
                else if (e instanceof TreeNode)
                    ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                else { // preserve order
                    Node<K,V> loHead = null, loTail = null;
                    Node<K,V> hiHead = null, hiTail = null;
                    Node<K,V> next;
                    do {
                        next = e.next;
                        if ((e.hash & oldCap) == 0) {
                            if (loTail == null)
                                loHead = e;
                            else
                                loTail.next = e;
                            loTail = e;
                        }
                        else {
                            if (hiTail == null)
                                hiHead = e;
                            else
                                hiTail.next = e;
                            hiTail = e;
                        }
                    } while ((e = next) != null);
                    if (loTail != null) {
                        loTail.next = null;
                        newTab[j] = loHead;
                    }
                    if (hiTail != null) {
                        hiTail.next = null;
                        newTab[j + oldCap] = hiHead;
                    }
                }
            }
        }
    }
    return newTab;
}

8.4 Properties

Properties继承自HashTable,但是添加了setProperty和getProperty方法,只能操作字符串。

作用是用来加载系统的配置文件。所以里面还添加了load方法,专门用来加载配置。

public class TestMain7 {
    public static void main(String[] args) {
        Properties prop = new Properties();
        //设置值
        prop.setProperty("username","root");
        prop.setProperty("password","root");
        //获取值
        System.out.println(prop.getProperty("username"));
    } 
}

8.5 TreeMap

TreeMap使用树的方式来存放数据,需要对key对应的类实现Comparable接口。当添加后,会自动排序。

public class Student implements Comparable<Student> {
    private String id;
    private String name;
    private int age;

    public Student(String id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Student)) return false;
        Student student = (Student) o;
        return age == student.age && Objects.equals(id, student.id) && Objects.equals(name, student.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, name, age);
    }

    @Override
    public String toString() {
        return "Student{" +
            "id='" + id + '\'' +
            ", name='" + name + '\'' +
            ", age=" + age +
            '}';
    }

    @Override
    public int compareTo(Student o) {
        return this.id.hashCode() - o.id.hashCode();
    }
}

public class TestMain8 {
    //TreeMap的用法
    public static void main(String[] args) {
        TreeMap<Student,String> map = new TreeMap<>();
        map.put(new Student("1","张三2",20),"aaa" );
        map.put(new Student("2","李四1",22),"bbb" );
        map.put(new Student("3","王五4",24),"ccc" );
        System.out.println(map);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

红骸儿

你的鼓励拯救一个程序员

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值