Java学习(十四)--集合

介绍

Java 的集合就像一个容器,用来存储 Java 类的对象;

  • 实际上只是保存对象的引用变量,但通常习惯上认为集合里保存的是对象;
  • 可以动态保存任意多个对象,使用方便(相比数组);

集合类主要负责保存、盛装其他数据,因此也被称为容器类;

java.util包 提供一个表示和操作对象集合的统一构架,包含大量集合接口,以及这些接口的实现类和操作它们的算法;

集合接口

单列集合Collection:定义存取一组对象的方法的集合

双列集合Map:保存具有映射关系“key-value对”的集合

集合的实现类

collection实现子类可以存放多个元素,每个元素可以是Object;

  • 在 Java5 之前, Java 集合会丢失容器中所有对象的数据类型,把所有对象都当成 Object 类型处理;
  •  从 JDK 5.0 增加了泛型以后,Java 集合可以记住容器中对象的数据类型;

有些是有序、可重复(list),有些是无序、不可重复(set);

collection接口没有直接的实现子类,是通过它的子接口Set和List来实现;

如何选用

开发中,根据业务操作特点,并结合集合实现类特征进行选择。


Collection接口

Collection 接口定义一些通用的方法,实现对集合的基本操作;    例如:添加对象、删除对象、清空容器和判断容器是否为空等。

Collection接口没有直接的实现子类,通过它的子接口Set和List来实现;

子接口List

 List 集合类中元素有序(即添加顺序和取出顺序一致)、且可重复;

List 集合中的每个元素都有其对应的顺序索引,即支持索引

  •     从0开始
  •     list.get(3)

JDK API中List接口的常用的实现类:ArrayList、LinkedList和Vector。

List容器中元素均对应一个整数型的序号记载其在容器中的位置,可根据序号存取容器中的元素;

常用方法

遍历方式

  • 1.iterator
  • 2.增强for
  • 3.普通for循环

//1、迭代器遍历方式

Collection col = new ArrayList();
col.add(new Book("三国演义", "罗贯中", 10.1));
col.add(new Book("小李飞刀", "古龙", 5.1));
col.add(new Book("红楼梦", "曹雪芹", 34.6));

//遍历 col 集合
//1. 先得到 col 对应的迭代器(Iterator对象)
Iterator iterator = col.iterator();

//2. 使用 while 循环遍历
while (iterator.hasNext()) {//判断是否还有数据

//返回下一个元素,类型是 Object
Object obj = iterator.next();

System.out.println("obj=" + obj);
}

//3. 当退出 while 循环后 , 这时 iterator 迭代器,指向最后的元素

//在使用next()方法前,若没有调用hasNext()方法,会发生NoSuchElementException异常
// iterator.next();

//4. 如果希望再次遍历,需要重置我们的迭代器
iterator = col.iterator();

System.out.println("===第二次遍历===");

while (iterator.hasNext()) {
Object obj = iterator.next();
System.out.println("obj=" + obj);
}
//2、增强for循环 遍历方式

// 创建一个集合
List list = new ArrayList();
list.add(new Dog("小黑", 3));
list.add(new Dog("大黄", 100));
list.add(new Dog("大壮", 8));

//先使用 for 增强
for (Object dog : list) {
System.out.println("dog=" + dog);
}


与使用 Iterator 接口迭代访问集合元素类似的是,foreach 循环中的迭代变量也不是集合元素本身,系统只是依次把集合元素的值赋给迭代变量,因此在 foreach 循环中修改迭代变量的值也没有任何实际意义;
//3.普通for遍历方式
 
for (int i = 0; i < list.size(); i++) {
     System.out.println("对象=" + list.get(i));
 }

实现类Arraylist

基于动态数组的数据结构实现数据存储;

  •     对象引用的一个”变长”数组;     实现了可变数组的大小;
  •     提供了快速基于索引访问元素的方式,对尾部成员的增加和删除支持较好;
  •     元素必须连续存储;

可将ArrayList作为缺省选择

基本等同于Vector,但线程不安全(执行效率高);

底层操作机制

维护一个Object类型的数组elementData;

