集合

集合

概念:将多个元素组成一个单元的对象,用于存储、检索、操纵和传输数据。

集合按照存储结构可分为两大类:

单列集合(Collection),双列集合(Map)。

和数组的区别

1.数组长度固定,集合长度不固定。

2.数组可以存储基本类型和引用类型,集合只能存储引用类型

位置

java.util.*;

Collection体系

img

Collection父类接口

简述:Collection是所有单列集合的根接口,单列集合分为List和Set。

创建集合:

Collection collection = new ArrayList();

常用方法:

  1. 添加元素 collection.add();

  2. 删除元素

    删除指定的元素collection.remove();

    删除所有的元素collection.clear();

  3. 遍历元素(重点)

    1. 使用增强for(因为无下标)

      for(Object object : collection){ }

    2. 使用迭代器

      //haNext(); 有没有下一个元素
      //next(); 获取下一个元素
      //remove(); 删除当前元素
      Iterator it = collection.iterator();
      while(it.hasNext()){
        String object = (String)it.next(); //强转
        // 可以使用it.remove(); 进行移除元素
        // collection.remove(); 不能用collection其他方法 会报并发修改异常
      }
      
  4. 判断 collection.contains(); collection.isEmpty();

  5. Stream stream() 将集合元素进行聚合操作。

List接口

特点:有序、有下标、元素可以重复。元素是以一种线性的方式进行存储,存储过程中可以通过索引来访问集合中指定元素。

创建集合对象:

List list = new ArrayList<>( );

常用方法

  1. 添加元素 list.add( ); 会对基本类型进行自动装箱

     List list2 = new ArrayList();
            //自动装箱
            list2.add(20);
            list2.add(21);
            list2.add(22);
            list2.add(23);
            list2.add(24);
            list2.remove((Object) 21);
            list2.remove(new Integer(20));
            System.out.println("元素个数:"+list2.size());
            System.out.println(list2.toString());
    
  2. 删除元素 可以用索引 list.remove(0)

    当删除数字与索引矛盾时 对数字强转

     list2.remove((Object) 21);
     list2.remove(new Integer(20));
    
  3. 遍历

    1. 使用for遍历

      for(int i = 0; i < lise.size(); i++){
        sout(list.get(i)); 
      }
      
    2. 使用增强for

      for(Object list: collection){ }

    3. 使用迭代器

      Iterator it = collection.iterator();
      while(it.hasNext()){
        String object = (String)it.next(); //强转
        // 可以使用it.remove(); 进行移除元素
        // collection.remove(); 不能用collection其他方法 会报并发修改异常
      }
      
    4. 使用列表迭代器 💡(注意和迭代器区别:可以向前或者向后遍历,添加,删除,修改元素)

      ListIterator li = list.listIterator();
      while(li.hasNext()){
        System.out.println(li.nextIndex() + ":" + li.next()); //从前往后遍历
      }
      
      while(li.hasPrevious()){
        System.out.println(li.previousIndex() + ":" + li.previous()); //从后往前遍历
      }
      
  4. 获取指定元素 list.indexOf( );

  5. 返回子集合 sublist(x, y); 左闭右开

    List subList = list.subList(1, 3); 返回索引 1、2

List实现类

ArrayList【重点】:

· 数组结构实现,查询快,增删慢。

*JDK1.2版本,运行效率快,线程不安全。

* 用Object数组存储对象

* 默认大小为10

* 当容量不够时,会自动增大【增大的系数是1.5倍+1】

* 内容可以重复

* 有序

源码分析:

注意:如果没有向集合中添加任何元素,容量为0,添加一个元素后容量为10,每次扩容为原来的1.5倍。

DEFAULT_CAPACITY = 10; 默认容量
elementData 存放元素的数组
size 集合大小
add(); 添加元素
public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!修改个数
        elementData[size++] = e;
        return true;
    }
private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(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;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        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);
    }

Vector:

*数组结构实现,查询快,增删慢。

*JDK1.0版本,运行效率慢,线程安全。

