Java集合框架(复习笔记+重难点理解)_Super_Song_的博客-程序员宝宝

技术标签: java  Java基础  hashmap  数据结构  

一、集合概述

1.概念:

存储对象的容器,定义了对多个对象进项操作的的常用方法。可实现数组的功能

2.和数组的区别

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

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

基本类型数据装箱,转为引用类型也可以存集合里,集合类位于java.util包中

二、Collection体系集合

在这里插入图片描述

有序指添加顺序和遍历顺序一致,无序指添加顺序和遍历顺序不一定一致

1.Collection父接口

(1)特点

代表一组任意类型的对象,部分有序,部分无序

(2)方法

boolean add(Object obj) //添加一个对象。
boolean addAll(Collection c) //将一个集合中的所有对象添加到此集合中。
void clear() //清空此集合中的所有对象。
boolean contains(Object o) //检查此集合中是否包含o对象。
boolean equals(Object o) //比较此集合是否与指定对象相等。
boolean isEmpty() //判断此集合是否为空。
boolean remove(Object o) //在此集合中移除单个对象。
boolean removeAll(Collection<?> c)//移除里面的c集合
boolean retainAll(Collection<?> c)//保留其中的c集合,相当取交集
int size() //返回此集合中的元素个数。
Object[] toArray() //将此集合转换成数组
Iterator<E> iterator()//迭代器
遍历集合

1.增强for循环,普通for循环用不了,因为没有下标

for(Object object : collection){
    
	System.out.println(object);
}

2.迭代器Iterator接口,专门用来遍历集合

Iterator it = collection.iterator();

Iterator接口有三个方法,

boolean hasNext();//判断是否有下一个元素
next();//返回迭代的下一个元素
remove();//删除当前元素,也就是上一次next()返回的元素

因此迭代器遍历可以为

Iterator it = collection.iterator();
while(it.hasNext()) {
    
    Object object = it.next();
}
判断contains()

判断集合是否含有一个元素,默认判断的是地址值,所以如果有个与collection子元素同属性的对象,contains()判断是不包含的,如果需要同属性就算包含,可以重写子元素equals方法

(3)注意事项

1.迭代期间不可以使用collection的其他方法删除元素,否则会出现并发修改异常,如需移除只能使用迭代器方法remove()

2.collection增删一个对象元素时,实际上增删的是地址值,即使删除了,对象元素本身还存在

2.List接口

(1)特点

有序、有下标、元素可重复

(2)方法

除了Collection方法之外,还有涉及元素位置的方法

void add(int index,Object o) //在index位置插入对象o。
boolean addAll(index,Collection c) //将一个集合中的元素添加到此集合中的index位置。
Object get(int index) //返回集合中指定位置的元素。
List subList(int fromIndex,int toIndex) //返回fromIndex和toIndex之间的集合元素。
remove(int index);//根据下标删除元素
ListIterator<E> listIterator();//迭代器
ListIterator<E> listIterator(int index);//迭代器,从指定位置返回  
遍历集合

1.for循环

List list = new ArrayList();
for (int i=0;i<list.size();i++) {
    
	System.out.println(list.get(i));
}

2.增强for循环

for (Object object : list) {
    
	System.out.println(object);
}

3.Collection迭代器Iterator,方法同上

4.列表迭代器ListIterator,可以按任意方向遍历列表,添加、删除、修改

boolean hasNext();//是否有下一个元素
boolean hasPrevious();//逆向遍历,是否有下一个
Object next();//返回下一个元素
int nextIndex();//返回下个元素位置
Object previous();//返回前一个元素
int previousIndex();//返回前一个元素位置
remove();//移除列表中next或previous返回的最后一个元素
set(Object o);//替换next()或previous()返回的最后一个元素
//3.4使用列表迭代器,listIterator可以双向遍历,添加、删除及修改元素。
ListIterator listIterator = list.listIterator();
//从前往后
while (listIterator.hasNext()) {
    
	System.out.println(listIterator.next());		
}
//从后往前(此时“遍历指针”已经指向末尾)
while(listIterator.hasPrevious()) {
    
	System.out.println(listIterator.previous());
}
判断是否存在某元素
获取元素位置
自动装箱