当用无参构造器创建对象时,初始elementData容量为0;

  • 第1次添加,则扩容为10;
  • 如需再次扩容,则扩容为1.5倍;(10*1.5 =15)

若使用指定大小的构造器,则初始elementData容量为指定大小[例如:8];

  • 若需扩容,则直接扩容为elementData容量的1.5倍。
public static void main(String[] args) {
	List list = new ArrayList();
Person smith = new Person("smith", 80, 50000);
Person smith02 = new Person("smith02", 80, 50000);
Person smith03 = new Person("smith03", 80, 50000);
	list.add(smith);
	List list02 = new ArrayList(2);
	list02.add(smith);
	list02.add(smith02);
	list02.add(smith03);
}


//ArrayList部分源码分析
/*
属性: 
private static final int DEFAULT_CAPACITY = 10;
private static final Object[] EMPTY_ELEMENTDATA = {};  
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
transient Object[] elementData; 
private int size; 


  public ArrayList() { //无参构造器
      this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;//创建一个空的elementData数组{}
    }

第一次扩容:

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // 1、先确定是否需要扩容
        elementData[size++] = e;//2、再进行赋值
        return true;
    }


private void ensureCapacityInternal(int minCapacity) {//minCapacity为1;
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { //若elementData为空数组{}
          minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); //为minCapacity赋值10
        }

        ensureExplicitCapacity(minCapacity);//此时minCapacity为10
    }


    private void ensureExplicitCapacity(int minCapacity) {
        modCount++; //记录集合被修改的次数

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity); //扩容
    }

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; //值为2147483639


private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length; //oldCapacity值为0
        int newCapacity = oldCapacity + (oldCapacity >> 1); //newCapacity=0;
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;  //minCapacity=11,表示新增的第11个元素;
        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);//第一次扩容 newCapacity = 10;
    }


再一次扩容:

private void grow(int minCapacity) { //此时minCapacity为11
        // overflow-conscious code
        int oldCapacity = elementData.length; //oldCapacity值为10
        int newCapacity = oldCapacity + (oldCapacity >> 1); //newCapacity=15;
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;  //newCapacity=11;
        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);//第二次扩容 newCapacity = 15;
    }


(2)过程
1)使用无参构造器
	1.创建一个空的elementData数组 = {};
	2.执行list.add,先确定是否要扩容,然后执行赋值;
	3.确定minCapacity,第一次扩容10;
	4.modCount++记录集合被修改的次数;
	5.如果elementData的大小不够,则使用grow()去扩容;
	6.若扩容,则使用扩容机制;
		第1次newCapacity = 10;
		第2次及其以后,按照1.5倍扩容
		扩容使用Arrays.copyOf();
2)使用有参构造器
	1.创建一个指定大小的elementData数组;
		this.elementData= new Object[capacity];
	2.若需扩容,按照elementDatade 1.5被扩容,执行流程同无参构造器扩容流程;
    

实现类LinkedList

插入或删除元素的操作的效率高;    但随即访问元素的速度相对较慢(随机访问是指检索集合中特定索引位置的元素);

可以添加任意元素(元素可以重复),包括null

双向链表结构

  •     基于链表数据结构的实现;        占用的内存空间比较大
  •     底层实现了双向链表和双端队列的特点

线程不安全,没有实现同步。


底层操作机制

LinkedList维护两个属性 first和last分别指向 首节点和尾节点

节点(Node对象):

  •     维护prev、next、item三个属性;
  •     通过prev指向前一个,通过next指向后一个节点;
  •     是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;
        }
}

备注:
prev变量记录前一个元素的位置
next变量记录下一个元素的位置
item:保存数据
源码分析:

/*
 LinkedList linkedList = new LinkedList();
 linkedList.add(2);//增加一个结点,默认添加到末尾

属性:
    transient Node<E> first;
    transient Node<E> last;

           1.   public LinkedList() {//无参构造器
                    }

           2. 这时 linkeList 的属性 first = null  last = null

           3. 执行 添加
               public boolean add(E e) {//e为Integer 2
                    linkLast(e);
                    return true;
                }

            4.将新的结点,加入到双向链表的最后
             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++;
            }

         */


 /*
 linkedList.remove(); // 这里默认删除的是第一个结点

          1. 执行 removeFirst
            public E remove() {
                return removeFirst();
            }
         2. 执行
            public E removeFirst() {
                final Node<E> f = first;
                if (f == null)
                    throw new NoSuchElementException();
                return unlinkFirst(f);
            }
          3. 执行 unlinkFirst, 将 f 指向的双向链表的第一个结点拿掉
            private E unlinkFirst(Node<E> f) {
                // assert f == first && f != null;
                final E element = f.item;
                final Node<E> next = f.next;
                f.item = null;
                f.next = null; // help GC
                first = next;
                if (next == null)
                    last = null;
                else
                    next.prev = null;
                size--;
                modCount++;
                return element;
            }
         */

