Bootstrap

Map接口下的集合,Colletions集合工具类,ArrayList和LinkedList底层

Day17

一、初识Map

Map不能直接遍历-没有获取迭代器的功能。

clear()-清空

cotainsKey(),containsValue()-是否包含键/值,返回布尔值

entrySet() - 返回此地图中包含的映射的Set集合

get(key)-通过键获取值

二、HashMap

知识点:HashMap的使用

HashMap<String,Integer> map = new HashMap<>();

map.put(key,value) - 如果没有该映射则添加,此时返回的是null;如果有key就替换value,返回被替换的值。(注意:返回的值是包装类)

map.replace(k,v)-替换,如果有key就替换value返回被替换的值,没有key就返回null。

map.replace(k,oV,nV) - 通过key+value替换。

HashMap newMap = new HashMap<>();

map.putAll(newMap);-将newMap中的所有映射添加到map集合中。

map.putIfAbsent(k,v)-如果集合中有此key,则返回value,没有则添加并返回null。

map.get(k)-通过键返回值,如果不存在则返回null。

map.getOrDefault(k,v)-通过键返回值,如果不存在则返回默认值。

map.containsKey, map.containsValue, map.isEmpty和map中功能一致,均返回布尔值。

map.remove(k) - 根据key删除元素,返回被删除的value。

map.remove(k, v) - 根据key和value删除元素,成功返回true,失败返回false。

map.size() - 获取元素个数。

map.values() - 后去map集合中所有的value,返回的是value类型的集合。

map.keySet() - 用于遍历集合,获取map集合中所有的key,并返回一个set集合。

遍历思路1:遍历思路:keySet()将Map中所有的key获取出,放在Set集合中,遍历Set集合依次获取key,利用map.get(key)获取对应的value。

Set<String> keySet=map.keySet();
for(String key:keySet){
    Integer value = map.get(key);//注意这里是从HashMap中获取的值。
    System.out.print(key + "---"+value);
}

entrySet() - 用于遍历集合,将map集合中的所有映射关系获取出,以Entry<k,v>的类型(映射关系)放在Set中。

遍历思路2:entrySet()将Map中所有的映射关系对象获取出,放在Set集合中,遍历Set集合依次遍历出映射关系对象,映射关系对象中包含了key和value

Set<Entry<String Integer>> entrySet = map.entrySet();
for(Entry<String,Integer> entry: entrySet){
    String key = entry.getKey();
    Integer value = entry.getValue();//注意这里是从集合entrySet中获取的元素,每个元素是一个Map.Entry<k,v>对象,再获取键和值。
    System.out.println(key + " -- " + value);
}

知识点:HashMap的特点

特点:无序 且 key去重(唯一)

知识点:HashMap的面试题

需求:给HashMap的value排序

思路:HashMap 获取映射关系对象的Set集合 -> ArrayList对象 -> list.sort(外置比较器)

public static void main(String[] args) {
		
		HashMap<String,Integer> map = new HashMap<>;
		
		map.put("生希", 28);
		map.put("名空", 23);
		map.put("菜丽", 29);
		map.put("桐光", 21);
		map.put("小康", 21);
		map.put("岛玲", 28);
		
		//获取映射关系对象的集合
		Set<Entry<String,Integer>> entrySet = map.entrySet();
		
		//将Set集合转换为ArraryList集合
		ArrayList<Entry<String,Integer>> list = new ArrayList<>(entrySet);//调用了ArrayList类的一个有参构造,接受一个 Collection 类型的参数,用于将该集合中的元素添加到新创建的 ArrayList 中
		
		//排序
		list.sort(new Comparator<Entry<String,Integer>>() {
			@Override
			public int compare(Entry<String, Integer> o1, Entry<String, Integer> o2) {
				return o1.getValue() - o2.getValue();
			}
		});
		
		for (Entry<String, Integer> entry : list) {
			String key = entry.getKey();
			Integer value = entry.getValue();
			System.out.println(key + "---" + value);
			System.out.println(entry);
		}
		
		
	}