public static void main(String[] args) {
        //创建集合
        Vector vector = new Vector();
        vector.add("西瓜");
        vector.add("哈密瓜");
        vector.add("甜瓜");
        vector.add("香瓜");
        System.out.println("元素个数:"+vector.size());
        System.out.println(vector);
        //遍历
        //使用枚举器
        Enumeration en = vector.elements();
        while (en.hasMoreElements()){
            String o = (String)en.nextElement();
            System.out.println(o);
        }
        //查找最后一个元素
        System.out.println(vector.lastElement());
        //查找第一个元素
        System.out.println(vector.firstElement());
        //查找第3个元素
        System.out.println(vector.elementAt(2));
    }

LinkedList:

* 链表结构 内存不连续

* 内容可重复

* 双向链表

* 增删改效率高 查询效率低【因它的数据结构而决定的特性】

* 有下标

* 有序

  • JDK1.2版本,运行效率快,线程不安全。

双向链表就是他包含上一个节点的内存地址。并且还包含下一个节点的内存地址。

img

源码分析:

size 集合大小
Node<E> first 链表头结点
Node<E> last 链表尾结点
public boolean add(E e) {
        linkLast(e);
        return true;
    }
 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;
        }
    }
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++;
    }

ArrayList和LinkedList的区别:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M1wvjNDC-1616019603584)(C:\Users\lenovo\AppData\Roaming\Typora\typora-user-images\image-20210312051437609.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vF3VzggQ-1616019603587)(C:\Users\lenovo\AppData\Roaming\Typora\typora-user-images\image-20210316024739420.png)]

Deque(双向队列)

Deque 接口(先进先出)的大小可变数组的实现。数组双端队列没有容量限制;它们可根据需要增加以支持使用。它们不是线程安全的;在没有外部同步时,它们不支持多个线程的并发访问。禁止 null 元素。此类很可能在用作堆栈时快于 Stack,在用作队列时快于 LinkedList。

大多数 ArrayDeque 操作以摊销的固定时间运行。

public static void main(String[] args) {
        ArrayDeque<String> deque = new ArrayDeque<>();
        ArrayList<String> name = new ArrayList<>();
        name.add("张三");
        name.add("lisi");
        name.add("wangwu");
        //将属于Collection中的子类对象作为参数传入队列
        ArrayDeque<String> deque1 = new ArrayDeque<>(name);
        System.out.println(deque1);
        //取得第一个
        System.out.println(deque1.getFirst());
        //取得最后一个
        System.out.println(deque1.getLast());
    }
public static void main(String[] args) {
        ArrayDeque<String> deque = new ArrayDeque<>();
        //添加
        deque.add("a");
        //添加到末尾
        deque.addLast("z");
        //添加到第一个
        deque.addFirst("b");
        //插入开头
        deque.offerFirst("v");
        //插入末尾
        //deque.offerLast("q");
        //清空
        //deque.clear();
        //是否包含
        System.out.println(deque.isEmpty());
        System.out.println(deque.contains("a"));
        //遍历
        Iterator it=deque.iterator();
        while (it.hasNext()){
            String s = (String) it.next();
            System.out.println(s);
        }
    }
	public static void main(String[] args) {
		//创建
		ArrayDeque deque=new ArrayDeque();
		//压栈
		deque.push("1");
		deque.push("2");
		deque.push("3");
		
		//取出第一个不删除元素
		System.out.println(deque.element());
		
		//出栈 并删除
		String str=(String) deque.pop();
		System.out.println(str);
		System.out.println(deque.pop());
		System.out.println(deque.pop());
		//取得栈中内容大小
		System.out.println(deque.size());
	}
}

Stack栈