实现类Vector

线程安全( synchronized);   

大多数操作与ArrayList相同;

底层操作机制

源码分析:
        
/*
属性:
 protected Object[] elementData;
 protected int elementCount;
 protected int capacityIncrement;

            1. new Vector() 底层
            public Vector() {
                this(10);
            }

         补充:如果是  Vector vector = new Vector(8),则直接走下面方法
            public Vector(int initialCapacity) {
                this(initialCapacity, 0);
            }

         2. vector.add(i)
         2.1  //下面这个方法就添加数据到vector集合
            public synchronized boolean add(E e) { //Vector类的操作方法是线程安全的
                modCount++;
                ensureCapacityHelper(elementCount + 1);
                elementData[elementCount++] = e;
                return true;
            }
          2.2  //确定是否需要扩容 条件 : minCapacity - elementData.length>0
            private void ensureCapacityHelper(int minCapacity) {
                // overflow-conscious code
                if (minCapacity - elementData.length > 0)
                    grow(minCapacity);
            }

          2.3 //如果 需要的数组大小 不够用,就扩容 , 扩容的算法
              //newCapacity = oldCapacity + ((capacityIncrement > 0) ?
              //                             capacityIncrement : oldCapacity);
              //就是扩容两倍.
            private void grow(int minCapacity) {
                // overflow-conscious code
                int oldCapacity = elementData.length;
                int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                                 capacityIncrement : oldCapacity);
                if (newCapacity - minCapacity < 0)
                    newCapacity = minCapacity;
                if (newCapacity - MAX_ARRAY_SIZE > 0)
                    newCapacity = hugeCapacity(minCapacity);
                elementData = Arrays.copyOf(elementData, newCapacity);
            }
         */

应用


子接口Set

元素;

  •     存放元素为无序(添加和取出的顺序不一致,但取出顺序是固定)、没有索引
  •     不能添加重复的元素;有且仅有一个null

set接口没有提供额外的方法;

不是同步的;


遍历方式

  • 1.iterator迭代器
  • 2.增强for循环;不能使用索引的方式遍历

实现类HashSet

Set接口的典型实现

  •     不是线程安全的;
  •     实际上HashMap

底层: 实际是HashMap,数组+链表+红黑树

  • HashMap.Node,记录了结点的hash(哈希值) , key , value , next(Node<K,V> next),用于存储单向链表结构中的数据

元素

  •     按Hash算法来存储集合中的元素,具有很好的存取、查找、删除;

性能

  •     不能保证元素的排列顺序,取决于hash后,再确定索引的结果
  •     可以存放 null ,但是只能有一个 null,即元素不能重复;
    public HashSet() { //无参构造器
        map = new HashMap<>();
    }
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;
        }
    }
afterNodeInsertion(evict); //evict = true;

void afterNodeAccess (Node<K,V> p) { }
    void afterNodeInsertion(boolean evict) { }
    void afterNodeRemoval(Node<K,V> p) { }


if ((p = tab[i = (n - 1) & hash]) == null)
                    tab[i] = newNode(hash, key, value, null);
    
Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {
        return new Node<>(hash, key, value, next);
    }

底层操作机制

说明:
1.HashSet底层为HashMap,添加元素时,先得到该对象的HashCode值
2.根据HashCode值,通过某个散列函数决定该对象在HashSet底层数组中的存储位置--(索引值)
	在存储数据表table,查看该索引位置的元素