三、LinkedHashMap,HashTable,ConcurrenthashMap

使用

方法和HashMap一致。

特点

LinkedHashMap的特点:

​ 继承关系:class LinkedHashMap<K,V> extends HashMap

​ 注意:LinkedHashMap在HashMap的基础上添加了双向链表

​ 特点:有序且Key去重

Hashtable的特点:

​ 特点:无序且key去重 + 线程安全(方法上加锁)(目前已弃用)

ConcurrentHashMap的特点:
特点:无序且key去重 + 线程安全(局部加锁)

比较

知识点:HashMap vs LinkedHashMap vs Hashtable vs ConcurrentHashMap

特点的区别:
HashMap:无序且去重
LinkedHashMap:有序且去重
Hashtable:无序且去重 + 线程安全(方法上加锁,已弃用)
ConcurrentHashMap:无序且去重 + 线程安全(局部加锁+CAS,效率更高)

存储null键null值的区别:
HashMap:ok
LinkedHashMap:ok
Hashtable:no
ConcurrentHashMap:no

四、TreeMap

使用

和HashMap使用一致。

特点

特点:针对于key进行自然排序

内置比较器

public static void main(String[] args) {
		
		TreeMap<Student, String> map = new TreeMap<>();
		            
		map.put(new Student("桐光", '女', 30, "2402", "004"),"跳舞");            
		map.put(new Student("岛玲", '女', 18, "2402", "005"),"品茗");            
		map.put(new Student("井步", '女', 19, "2402", "006"),"对弈");            
		Set<Entry<Student,String>> entrySet = map.entrySet();
		for (Entry<Student, String> entry : entrySet) {
			System.out.println(entry);
		}
	}
package com.qf.treemap_class;

public class Student implements Comparable<Student>{

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

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public char getSex() {
		return sex;
	}

	public void setSex(char sex) {
		this.sex = sex;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public String getClassId() {
		return classId;
	}

	public void setClassId(String classId) {
		this.classId = classId;
	}

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}
	
	@Override
	public boolean equals(Object obj) {
		if(this == obj){
			return true;
		}
		
		if(obj instanceof Student){
			Student stu = (Student) obj;
			if(classId.equals(stu.classId) && id.equals(stu.id)){
				return true;
			}
			
		}
		return false;
	}

	@Override
	public String toString() {
		return name + "\t" + sex + "\t" + age + "\t" + classId + "\t" + id;
	}

	//排序规则:按照年龄排序
	@Override
	public int compareTo(Student o) {
		return this.age - o.age;
	}
}

外置比较器

public static void main(String[] args) {
		
		TreeMap<Student, String> map = new TreeMap<>(new Comparator<Student>() {
			//排序规则:按照名字长度排序,名字长度一致按照年龄排序
			@Override
			public int compare(Student o1, Student o2) {
				if(o1.equals(o2)){
					return 0;
				}
				
				int nameLen1 = o1.getName().length();
				int nameLen2 = o2.getName().length();
				if(nameLen1 != nameLen2){
					return nameLen1 - nameLen2;
				}
				
				int age1 = o1.getAge();
				int age2 = o2.getAge();
				if(age1 != age2){
					return age1 - age2;
				}
				return 1;
			}
		});
		
		map.put(new Student("桐光", '女', 30, "2402", "004"),"跳舞");            
		map.put(new Student("岛玲", '女', 18, "2402", "005"),"品茗");            
		map.put(new Student("井步", '女', 19, "2402", "006"),"对弈");        
		
		Set<Entry<Student,String>> entrySet = map.entrySet();
		for (Entry<Student, String> entry : entrySet) {
			System.out.println(entry);
		}
	}

五、Properties

知识点:Properties

public static void main(String[] args) throws IOException {
		
		//配置文件对象
		Properties properties = new Properties();
		
		//将配置文件加载到对象中
    	properties.load(Test01.class.getClassLoader().getResourceAsStream("DBConfig.properties"));
		
		//获取配置文件里的数据
		String username = properties.getProperty("username");
		String password = properties.getProperty("password");
		System.out.println(username + " -- " + password);
	}