简介

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Jalbvgsy-1616019603590)(file:///C:\Users\lenovo\AppData\Local\Temp\ksohtml21208\wps5.jpg)]

public class Stackextends VectorStack 类表示后进先出(LIFO)的对象堆栈。它通过五个操作对类 Vector 进行了扩展 ,允许将向量视为堆栈。它提供了通常的 push 和 pop 操作,以及取堆栈顶点的 peek 方法、测试堆栈是否为空的 empty 方法、在堆栈中查找项并确定到堆栈顶距离的 search 方法。

首次创建堆栈时,它不包含项。

public static void main(String[] args) {
		
		//栈
		Stack<String> stack=new Stack<String>();
		
		stack.push("1");
		stack.push("2");
		stack.push("3");
		stack.push("4");
		
		//查看堆栈顶部的对象,但不从堆栈中移除它。
		System.out.println(stack.peek());
		
		//不为空则取
		while(!stack.isEmpty()){
			//弹出
			String str=stack.pop();
			System.out.println(str);
		}
	}

范型

  • 本质是参数化类型,把类型作为参数传递
  • 常见形式有泛型类、泛型接口、泛型方法
  • 语法 T成为类型占位符,表示一种引用类型,可以写多个逗号隔开
  • 好处 1. 提高代码重用性 2. 防止类型转换异常,提高代码安全性

范型类

/**
 * 范型类
 * 语法:类名
 * T是占位符,表示一种引用类型,如果编写多个用逗号隔开
 */
public class Mygeneric<T> {
   //创建范型T
    //创建变量
   T t;
   //范型作为方法的参数
   public void show(T t){
       System.out.println(t);
   }
   //范型作为方法的返回值
    public T getT(){
       return t;
    }

}

范型接口

/**
 * 范型接口
 * @param <T>
 */
public interface Myinterface<T> {
    String name = "张三";
    T server(T t);
}
public class Test2 implements Myinterface<String> {
    @Override
    public String server(String s) {
        System.out.println(s);
        return s;
    }
}

范型方法

  //泛型方法
    public <T> T show(T t){
        System.out.println("泛型方法" + t);
        return t;
    }
public static void main(String[] args) {
        MyMethod myMethod = new MyMethod();
        myMethod.show("字符串");// 自动类型为字符串
        myMethod.show(200);// integer类型
        myMethod.show(3.14);// double类型
    }

范型集合

概念:参数化类型、类型安全的集合,强制集合元素的类型必须一致

特点:

  • 编译时即可检查,而非运行时抛出异常
  • 访问时,不必类型转换(拆箱)
  • 不同泛型之间应用不能相互赋值,泛型不存在多态

Set集合概述

Set子接口

特点:无序,无下标,元素不可重复。

方法:全部继承于collection。

/**
 * 测试Set接口的使用
 * 特点:1.无序,没有下标;2.重复
 * 1.添加数据
 * 2.删除数据
 * 3.遍历【重点】
 * 4.判断
 */
public class Demo1 {
	public static void main(String[] args) {
		Set<String> set=new HashSet<String>();
		//1.添加数据
		set.add("a");
		set.add("b");
		set.add("c");
		System.out.println("数据个数:"+set.size());
		System.out.println(set.toString());//无序输出
		//2.删除数据
		/*
		 * set.remove("tang"); System.out.println(set.toString());
		 */
		//3.遍历【重点】
		//3.1 使用增强for
		for (String string : set) {
			System.out.println(string);
		}
		//3.2 使用迭代器
		Iterator<String> iterator=set.iterator();
		while (iterator.hasNext()) {
			System.out.println(iterator.next());
		}
		//4.判断
		System.out.println(set.contains("a"));
		System.out.println(set.isEmpty());
	}
}

set实现类

HashSet【重点】
  • 基于HashCode计算元素存放位置。
  • 当存入元素的哈希码相同时,会调用equals进行确认,如结果为true,则拒绝后者存入。
/**
 * 人类
 */
public class Person {
	private String name;
	private int age;
	public Person(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 "Peerson [name=" + name + ", age=" + age + "]";
	}
}
/**
 * HashSet集合的使用
 * 存储结构:哈希表(数组+链表+红黑树)
 * 1.添加元素
 * 2.删除元素
 * 3.遍历
 * 4.判断
*/
public class Demo3 {
	public static void main(String[] args) {
		HashSet<Person> hashSet=new HashSet<>();
		Person p1=new Person("a",21);
		Person p2=new Person("b", 22);
		Person p3=new Person("c", 21);
		//1.添加元素
		hashSet.add(p1);
		hashSet.add(p2);
		hashSet.add(p3);
        //重复,添加失败
        hashSet.add(p3);
        //直接new一个相同属性的对象,依然会被添加,不难理解。
        //假如相同属性便认为是同一个对象,怎么修改?
        hashSet.add(new Person("yu", 21));
		System.out.println(hashSet.toString());
		//2.删除元素
		hashSet.remove(p2);
		//3.遍历
		//3.1 增强for
		for (Person person : hashSet) {
			System.out.println(person);
		}
		//3.2 迭代器
		Iterator<Person> iterator=hashSet.iterator();
		while (iterator.hasNext()) {
			System.out.println(iterator.next());		
		}
		//4.判断
		System.out.println(hashSet.isEmpty());
        //直接new一个相同属性的对象结果输出是false,不难理解。
        //注:假如相同属性便认为是同一个对象,该怎么做?
		System.out.println(hashSet.contains(new Person("a", 21)));
	}
}

:hashSet存储过程:

  1. 根据hashCode计算保存的位置,如果位置为空,则直接保存,否则执行第二步。
  2. 执行equals方法,如果方法返回true,则认为是重复,拒绝存储,否则形成链表。

存储过程实际上就是重复依据,要实现“注”里的问题,可以重写hashCode和equals代码:

 @Override
    public int hashCode() {
        int n = this.name.hashCode();
        int n2 = this.age;
        return n+n2;
    }
    @Override
    public boolean equals(Object obj) {
        if (this==obj){
            return true;
        }
        if (obj==null){
            return false;
        }
        if (obj instanceof Person){
            Person person = (Person) obj;
            if(this.name.equals(person.getName())&&this.age== person.getAge());
            return true;
        }
        return false;
    }

hashCode方法里为什么要使用31这个数字大概有两个原因:

  1. 31是一个质数,这样的数字在计算时可以尽量减少散列冲突。
  2. 可以提高执行效率,因为31*i=(i<<5)-i,31乘以一个数可以转换成移位操作,这样能快一点;但是也有网上一些人对这两点提出质疑。

TreeSet

基于排列顺序实现元素不重复。

实现了SortedSet接口,对集合自动排序。

元素对象的类型必须实现comparable接口,指定排序规则。

通过compareTo方法确定是否为重复元素。

/**
 * 使用TreeSet保存数据
 * 存储结构:红黑树
 * 要求:元素类必须实现Comparable接口,compareTo方法返回0,认为是重复元素 
 */
public class Demo4 {
	public static void main(String[] args) {
		TreeSet<Person> persons=new TreeSet<Person>();
		Person p1=new Person("tang",21);
		Person p2=new Person("he", 22);
		Person p3=new Person("yu", 21);
		//1.添加元素
		persons.add(p1);
		persons.add(p2);
		persons.add(p3);
		//注:直接添加会报类型转换错误,需要实现Comparable接口
		System.out.println(persons.toString());
		//2.删除元素
		persons.remove(p1);
		persons.remove(new Person("he", 22));
		System.out.println(persons.toString());
		//3.遍历(略)
		//4.判断
		System.out.println(persons.contains(new Person("yu", 21)));
	}
}

查看Comparable接口的源码,发现只有一个compareTo抽象方法,在人类中实现它:

public class Person implements Comparable<Person>{
    @Override
	//1.先按姓名比
	//2.再按年龄比
	public int compareTo(Person o) {
		int n1=this.getName().compareTo(o.getName());
		int n2=this.age-o.getAge();
		return n1==0?n2:n1;
	}
}

除了实现Comparable接口里的比较方法,TreeSet也提供了一个带比较器Comparator的构造方法,使用匿名内部类来实现它:

 public static void main(String[] args) {
        TreeSet<Person> treeSet = new TreeSet<>(new Comparator<Person>() {
            @Override
            public int compare(Person o1, Person o2) {
                // 先按年龄比较
				// 再按姓名比较
                int n1 = o1.getAge()-o2.getAge();
                int n2 = o1.getName().compareTo(o2.getName());
                return n1==0?n2:n1;
            }
        });
        Person p1 = new Person("a",1);
        Person p2 = new Person("b",1);
        Person p3 = new Person("c",1);
        Person p4 = new Person("d",1);
        treeSet.add(p1);
        treeSet.add(p2);
        treeSet.add(p3);
        treeSet.add(p4);
        System.out.println(treeSet);
    }

小案例

/**
 * 要求:使用TreeSet集合实现字符串按照长度进行排序
 * helloworld tangrui hechengyang wangzixu yuguoming
 * Comparator接口实现定制比较
 */
public class Demo6 {
	public static void main(String[] args) {
		TreeSet<String> treeSet=new TreeSet<String>(new Comparator<String>() {
			@Override
			//先比较字符串长度
			//再比较字符串
			public int compare(String o1, String o2) {
				int n1=o1.length()-o2.length();
				int n2=o1.compareTo(o2);
				return n1==0?n2:n1;
			}			
		});
		treeSet.add("helloworld");
		treeSet.add("tangrui");
		treeSet.add("hechenyang");
		treeSet.add("yuguoming");
		treeSet.add("wangzixu");
		System.out.println(treeSet.toString());
        //输出[tangrui, wangzixu, yuguoming, hechenyang, helloworld]
	}
}

Map集合

Map接口的特点:

  1. 用于存储任意键值对(Key-Value)。
  2. 键:无序、无下标、不允许重复(唯一)。
  3. 值:无序、无下标、允许重复。

img

  • 方法
    • V put(K key,V value)//将对象存入到集合中,关联键值。key重复则覆盖原值。
  • Object get(Object key)//根据键获取相应的值。
    • Set<K>//返回所有的key
    • Collection<V> values()//返回包含所有值的Collection集合。
    • Set<Map.Entry<K,V>>//键值匹配的set集合
/**
 * Map接口的使用
 * 特点:1.存储键值对 2.键不能重复,值可以重复 3.无序
 */
public class Demo1 {
	public static void main(String[] args) {
		Map<String,Integer> map=new HashMap<String, Integer>();
		//1.添加元素
		map.put("tang", 21);
		map.put("he", 22);
		map.put("fan", 23);
		System.out.println(map.toString());
		//2.删除元素
		map.remove("he");
		System.out.println(map.toString());
		//3.遍历
		//3.1 使用keySet();
		for (String key : map.keySet()) {
			System.out.println(key+" "+map.get(key));
		}
		//3.2 使用entrySet();效率较高
		for (Map.Entry<String, Integer> entry : map.entrySet()) {
			System.out.println(entry.getKey()+" "+entry.getValue());
		}
	}
}

Map集合的实现类

HashMap【重点】
  • JDK1.2版本,线程不安全,运行效率快;允许用null作为key或是value。
/**
   * 学生类
   */
  public class Student {
  	private String name;
  	private int id;	
  	public Student(String name, int id) {
  		super();
  		this.name = name;
  		this.id = id;
  	}
  	public String getName() {
  		return name;
  	}
  	public void setName(String name) {
  		this.name = name;
  	}
  	public int getId() {
  		return id;
  	}
  	public void setId(int id) {
  		this.id = id;
  	}
  	@Override
  	public String toString() {
  		return "Student [name=" + name + ", age=" + id + "]";
  	}
  }
/**
   * HashMap的使用
   * 存储结构:哈希表(数组+链表+红黑树)
   */
  public class Demo2 {
  	public static void main(String[] args) {
  		HashMap<Student, String> hashMap=new HashMap<Student, String>();
  		Student s1=new Student("a", 36);
  		Student s2=new Student("b", 55);
  		Student s3=new Student("c", 30);
  		//1.添加元素
  		hashMap.put(s1, "成都");
  		hashMap.put(s2, "杭州");
  		hashMap.put(s3, "郑州");
  		//添加失败,但会更新值
  		hashMap.put(s3,"上海");
  		//添加成功,不过两个属性一模一样;
  		//注:假如相同属性便认为是同一个对象,怎么修改?
  		hashMap.put(new Student("he", 10),"上海");
  		System.out.println(hashMap.toString());
  		//2.删除元素
  		hashMap.remove(s3);
  		System.out.println(hashMap.toString());
  		//3.遍历
  		//3.1 使用keySet()遍历
  		for (Student key : hashMap.keySet()) {
  			System.out.println(key+" "+hashMap.get(key));
  		}
  		//3.2 使用entrySet()遍历
  		for (Entry<Student, String> entry : hashMap.entrySet()) {
  			System.out.println(entry.getKey()+" "+entry.getValue());
  		}
  		//4.判断
  		//注:同上
  		System.out.println(hashMap.containsKey(new Student("he", 10)));
  		System.out.println(hashMap.containsValue("成都"));
  	}
  }

注:和之前说过的HashSet类似,重复依据是hashCode和equals方法,重写即可:

@Override
  public int hashCode() {
      final int prime = 31;
      int result = 1;
      result = prime * result + id;
      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;
      Student other = (Student) obj;
      if (id != other.id)
          return false;
      if (name == null) {
          if (other.name != null)
              return false;
      } else if (!name.equals(other.name))
          return false;
      return true;
  }
HashMap源码分析
  • 默认初始化容量: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;

  • 链表调整为红黑树的链表长度阈值(JDK1.8):static final int TREEIFY_THRESHOLD = 8;

  • 红黑树调整为链表的链表长度阈值(JDK1.8):static final int UNTREEIFY_THRESHOLD = 6;

  • 链表调整为红黑树的数组最小阈值(JDK1.8):static final int MIN_TREEIFY_CAPACITY = 64;

  • HashMap存储的数组:transient Node<K,V>[] table;

  • HashMap存储的元素个数:transient int size;

    • 默认加载因子是什么?
      • 就是判断数组是否扩容的一个因子。假如数组容量为100,如果HashMap的存储元素个数超过了100*0.75=75,那么就会进行扩容。
    • 链表调整为红黑树的链表长度阈值是什么?
      • 假设在数组中下标为3的位置已经存储了数据,当新增数据时通过哈希码得到的存储位置又是3,那么就会在该位置形成一个链表,当链表过长时就会转换成红黑树以提高执行效率,这个阈值就是链表转换成红黑树的最短链表长度;
    • 红黑树调整为链表的链表长度阈值是什么?
      • 当红黑树的元素个数小于该阈值时就会转换成链表。
    • 链表调整为红黑树的数组最小阈值是什么?
      • 并不是只要链表长度大于8就可以转换成红黑树,在前者条件成立的情况下,数组的容量必须大于等于64才会进行转换。
    • HashMap的数组table存储的就是一个个的Node<K,V>类型,很清晰地看到有一对键值,还有一个指向next的指针(以下只截取了部分源码):

      COPYstatic class Node<K,V> implements Map.Entry<K,V> {
            final K key;
            V value;
            Node<K,V> next;
        }
      

      之前的代码中在new对象时调用的是HashMap的无参构造方法,进入到该构造方法的源码查看一下:

      COPYpublic HashMap() {
            this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
        }
      

      发现没什么内容,只是赋值了一个默认加载因子;而在上文我们观察到源码中table和size都没有赋予初始值,说明刚创建的HashMap对象没有分配容量,并不拥有默认的16个空间大小,这样做的目的是为了节约空间,此时table为null,size为0。

      当我们往对象里添加元素时调用put方法:

      COPYpublic V put(K key, V value) {
            return putVal(hash(key), key, value, false, true);
        }
      

      put方法把key和value传给了putVal,同时还传入了一个hash(Key)所返回的值,这是一个产生哈希值的方法,再进入到putVal方法(部分源码):

      COPYfinal 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{
                //略
            }
        }
      

      这里面创建了一个tab数组和一个Node变量p,第一个if实际是判断table是否为空,而我们现在只关注刚创建HashMap对象时的状态,此时tab和table都为空,满足条件,执行内部代码,这条代码其实就是把resize()所返回的结果赋给tab,n就是tab的长度,resize顾名思义就是重新调整大小。查看resize()源码(部分):

      COPYfinal Node<K,V>[] resize() {
            Node<K,V>[] oldTab = table;
            int oldCap = (oldTab == null) ? 0 : oldTab.length;
            int oldThr = threshold;
            if (oldCap > 0);
            else if (oldThr > 0);
            else {               // zero initial threshold signifies using defaults
                newCap = DEFAULT_INITIAL_CAPACITY;
                newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
            } 
            @SuppressWarnings({"rawtypes","unchecked"})
            Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
            table = newTab;
            return newTab;
        }
      

      该方法首先把table及其长度赋值给oldTab和oldCap;threshold是阈值的意思,此时为0,所以前两个if先不管,最后else里newCap的值为默认初始化容量16;往下创建了一个newCap大小的数组并将其赋给了table,刚创建的HashMap对象就在这里获得了初始容量。然后我们再回到putVal方法,第二个if就是根据哈希码得到的tab中的一个位置是否为空,为空便直接添加元素,此时数组中无元素所以直接添加。至此HashMap对象就完成了第一个元素的添加。当添加的元素超过16*0.75=12时,就会进行扩容:

      COPYfinal V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict){
            if (++size > threshold)
                resize();
        }
      

      扩容的代码如下(部分):

      COPYfinal Node<K,V>[] resize() {
            int oldCap = (oldTab == null) ? 0 : oldTab.length;
            int newCap;
            if (oldCap > 0) {
                if (oldCap >= MAXIMUM_CAPACITY) {//略}
                else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                         oldCap >= DEFAULT_INITIAL_CAPACITY)
            }
        }
      

      核心部分是else if里的移位操作,也就是说每次扩容都是原来大小的两倍

    • *:额外说明的一点是在JDK1.8以前链表是头插入,JDK1.8以后链表是尾插入。