集合元素不能为基本类型,add()添加基本类型数字元素时,底层会自动装箱转为引用类型

删除

添加元素

List list=new ArrayList();
//1.添加数字数据(自动装箱)
list.add(20);
list.add(30);
list.add(40);
list.add(50);

删除元素


list.remove(0);//用下标删除
//list.remove(20);默认为用下标删除,但是这样会数组下标越界
list.remove(Object(20));//删除指定元素,可以使用
list.remove(new Integer(20));//删除指定元素,可以使用

这里可以用new Integer删除,因为Integer有缓存,超过缓存范围就会报异常

subList

返回子集合,含头不含尾

2-1.ArrayList实现类

(1)特点

数组列表,内部维护着数组,查询快,增删慢,运行效率快,线程不安全

(2)方法

删除

remove通过对象删除子元素时,默认通过地址值判断,可以自己重写子元素的equals方法,只要属性相同还是同一个对象

遍历

Iterator ListIterator for循环 增强for循环

(3)源码分析

默认容量:private static final int DEFAULT_CAPACITY = 10;

实际的元素个数:private int size;

存放元素的数组:transient Object[] elementData;

空数组:private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

无参构造:返回空数组,没有添加任何元素时,容量0

public ArrayList() {
    
	this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

add()方法:

public boolean add(E e) {
    
	ensureCapacityInternal(size + 1); //准备好存放元素的位置
	elementData[size++] = e;//添加元素
	return true;
}

进入到ensureCapacityInternal()

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

进入到calculateCapacity

private static int calculateCapacity(Object[] elementData, int minCapacity) {
    
	if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
    
		return Math.max(DEFAULT_CAPACITY, minCapacity);
	}
	return minCapacity;
}

进入到ensureExplicitCapacity

private void ensureExplicitCapacity(int minCapacity) {
    
	modCount++;
	// overflow-conscious code
	if (minCapacity - elementData.length > 0)
		grow(minCapacity);
}

进入到grow

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

calculateCapacity,当我们添加第一个元素时,elemenData是当前的数组(目前为空),minCapacity是size+1(也就是1),也就是add添加之后所需的最小容量;

如果当前数组为空,则返回默认初始容量DEFAULT_CAPACITY也就是10,如果不为空,则返回我们需要的容量minCapacity

ensureExplicitCapacity,上面得出我们需要的数组容量为10或最小容量minCapacity,开始判断,如果所需的容量大于当前数组容量(当前空数组为0),则进行扩容grow

grow,先将oldCapacity变为oldCapacity + (oldCapacity >> 1),也就是容量变为1.5倍,与minCapacity比较,取大的值赋值给newCapacity,最后将原数组复制一个,容量为newCapacity

结论:

1.空数组初始容量为0

2.当空数组添加第一个元素时,初始容量为10,add添加元素且添加后容量小于等于10,数组的容量一直都是10

3.当添加元素后所需容量大于10以后,将原有容量变1.5倍,与所需的容量比较,取大的作为新的数组容量

结论很简单,重在理解源码的设计思想,严谨、简洁、高效

2-2.Vector实现类

(1)特点

数组结构实现,查询快,增删慢,运行效率略慢,线程安全

(2)方法

遍历:for 增强for 迭代器,其他方法基本相同

特色:枚举器

Vector vector = new Vector();
Enumeration en = vector.elements();
while (en.hasMoreElements()) {
    
	Object o = en.nextElement();
}

现在开发用的很少了

2-3.LinkedList实现类

(1)特点

双向链表结构实现,增删快,查询慢

每添加一个元素,前后两个元素就会互相指向,是个前后引用关系,增删元素其实是在改变引用关系

(2)方法

add()添加元素,remove()删除,clear()清除,for循环,增强forIterator迭代器,ListIterator迭代器,contains() isEmpty()判断

(3)源码分析

容量:transient int size = 0;

头结点:transient Node<E> first;

尾结点:transient Node<E> last;

add方法

public boolean add(E e) {
    
	linkLast(e);//其实是向尾结点添加元素
	return true;
}
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++;
}