六、Collections集合工具类

public static void main(String[] args) {
		
		ArrayList<Integer> list = new ArrayList<>();
		
		//批量添加
		Collections.addAll(list, 5,8,3,4,1,2,7,9,6);
		
		//排序 -- 内置比较器(按照元素所属类的排序规则)
		Collections.sort(list);
		
		//注意:查找之前必须先排序
		int index = Collections.binarySearch(list, 3);
		System.out.println("获取元素的下标:" + index);
		
		//排序 -- 外置比较器
		Collections.sort(list, new Comparator<Integer>() {
			@Override
			public int compare(Integer o1, Integer o2) {
				return o2-o1;
			}
		});
		
		Integer max = Collections.max(list);
		System.out.println("最大值:" + max);
		
		Integer min = Collections.min(list);
		System.out.println("最小值:" + min);
		
		//替换所有元素
		Collections.fill(list, 888);
		
		//获取线程安全的List集合
		List<Integer> synchronizedList = Collections.synchronizedList(list);
		
		//[888, 888, 888, 888, 888, 888, 888, 888, 888]
		System.out.println(Arrays.toString(synchronizedList.toArray()));
	}

七、EnumMap 与EnumSet

//信号灯
public enum Signal {

	RED, YELLOW, GREEN;
}

知识点:EnumSet – 可以存储枚举的Set集合

public static void main(String[] args) {
		
		//Set集合中存储了Signal的对象
		EnumSet<Signal> set = EnumSet.allOf(Signal.class);
		
		set.remove(Signal.RED);
		
		Iterator<Signal> it = set.iterator();
		while(it.hasNext()){
			Signal next = it.next();
			System.out.println(next);
		}
	}

知识点:EnumMap – 可以存储枚举的Map集合

注意:将枚举对象存储在key的位置

public class Test01 {
	/**
	 * 知识点:EnumMap -- 可以存储枚举的Map集合
	 * 注意:将枚举对象存储在key的位置
	 */
	public static void main(String[] args) {
		
		EnumMap<Signal,String> map = new EnumMap<>(Signal.class);
		map.put(Signal.RED, "红灯");
		map.put(Signal.YELLOW, "黄灯");
		map.put(Signal.GREEN, "绿灯");
		Set<Entry<Signal,String>> set = map.entrySet();
		for(Entry<Signal,String> entry:set){
			System.out.println(entry);
			System.out.println(entry.getKey()+"---"+entry.getValue());
		}
	}

解读:

  1. EnumSet:由于枚举类型的元素是有限的且连续的,因此可以使用位向量来高效表示集合中的元素。这种实现方式使得 EnumSet 在性能和内存消耗方面都非常高效。因此,设计者认为直接提供静态工厂方法来创建 EnumSet 对象更加简洁和高效。

    EnumSet.of(o1, o2, o3, ...):创建包含指定枚举常量的 EnumSet 实例。

    EnumSet.allOf(O.class):创建一个包含指定枚举类型的所有枚举常量的 EnumSet 实例。

    EnumSet.noneOf(O.class):创建一个空的 EnumSet 实例。

    EnumSet.range(o1, o3):创建一个从 o1 到 o3(包括 o1 和 o3)范围内的 EnumSet 实例。