HashSet源码分析

了解完HashMap之后,再回过头来看之前的HashSet源码,为什么放在后面写你们看一下源码就知道了(部分):

COPYpublic class HashSet<E>
      extends AbstractSet<E>
      implements Set<E>, Cloneable, java.io.Serializable
  {
      private transient HashMap<E,Object> map;
      private static final Object PRESENT = new Object();
      public HashSet() {
          map = new HashMap<>();
      }
  }

可以看见HashSet的存储结构就是HashMap,那它的存储方式是怎样的呢?可以看一下add方法:

COPYpublic boolean add(E e) {
      return map.put(e, PRESENT)==null;
  }

很明了地发现它的add方法调用的就是map的put方法,把元素作为map的key传进去的。。

Hashtable
  • JDK1.0版本,线程安全,运行效率慢;不允许null作为key或是value。

  • 初始容量11,加载因子0.75。

    这个集合在开发过程中已经不用了,稍微了解即可。

Properties
  • Hashtable的子类,要求key和value都是String。通常用于配置文件的读取。

它继承了Hashtable的方法,与流关系密切,此处不详解。

TreeMap
  • 实现了SortedMap接口(是Map的子接口),可以对key自动排序。
/**
 * TreeMap的使用
 * 存储结构:红黑树
 */
public class Demo3 {
	public static void main(String[] args) {
		TreeMap<Student, Integer> treeMap=new TreeMap<Student, Integer>();
		Student s1=new Student("tang", 36);
		Student s2=new Student("yu", 101);
		Student s3=new Student("he", 10);
		//1.添加元素
		treeMap.put(s1, 21);
		treeMap.put(s2, 22);
		treeMap.put(s3, 21);
		//不能直接打印,需要实现Comparable接口,因为红黑树需要比较大小
		System.out.println(treeMap.toString());
		//2.删除元素
		treeMap.remove(new Student("he", 10));
		System.out.println(treeMap.toString());
		//3.遍历
		//3.1 使用keySet()
		for (Student key : treeMap.keySet()) {
			System.out.println(key+" "+treeMap.get(key));
		}
		//3.2 使用entrySet()
		for (Entry<Student, Integer> entry : treeMap.entrySet()) {
			System.out.println(entry.getKey()+" "+entry.getValue());
		}
		//4.判断
		System.out.println(treeMap.containsKey(s1));
		System.out.println(treeMap.isEmpty());		
	}
}