进入Node类,有三个属性,当前元素,前一个元素,下一个元素

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

假设刚开始new了一个LinkedList对象,first和last属性都为空,调用add进入到linkLast方法。

首先创建一个Node变量 l 将last(此时为空)赋给它,然后new一个newNode变量存储数据,并且它的前驱指向l,后继指向null;再把last指向newNode。如下图所示:
在这里插入图片描述
如果满足if条件,说明这是添加的第一个结点,将first指向newNode:
在这里插入图片描述
至此,LinkedList对象的第一个数据添加完毕。假设需要再添加一个数据,我们可以再来走一遍,过程同上不再赘述,图示如下:
在这里插入图片描述

2-4.ArrayList和LinkedList区别

ArrayList:必须开辟连续空间,查询快,增删慢。

LinkedList:无需开辟连续空间,查询慢,增删快。
在这里插入图片描述

3.泛型

3-1.泛型概述

Java泛型是JDK1.5中引入的一个新特性,其本质是参数化类型,把类型作为参数传递

常见形式有泛型类、泛型接口、泛型方法。

语法:
<T,…> T称为类型占位符,表示一种引用类型。

好处:
提高代码的重用性。
防止类型转换异常,提高代码的安全性。

3-2 泛型类

/**
 * 泛型类
 * 语法:类名<T>
 * T是类型占位符,表示一种引用类型(只能是引用类型),编写多个使用逗号隔开
 */
public class myGeneric<T>{
    
	//1.创建泛型变量,T是引用类型,所以可以创建变量
	//不能使用new来创建,因为泛型是不确定的类型,也可能拥有私密的构造方法。
	T t;
	//2.泛型作为方法的参数
	public void show(T t) {
    
		System.out.println(t);
	}
	//泛型作为方法的返回值
	public T getT() {
    
		return t;
	}
}
/**
 * 注意:
 * 1.泛型只能使用引用类型
 * 2.不同泛型类型的对象不能相互赋值
 */
public class testGeneric {
    
	public static void main(String[] args) {
    
		//使用泛型类创建对象
		myGeneric<String> myGeneric1=new myGeneric<String>();//1.7以后,后面的<T>可以省略
		myGeneric1.t="tang";
		myGeneric1.show("he");
		
		myGeneric<Integer> myGeneric2=new myGeneric<Integer>();
		myGeneric2.t=10;
		myGeneric2.show(20);
		Integer integer=myGeneric2.getT();
	}
}

注意:

1.T是引用类型,所以可以创建变量

2.泛型变量不能使用new创建,因为无法保证T这个类型能使用构造,或者是否有无参构造

3.定义泛型类时我们使用T作为类型占位符,创建泛型类的实例对象时,必须要将T改成实际我们使用的类型

4.不同泛型类的对象之间不能相互赋值

3-3 泛型接口

/**
 * 泛型接口
 * 语法:接口名<T>
 * 注意:不能创建泛型静态常量,因为T不确定,没法实例化
 */
public interface MyInterface<T> {
    
    //创建常量
	String nameString="tang";
	//抽象方法
	T server(T t);
}

1.实现类可以在实现接口时就确定泛型,然后实现类内部的泛型也都随之改变

/**
 * 实现接口时确定泛型类
 */
public class MyInterfaceImpl implements MyInterface<String>{
    
	@Override
	public String server(String t) {
    
		System.out.println(t);
		return t; 
	}
}

2.实现类还不确定泛型类型,也加上<T>,变成泛型类,创建实例对象的时候再确定,

在这里实现类的<T>,会传递到泛型类的<T>

/**
 * 实现接口时不确定泛型类
 */
public class MyInterfaceImpl2<T> implements MyInterface<T>{
    
	@Override
	public T server(T t) {
    
		System.out.println(t);
		return t;
	}
}

3-4 泛型方法

定义普通的类,方法的返回值类型用<T>

/**
 * 泛型方法
 * 语法:<T> 返回类型
 */
public class MyGenericMethod {
    
	public <T> void show(T t) {
    
		System.out.println("泛型方法"+t);
	}
}
//测试
MyGenericMethod myGenericMethod=new MyGenericMethod();
myGenericMethod.show("tang");
myGenericMethod.show(200);
myGenericMethod.show(3.14);