3.若该位置为null,则直接添加
4.若有,则调用equals()方法;
	5.equals()方法返回true,则添加失败
	6.如果为false,保存该元素,通过链表的方式链接
7. 在java8中,若一条链表的元素个数达到TREEIFY_THRESHOLD(8),且table表的大小>=MIN_TREEIFY_CAPACITY(64),则进行树化(红黑树)。


 @SuppressWarnings({"all"})
public class HashSetSource {

public static void main(String[] args) {
	HashSet hashSet = new HashSet();
	hashSet.add("java");//到此位置,第 1 次 add 分析完毕.
	hashSet.add("php");//到此位置,第 2 次 add 分析完毕
	hashSet.add("java");
	System.out.println("set=" + hashSet);
}
}


//源码分析 
  
//HashSet类
1、执行HashSet()
属性:	
private static final Object PRESENT = new Object();
	
	public HashSet() {//无参构造器
        map = new HashMap<>();
    }
	
	

//HashMap类
属性:
	transient Node<K,V>[] table;
	int threshold;
	static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
	static final int MAXIMUM_CAPACITY = 1 << 30;
	static final float DEFAULT_LOAD_FACTOR = 0.75f;
	static final int TREEIFY_THRESHOLD = 8;
		
	    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // DEFAULT_LOAD_FACTOR=0.75;
    }
	

2、执行add()	

//HashSet类
	    public boolean add(E e) { //e:java
        return map.put(e, PRESENT)==null; //e:java map:{} PRESENT:Object@540,共享的一个对象
    }

3.执行 put() , 该方法会执行 hash(key) 得到key对应的hash值 算法h = key.hashCode()) ^ (h >>> 16)	
//HashMap类	
		public V put(K key, V value) {//key:java value:Object@540
       return putVal(hash(key), key, value, false, true);
    }
	
4、执行 putVal
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: 是HashMap 的一个数组,类型是Node[],transient Node<K,V>[] table;
        if ((tab = table) == null || (n = tab.length) == 0) //if语句表示如果当前table 是null, 或者 大小=0,就开始第一次扩容到16个空间.
            n = (tab = resize()).length;//扩容后返回HashMap$Node[16]@571 ;此时table为HashMap$Node[16]@571;
		//根据key得到 hash, 计算该 key 应该存放到 tab 表的哪个索引位置,并把这个位置的对象,赋给 p	
        if ((p = tab[i = (n - 1) & hash]) == null)//判断 p 是否为 null
            tab[i] = newNode(hash, key, value, null); //如果 p 为 null, 表示还没有存放元素, 就创建一个newNode结点放入tab[i]位置;
        else {
		//一个开发技巧提示: 在需要局部变量(辅助变量)时候,再创建
            Node<K,V> e; K k;
			//如果当前索引位置对应的链表的第一个元素和准备添加的 key 的 hash 值一样;
			//并且满足 下面两个条件之一:
			//(1) 准备加入的 key 和 p 指向的 Node 结点的 key 是同一个对象
			//(2) p 指向的 Node 结点的 key 的 equals() 和准备加入的 key 比较后相同,就不能加入
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
			//再判断 p 是不是一颗红黑树,如果是一颗红黑树,就调用 putTreeVal , 来进行添加	
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
			//如果 table 对应索引位置,已经是一个链表, 就使用 for 循环比较
			//(1) 依次和该链表的每一个元素比较后,都不相同, 则加入到该链表的最后
				//注意在把元素添加到链表后,立即判断 该链表是否已经达到 8 个结点, 就调用 treeifyBin() 对当前这个链表进行树化(转成红黑树)
				//注意,在转成红黑树时,要进行判断, 判断条件
				//if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY(64))
				//	resize();
				//	如果上面条件成立,先 table 扩容.
				//	只有上面条件不成立时,才进行转成红黑树;
			//(2) 依次和该链表的每一个元素比较过程中,如果有相同情况,就直接 break
			
                for (int binCount = 0; ; ++binCount) {//死循环
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        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;
		//就是我们每加入一个结点 Node(k,v,h,next), size++
        if (++size > threshold)
            resize();//扩容
        afterNodeInsertion(evict);
        return null;
    }
	
	//扩容
    final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;//当前table 是null,即初始值
        int oldCap = (oldTab == null) ? 0 : oldTab.length; //oldCap:0
        int oldThr = threshold; //属性threshold初始值为0;
        int newCap, newThr = 0;
        if (oldCap > 0) {
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold
        }
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults
            newCap = DEFAULT_INITIAL_CAPACITY; //DEFAULT_INITIAL_CAPACITY初始值为16
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); //DEFAULT_LOAD_FACTOR初始值为0.75 ;newThr为12
        }
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        threshold = newThr; //threshold赋值为12 
        @SuppressWarnings({"rawtypes","unchecked"})
            Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; //创建newTab:HashMap$Node[16]@571
        table = newTab;//table:HashMap$Node[16]@571
        if (oldTab != null) {//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;//newTab:HashMap$Node[16]@571
    }