在学生类中实现Comparable接口:

COPYpublic class Student implements Comparable<Student>{
    @Override
    public int compareTo(Student o) {
        int n1=this.id-o.id;
        return n1;
}

除此之外还可以使用比较器来定制比较:

COPYTreeMap<Student, Integer> treeMap2=new TreeMap<Student, Integer>(new Comparator<Student>() {
    @Override
    public int compare(Student o1, Student o2) {
        // 略
        return 0;
    }			
});
TreeSet源码

和HashSet类似,放在TreeMap之后讲便一目了然(部分):

COPYpublic class TreeSet<E> extends AbstractSet<E>
    implements NavigableSet<E>, Cloneable, java.io.Serializable
{
    private transient NavigableMap<E,Object> m;
    private static final Object PRESENT = new Object();
    TreeSet(NavigableMap<E,Object> m) {
        this.m = m;
    }
    public TreeSet() {
        this(new TreeMap<E,Object>());
    }
}

TreeSet的存储结构实际上就是TreeMap,再来看其存储方式:

COPYpublic boolean add(E e) {
    return m.put(e, PRESENT)==null;
}

它的add方法调用的就是TreeMap的put方法,将元素作为key传入到存储结构中。

Collections工具类

  • 概念:集合工具类,定义了除了存取以外的集合常用方法。

  • 方法

    • public static void reverse(List<?> list)//反转集合中元素的顺序
    • public static void shuffle(List<?> list)//随机重置集合元素的顺序
    • public static void sort(List<T> list)//升序排序(元素类型必须实现Comparable接口)
    COPY/**
     * 演示Collections工具类的使用
     *
     */
    public class Demo4 {
    	public static void main(String[] args) {
    		List<Integer> list=new ArrayList<Integer>();
    		list.add(20);
    		list.add(10);
    		list.add(30);
    		list.add(90);
    		list.add(70);
    		
    		//sort排序
    		System.out.println(list.toString());
    		Collections.sort(list);
    		System.out.println(list.toString());
    		System.out.println("---------");
    		
    		//binarySearch二分查找
    		int i=Collections.binarySearch(list, 10);
    		System.out.println(i);
    		
    		//copy复制
    		List<Integer> list2=new ArrayList<Integer>();
    		for(int i1=0;i1<5;++i1) {
    			list2.add(0);
    		}
    		//该方法要求目标元素容量大于等于源目标
    		Collections.copy(list2, list);
    		System.out.println(list2.toString());
    		
    		//reserve反转
    		Collections.reverse(list2);
    		System.out.println(list2.toString());
    		
    		//shuffle 打乱
    		Collections.shuffle(list2);
    		System.out.println(list2.toString());
    		
    		//补充:list转成数组
    		Integer[] arr=list.toArray(new Integer[0]);
    		System.out.println(arr.length);
    		//补充:数组转成集合 
    		String[] nameStrings= {"tang","he","yu"};
    		//受限集合,不能添加和删除
    		List<String> list3=Arrays.asList(nameStrings);
    		System.out.println(list3);
    		
    		//注:基本类型转成集合时需要修改为包装类
    	}
    }
    

总结

集合的概率:对象的容器,和数组类似,定义了许多个对象进行操作的常用方法。

List集合:有序,有下标,元素可以重复。(ArrayList基于数组结构、查询块、增删慢、运行效率快、线程不安全。Vector 数组结构实现,查询快、增删慢;运行效率慢、线程安全。LinkedList链表结构实现,增删快,查询慢。)

Set集合:无序,无下标,元素不可以重复。(HashSet基于HashCode计算元素存放位置。当存入元素的哈希码相同时,会调用equals进行确认,如结果为true,则拒绝后者存入。TreeSet(和HashSet类似)基于平衡二叉树实现不重复。实现了SortedSet接口,对集合元素自动排序。元素对象的类型必须实现Comparable接口,指定排序规则。通过CompareTo方法确定是否为重复元素。)

Map集合:存储一对数据,无序,无下标,键不可重复,值可重复。(HashMap线程不安全,运行效率快;允许用null作为key或是value。存储结构:哈希表(数组+链表+红黑树)。TreeMap实现了SortedMap接口(是Map的子接口),可以对key自动排序。存储结构:红黑树)

Collections:集合工具类,定义了除存取以外的集合常用方法。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值