注意:

1.泛型类中的泛型,适用于整个类,泛型方法中的泛型,只适用于方法内

2.泛型方法使用时,泛型<T>随着我们传递的参数类型而变化

泛型的好处

提高代码复用性:以往一个方法要想传多种参数,必须在类中定义方法重载,现在一个泛型解决

防止类型转化异常,提高代码安全性

注意:

Java源码中定义的类,如List有泛型约束,但是我们在实际使用时,不加泛型也能使用,这是因为不加默认泛型为<Object>

但是获取其中的元素也是Object类型,还要再类型转换,如果存在不同的类型,很容易出现错误,所以在使用泛型类时可以直接规范好

3-5 泛型集合

概念:

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

特点:

1.编译时即可检查,而非运行时抛出异常。

2.访问时,不必类型转换(拆箱)。

3.不同泛型之间引用不能相互赋值,泛型不存在多态。

之前我们在创建LinkedList类型对象的时候并没有使用泛型,但是进到它的源码中会发现

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable{
    //略}

它是一个泛型类,而我之前使用的时候并没有传递,说明java语法是允许的,这个时候传递的类型是Object类,虽然它是所有类的父类,可以存储任意的类型,但是在遍历、获取元素时需要原来的类型就要进行强制转换。这个时候就会出现一些问题,假如往链表里存储了许多不同类型的数据,在强转的时候就要判断每一个原来的类型,这样就很容易出现错误。

4.Set接口

(1)特点

无序、无下标、元素不可重复。

(2)方法

全部继承自Collection中的方法。添加、删除、遍历、判断

添加顺序和打印顺序可能不一致,重复元素添加不起作用,

不能使用普通for,循环可以使用增强for,迭代器Iterator

4-1 HashSet【重点】

(1)特点

基于HashCode计算元素存放位置。

当存入元素的哈希码相同时,会调用equals进行确认,如结果为true,则拒绝后者存入

存储结构:哈希表(数组+链表+红黑树)

(2)理解

我们可以结合火车站买票理解,火车站有5个售票窗口构成一个数组,每个窗口前排了几个人,每个窗口人又形成了链表

再有人来买票,选择哪个窗口?hashcode每个引用对象都有,所以根据hashcode计算决定到哪个窗口,如果窗口没有人,直接存放,如果有人,通过equals比较是否相同,相同则拒绝存放,不同则在后面排队,形成单向链表

(3)方法

add添加,remove删除,增强for遍历,Iterator迭代器,contains判断,isEmpty判断,等用法同上

(4)存储过程

1.根据hashCode计算保存的位置,如果位置为空,则直接保存,如果不为空则执行第二步。

2.执行equals方法判断是否相同,如果方法返回true,则认为是重复,拒绝存储,如果不同,则存入形成链表。

3.这样也有问题,如果添加的是一个相同属性的新对象,也能存入,如果不想存入相同属性的对象,则应该重写equals方法,属性相同就可以返回true,这样hashSet不会重复存储

4.存储过程也是一个判断是否重复的过程

5.重写hashcode方法、toString方法比较常用,可以使用IDE直接生成

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

31是一个质数,这样的数字在计算时可以尽量减少散列冲突。

可以提高执行效率,因为31*i=(i<<5)-i,31乘以一个数可以转换成移位操作,这样能快一点;但是也有网上一些人对这两点提出质疑。

4-2 TreeSet

红黑树,原理自行查询

(1)特点

基于排序顺序实现不重复。

本身无序,但实现了SortedSet接口,对集合元素自动排序。

元素对象的类型必须实现Comparable接口,指定排序规则。元素存放红黑树时,必须要比较大小,如何定义比较的规则?实现Comparable接口即可,这样元素才能存放HashSet

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

(2)使用

添加、删除、循环、判断,方法同上

实现Comparable接口

Person对象要想存入TreeSet集合中,Persion必须实现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;
	}
}

所以当compareTo方法的返回值为0,我们认为是重复元素,只能添加一个,