//创建Node[]结点	
    Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {
        return new Node<>(hash, key, value, next);
    }
HashSet的扩容和转化红黑树机制
  •     HashSet 底层是 HashMap, 第一次添加时, table 数组扩容到 16,临界值(threshold) =  16*加载因子(loadFactor)是 0.75 = 12
  •     如果 table 数组使用到了临界值 12,就会扩容到 16 * 2 = 32,新的临界值就是 32*0.75 = 24, 依次类推
  •     在 Java8 中, 如果一条链表的元素个数到达 TREEIFY_THRESHOLD(默认是 8 ),并且 table 的大小 >= MIN_TREEIFY_CAPACITY(默认 64),就会进行树化(红黑树),否则仍然采用数组扩容机制

实现类LinkedHashSet

底层LinkedHashMap【是HashMap的子类】,维护了一个 数组+双向链表【Hash表和双向链表(head和tail)】

根据元素的HashCode值决定元素的存储位置,同时用链表维护元素的次序;

LinkedHashMap.Entry

  •     不仅记录了结点的hash , key , value , next
  •     还维护了一个双向的数据结构(其实就是维护了两个LinkedHashMap.Entry类的两个引用before和after)

底层操作机制
Set set = New LinkedHashSet();

//源码分析:

//LinkHashSet类
1.    public LinkedHashSet() {
        super(16, .75f, true);
    }

//HashSet类
2. HashSet(int initialCapacity, float loadFactor, boolean dummy) {//参数值16, 0.75,true
        map = new LinkedHashMap<>(initialCapacity, loadFactor);
    }

//LinkedHashMap类
3.    public LinkedHashMap(int initialCapacity, float loadFactor) {
        super(initialCapacity, loadFactor);
        accessOrder = false;
    }

//HashMap类
4.    public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        this.loadFactor = loadFactor; //0.75
        this.threshold = tableSizeFor(initialCapacity); //16
    }

/
2. set.add(new String("AA"));

//源码分析
    public String(String original) {
        this.value = original.value;
        this.hash = original.hash;
    }

//HashSet
3.    public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }
步骤同HashSet

实现类TreeSet

特点

  •     可以确保集合元素处于排序状态 ;        有序,查询速度比List快
  •     底层使用红黑树结构存储数据

排序方式

    自然排序:

  •       调用集合元素的compareTo(Object  obj)方法来比较元素之间的大小关系,然后将集合元素按升序(默认情况)排列;
  •            该排序方式下,只能向 TreeSet 集合中添加相同数据类型的对象,否则会抛出 ClassCastException 异常;
  •             针对Integer和String等基础对象类型;

    定制排序

  •         通过Comparator接口来实现。
  •         需要重写compare(T o1,T o2)方法
API方法

    相比collection接口,增加访问第一个、前一个、后一个、最后一个元素的方法和3 个从 TreeSet 中截取子 TreeSet 的方法    


map接口

介绍

Map与Collection并列存在;

  •     map用于保存具有映射关系的数据:key-value