  2. EnumMap:枚举类型作为键时,并不像枚举类型作为集合元素那样具有连续性和有限性。因此,EnumMap 的实现使用了数组来存储映射关系,因此可以直接提供公共的构造方法来创建 EnumMap 对象。

八、ArrayList底层源码

场景:

public static void main(String[] args) {
		
		ArrayList<String> list = new ArrayList<>();
		
		//添加数据
		list.add("宇");
		list.add("蒲");
		list.add("小康");
}

源码:

public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
    //外部操作数(记录添加和删除的次数)
	protected transient int modCount = 0;//3
}
public class ArrayList<E> extends AbstractList<E> implements List<E>{
	//默认容量
    private static final int DEFAULT_CAPACITY = 10;
    //空内容的数组
    private static final Object[] EMPTY_ELEMENTDATA = {};
	//默认容量的空内容的数组
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    //最大的容器容量
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    //数据容器 - [宇,蒲,小康,null,null,null,null,null,null,null]
    transient Object[] elementData;
    //元素个数
    private int size;//3
    
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    
    //initialCapacity - 
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+  initialCapacity);
        }
    }
    
    //e - 小康
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);
        elementData[size++] = e;
        return true;
    }
    
    //minCapacity - 1
    private void ensureCapacityInternal(int minCapacity) {
        //使用无参构造创建ArrayList对象,第一次添加数据时进入的判断
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            //minCapacity = Math.max(10,1); --> minCapacity = 10
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }
    
    //minCapacity - 11
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // 有溢出意识的代码
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);//扩容
    }
    
    //minCapacity - 11
    private void grow(int minCapacity) {
        // oldCapacity - 10
        int oldCapacity = elementData.length;
        // newCapacity = 15
        int newCapacity = oldCapacity + (oldCapacity >> 1);//扩容机制:1.5倍
        if (newCapacity - minCapacity < 0)
            //newCapacity - 10
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
       
        // 扩容
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
    
    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) 
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?Integer.MAX_VALUE :MAX_ARRAY_SIZE;
    }
}

几个面试题:

ArrayList底层数据结构是什么?

Object类型的一维数组

ArrayList默认初始化长度是多少?

10

ArrayList如何减少容器的伸缩性?

使用有参构造定义容量

ArrayList数组最大容量是多少?

Integer.MAX_VALUE-8

ArrayList数组最大容量为什么是Integer.MAX_VALUE-8?或 ArrayList数组最大容量为什么要减8?

减8是为了腾出空间存放数组的头部信息

ArrayList的扩容机制是什么?

是原来数组长度的1.5倍

九、LinkedList底层源码

场景:

public static void main(String[] args) {
		
		ArrayList<String> list = new ArrayList<>();
		
		//添加数据
		list.add("宇");
		list.add("蒲");
		list.add("小康");
}

源码:

public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
    //外部操作数
    protected transient int modCount = 0;
}
public abstract class AbstractSequentialList<E> extends AbstractList<E> {
}
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>{
    //元素个数
    transient int size = 0;//0
    //第一个节点
    transient Node<E> first;//null
    //最后一个节点
    transient Node<E> last;//null
    
    public LinkedList() {
    }
    
    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++;
    }
    
    //节点类
    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;
        }
    }
}

几个面试题:

LinkedList的底层数据结构是什么?

双向链表

ArrayList 和 LinkedList效率的区别?

ArrayList:一维数组

LinkedList:双向链表

添加功能 - 不扩容:ArrayList快

添加功能 - 扩容:LinkedList快

删除功能:LinkedList快

修改功能:ArrayList快

查询功能:ArrayList快

一般项目中使用ArrayList居多,因为业务流程里查询业务是最多的。

练习:

1.理解LinkedList是如何删除元素(底层)

E unlink(Node<E> x) {
    final E element = x.item;
    final Node<E> next = x.next;
    final Node<E> prev = x.prev;

    if (prev == null) {
        first = next;
    } else {
        prev.next = next;
        x.prev = null;
    }

    if (next == null) {
        last = prev;
    } else {
        next.prev = prev;
        x.next = null;
    }

    x.item = null;
    size--;
    modCount++;
    return element;
}

2.掌握双向链表和单向链表的代码

/**
	双向链表有first节点和last节点
*/
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++;
/**
	单向链表中只有first节点或head节点
*/
void linkLast(E e) {
    	final Node<E> newNode = new Node<>(e);
    	if (first == null) {
        	first = newNode;
    	} else {
       	final Node<E> current = first;
        	while (current.next != null) {
           	current = current.next;
       	 }
        	current.next = newNode;
    	}
    	size++;
        modCount++;
    }

;