Person实现Comparable接口后,TreeSetperson对象进行添加、删除、判断时都会先判断是否和已有的person对象相同,这个判断依据就是上面我们重写compareTo方法,返回0则相同,不为0则不同

不用实现Comparable

将要存放的元素也可以不用实现Comparable接口,TreeSet提供了一个带比较器Comparator的构造方法,通过匿名内部类实现定制比较,比较规则我们自己来定

TreeSet<Person> persons=new TreeSet<Person>(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;
	}			
});
		

定义好比较规则,添加元素,打印集合,集合就会按照我们制定规则进行排序输出

三、Map体系集合

在这里插入图片描述

1.Map父接口

(1)特点

1.用于存储任意键值对(Key-Value)。

2.键:无序、无下标、不允许重复(唯一)。

3.值:无序、无下标、允许重复。

(2)方法

boolean containsKey(Object key);//判断是否包含指定键的映射关系
boolean containsValue(Object value);//判断是否有一个或多个指定值的映射关系
Set<Map.Entry<K,V>> entrySet();//返回所有键值对,重点
V get(Object key);//根据key返回对应value,如果没有额返回null
boolean isEmpty();//判断是否为空
Set<K> keySet();//返回所有key
V put(K key, V value);//存入键值对
void putAll(Map<? extends K,? extends V> m);//存入一个map集合
V remove(Object key);//根据key移除键值对
int size();//返回包含的元素个数
Collection<V> values();//返回所有value的集合
/**
 * Map接口的使用
 * 特点:1.存储键值对 2.键不能重复,值可以重复,如果key重复,新的value会替换旧的value 3.无序
 */
public class Demo1 {
    
	public static void main(String[] args) {
    
		Map<String,Integer> map=new HashMap<String, Integer>();
		//1.添加元素
		map.put("a", 1);
		map.put("b", 2);
		map.put("c", 3);
		System.out.println(map.toString());
		//2.删除元素
		map.remove("a");
		System.out.println(map.toString());
		//3.遍历
		//3.1 使用keySet()得到所有key,通过key得到所有value
		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());
		}
	}
}
注意

1.添加元素时,如果K重复,则新的V会取代旧的V

2.Set<K> keySet()使用Set<K>集合接收,因为K不可重复

3.Collection<V> values()使用Collection<V>集合接收,因为V可能有重复

4.Entry<K,V>Map<K,V>接口里面的一个内部接口,所以定义类型时要用Map.Entry<K,V>

5.Set<Map.Entry<K,V>> entrySet()返回的一个Set集合,每个元素的类型为Map.Entry<K,V>

2.HashMap实现类【重点】

(1)特点

1.JDK1.2版本出现,线程不安全,运行效率快;

2.允许用null作为key或是value。

3.存储结构:哈希表(数组+链表+红黑树)

(2)方法

构造方法,默认初始容量16,默认加载因子0.75

添加、删除、遍历、判断同Map

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

(3)源码分析

属性
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;//加载因子0.75
static final int TREEIFY_THRESHOLD = 8;//jdk1.8出现,
static final int UNTREEIFY_THRESHOLD = 6;
static final int MIN_TREEIFY_CAPACITY = 64;
transient Node<K,V>[] table;//哈希表中的数组
size;元素个数

元素存入数组时,通过hashcode计算找到存放位置,如果没有元素,直接存入,如果已经元素,比较equals,如果相同则不再存放,同时替换掉values,如果equals判断不同,则存入元素,形成链表

static final int TREEIFY_THRESHOLD = 8
static final int UNTREEIFY_THRESHOLD = 6;
static final int MIN_TREEIFY_CAPACITY = 64;
指当链表长度大于8时,且数组长度大于等于64,这个链表就会变为红黑树,红黑树的查询效率很高
当红黑树长度小于6时,又会变回链表

我们向hashMap中添加的一个个元素就是Node<K,V>,里面有指向,也可以形成链表

transient Node<K,V>[] table;就是我们创建的数组链表

构造方法
public HashMap() {
    
	this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}

hashMap刚创建对象时,默认元素为空,table=null;size=0;,因为源码中没有给初始值,这么做是为了节省空间

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