key和value;

  •     key:        不允许重复,可以为null;  常用String类作为Map的“键”;
  •     velue:      可以为null,允许重复;
  •     key和value之间存在单向一对一关系,即通过指定的key总能找到唯一的、确定的value;
  •     Map中的key和value都可以是任何引用类型的数据,封装到HashMap$Node对象

Map存放数据示意图,1对k-v存放到一个HashMap$Node中,又因为HashMap.Node<k,v> 实现Map.Entry<k,v>接口;    

Map map = new HashMap();

map.put("no1", "韩顺平");//k-v
map.put("no2", "张无忌");//k-v
map.put(new Car(), new Person());//k-v

//1. k-v 最后是 HashMap$Node node = newNode(hash, key, value, null)
	static class Node<K,V> implements Map.Entry<K,V>{
		...}

//2. k-v 为了方便程序员的遍历,还会 创建entrySet 集合 ,该集合存放的元素的类型 Map.Entry, 而一个Entry对象有k,v

entrySet<map.Entry<K,V>> // 即: transient Set<Map.Entry<K,V>> entrySet;

//3. entrySet中,定义的类型是 Map.Entry ,实际上存放的还是 HashMap $Node

//因为 static class Node<K,V> implements Map.Entry<K,V>

//4. 当把 HashMap$Node 对象 存放到 entrySet 就方便我们的遍历, 因为 Map.Entry提供了重要方法 K getKey(); V getValue();

Set set = map.entrySet();

System.out.println(set.getClass());// HashMap$EntrySet

for (Object obj : set) {
System.out.println(obj.getClass()); //HashMap$Node

//从 HashMap$Node 取出k-v
//1. 先做一个向下转型

Map.Entry entry = (Map.Entry) obj;

System.out.println(entry.getKey() + "-" + entry.getValue() ); }

Set set1 = map.keySet();
System.out.println(set1.getClass());

Collection values = map.values();
System.out.println(values.getClass())

遍历元素方式

Map map = new HashMap();
map.put("邓超", "孙俪");
map.put("王宝强", "马蓉");
map.put("宋喆", "马蓉");
map.put("刘令博", null);
map.put(null, "刘亦菲");
map.put("鹿晗", "关晓彤");

//1、entries遍历

Set entrySet = map.entrySet(); // EntrySet<Map.Entry<K,V>>

for (Object entry : entrySet) {
//将 entry 转成 Map.Entry
Map.Entry m = (Map.Entry) entry;
System.out.println(m.getKey() + "-" + m.getValue());
}


//2、values遍历

Collection values = map.values();

for (Object value : values) {
System.out.println(value);
}


//3、通过键找值遍历--从键取值是耗时的操作

//第一组: 先取出 所有的 Key , 通过 Key 取出对应的 Value
Set keyset = map.keySet();

for (Object key : keyset) {
System.out.println(key + "-" + map.get(key));
}

常用方法


实现类

//使用Properties类来读取mysql.properties文件

//1.创建Properties对象
        Properties properties = new Properties();

//2.加载指定配置文件
        properties.load(new FileReader("src\\mysql.properties"));

//3.把k-v显示控制台
        properties.list(System.out);
 
//4.根据key获取对应的值
       String user = properties.getProperty("user");
        String pwd = properties.getProperty("pwd");
      
System.out.println("用户名=" + user);
System.out.println("密码是=" + pwd);
//使用Properties类来创建配置文件,修改配置文件内容
        Properties properties = new Properties();

//创建
//1.如果该文件没有key就是创建
//2.如果该文件有key,就是修改
        properties.setProperty("charset", "utf8");
 
       properties.setProperty("user", "汤姆");//注意保存时,是中文的 unicode 码值

        properties.setProperty("pwd", "888888");

//将k-v存储文件中即可

        properties.store(new FileOutputStream("src\\mysql2.properties"), (String)null);

        System.out.println("保存配置文件成功~");

Collection类

Collections 类是 Java 提供的一个操作 Set、List 和 Map 等集合的工具类;

提供了许多操作集合的静态方法,实现集合元素的排序、查找、替换和复制等操作;

方法

查找、替换操作

排序

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

hahaha2221

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值