进入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;
	if ((tab = table) == null || (n = tab.length) == 0)
		n = (tab = resize()).length;
	if ((p = tab[i = (n - 1) & hash]) == null)
		tab[i] = newNode(hash, key, value, null);
	else{
    
	//略
	}
}

Node<K,V>[] tab; Node<K,V> p; int n, i; 先定义数组,元素,n

当初hashMap为空,tab为空,调用resize()方法,返回值给了tab,求tab的长度赋值给n

进入resize方法,我们只看重点代码

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

tab为空,则oldTab为空,所以oldCap为0,当oldCap容量大于0,就会返回一个新的容量newCap也就是DEFAULT_INITIAL_CAPACITY默认初始容量16

得到的新数组newTab,赋值给table,所以回到putVal中,

n = (tab = resize()).length; 此时n = (tab = resize()).length;中,tab数组目前没有元素,容量为16,

tab[i] = newNode(hash, key, value, null); 将元素添加到数组中

当元素长度大于16,又会调用resize方法,

总结:

1.HashMap钢创建时,table是null,当添加第一个元素时,table容量为16

2.当元素个数大于阈值16*0.75=12,会进行扩容,扩容后的大小为原来的2倍,目的是减少调整元素的个数

3.jdk1.8,当链表长度大于8,且数组元素个数大于等于64时,会调整为红黑树,提高查询效率

4.jdk1.8当链表长度小于6时,调整为链表

5.jdk1.8之前,链表时头插入,jdk1.8以后时是尾插入

HashMap与HashSet的关系

查看HashSet源码,

public HashSet() {
    
	map = new HashMap<>();
}
public boolean add(E e) {
    
	return map.put(e, PRESENT)==null;
}

可以看到,HashSet就是HashMapHashset使用HashMap中的K来保存元素

3.HashTable

(1)特点

DK1.0版本,线程安全,运行效率慢;不允许null作为key或是value。
初始容量11,加载因子0.75。

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

Properties子类

(1)特点

Hashtable的子类,要求key和value都是String。通常用于配置文件的读取,与流关系密切。它继承了Hashtable的方法,与流关系密切,此处不详解。

TreeMap实现类

(1)特点

实现了SortedMap接口(是Map的子接口),可以对key自动排序,也是红黑树

(2)方法

构造方法

与TreeSet类似,添加元素时,需要元素实现Comparable接口,定义比较的规则,否则直接添加会报异常

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

也可以直接利用TreeMap的构造器自带的比较器定制比较创建对象

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

使用keySetentrySet,与Map相同

判断

TreeMap与TreeSet的关系

查看TreeSet源码

private transient NavigableMap<E,Object> m;

TreeSet(NavigableMap<E,Object> m) {
    
	this.m = m;
}

public TreeSet() {
    
	this(new TreeMap<E,Object>());
}

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

所以TreeSet就是使用TreeMap创建的,add添加数据时,用的TreeMapK来保存的TreeSet的数据

四、Collections工具类

(1)方法

public static void reverse(List<?> list)//反转集合中元素的顺序
ublic static void shuffle(List<?> list)//随机重置集合元素的顺序
public static void sort(List<T> list)//升序排序(元素类型必须实现Comparable接口)

/**
 * 演示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);
		}
		//该方法要求目标元素容量大于等于源目标,所以上面将list2的长度扩充为5
		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);
		
		//注:基本类型转成集合时需要修改为包装类
		Integer[] nums = {
    1,2,3};
		//如果这个数组是int类型,asList将提醒将List泛型变为int[],因为泛型不支持基本类,所以定义数组时就要用包装类
		List<Integer> list4 = Array.asList(nums);
	}
}
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_47257749/article/details/112914741

智能推荐

vue项目打包上传至服务器_风如也的博客-程序员宝宝

一种是使用 vue + webpack 自己搭建的vue项目,在项目打包时,直接在项目目录下运行 webpack 即可打包完成。打包完成后在项目的 dist 文件夹下就是打包好了的文件。然后将这两个文件使用 WinScp 上传至云服务器相应的文件夹(二级域名对应的文件夹)中,重启项目即可。另一种使用 vue-cli 搭建的项目,打包语法 npm run build...

cas添加验证码以及默认错误几次以后才出现验证码_丶roc的博客-程序员宝宝_cas输入错误三次 出现验证码

趁着周末加班,抽点时间完善下关于cas服务端的改造,其实过了好久,有些东西不看也想不起来了,当然自己做过的东西熟悉起来那也是相当的快的,废话不多说进入正题获取验证码,其实就是后端返回的一张图片,首先定义获取图片的控制器public class CaptchaImageCreateController implements Controller,InitializingB

PDF转换技巧:在线PDF转PPT_cuimei2582的博客-程序员宝宝

PDF文件作为一种通用的文件格式,有经验的小伙伴习惯将PPT文档转变为PDF格式,这样可以更加完美地呈现作品。但有时候为了方便修改,我们又需要PDF转PPT的操作。所以今天小编就教大家一个高效快速的...

webpack之『使用横幅 Plugin』_莫余的博客-程序员宝宝

认识pluginplugin是什么?plugin是插件的意思,通常是用于对某个现有的架构进行扩展。webpack中的插件,就是对webpack现有功能的各种扩展,比如打包优化,文件压缩等等。loader和plugin区别loader主要用于转换某些类型的模块,它是一个转换器。plugin是插件,它是对webpack本身的扩展,是一个扩展器。plugin的使用过程:步骤一:通过npm安装需要使用的plugins(某些webpack已经内置的插件不需要安装)步骤二:在webpack.confi

fyzf_旧城古人的博客-程序员宝宝

目录1、接口说明2、首页目录菜单接口3、综治概况接口4、首页目录菜单数字接接口5、首页新闻标题接口6、新闻列表数据1、接口说明{ "data":object, "code":"0", "msg":"操作成功!", "count":10000}所有接口都是上面格式,其中 data是Object类型,可以为集合、对象、和字符串等, count默认为0,当有分页时给count赋值, ...

随便推点

Beyond Compare4过期解决方法-亲测可行_橙子pipi的博客-程序员宝宝

修改注册表1、在搜索栏中输入 regedit,打开注册表2、删除项目CacheId : HKEY_CURRENT_USER\Software\Scooter Software\Beyond Compare 4\CacheId

游戏服务器应该为每个用户开辟一个线程还是一个进程,为什么?_N1314N的博客-程序员宝宝

参考回答:游戏服务器应该为每个用户开辟一个进程。因为同一进程间的线程会相互影响,一个线程死掉会影响其他线程,从而导致进程崩溃。因此为了保证不同用户之间不会相互影响,应该为每个用户开辟一个进程...

贪心算法之活动安排电视节目收看等_qiusi0225的博客-程序员宝宝

使用贪心算法安排电视节目收看,求最大。它和以前学过的活动安排一样的。#include #includeusing namespace std;struct show{ int startTime; int endTime; bool operator <(const show &a)const{ return endTime<

N42-WeekFour_Fluence_的博客-程序员宝宝_work ou't

1、统计出/etc/passwd文件中其默认shell为非/sbin/nologin的用户个数,并将用户都显示出来grep实现:打印用户个数:~]# grep -v '/sbin/nologin$' /etc/passwd | wc -l显示每个用户名:~]# grep -v '/sbin/nologin$' /etc/passwd | cut -d':' -f 1awk实现:打印...

HDFVIEW3.1.2下载_安安爸Chris的博客-程序员宝宝_hdfview下载

版本如下各个版本请直接下载即可centoshttps://hdf-wordpress-1.s3.amazonaws.com/wp-content/uploads/manual/HDFView/3.1.2/HDFView-3.1.2-centos7_64.tar.gzOSXhttps://hdf-wordpress-1.s3.amazonaws.com/wp-content/uploads/manual/HDFView/3.1.2/HDFView-3.1.2-osx1014_64.tar.g

Android Toast提示UI框架_唐衡三的博客-程序员宝宝_android toast框架

Android 好用的框架与UI效果demo收集android Toast 可以统一取消封装1.Toastyhttps://github.com/hss01248/Toasty

推荐文章

热门文章

相关标签