容器和HashMap底层 Iterator

容器

左边是一个一个往里扔,右边是一对一对往里扔
这里写图片描述

容器这一章,很多容器的底层实现还是数组。

List常用方法

List是有序可重复,,,重复的都可以放进去,什么叫重复。就是两个元素互相equals。。

/**
 * 测试List中的基本方法
 * ArrayList顾名思义:底层是基于数组来实现的。
 *
 */
public class Test01 {

	public static void main(String[] args) {
		List list = new ArrayList();
		//ArrayList:底层实现时数组,线程不安全,效率高。所以,查询快。修改、插入、删除慢。
		//LinkedList:底层实现是链表,线程不安全,效率高。所以,查询慢。修改、插入、删除快。
		//Vector:线程安全的,效率低。
		
		//关于线程。如果这些定义成局部变量那就和线程没什么关系了,所以大部分还是可用的。成员变量才有关系,还记得书上写的吗。
		//怎么记? 数组就是查询快,有下标嘛。数组是查询最快的
		
		list.add("aaa");
		list.add("aaa");
		list.add(new Date());
		list.add(new Dog());
		list.add(1234);  //包装类的:自动装箱!
		list.remove(new String("aaa"));  --- 
		System.out.println(list.size());  -- 注意这个size和内部数组的长度不一样,size一定是<=数组长度。
		for(int i=0;i<list.size();i++){
			System.out.println(list.get(i)); 
		}
		
		list.set(3, new String("3333"));
		list.add(4, new String("3333"));
/*		System.out.println(list.isEmpty());
		list.remove(new Dog());      //这里和hashcode和equals 
		System.out.println(list.size());
		
		List list2 = new ArrayList();
		list2.add("bbb");
		list2.add("ccc");
		
		list.add(list2);
		
		//跟顺序的操作。list是有序。 可以重复就有序嘛,哈哈。。
		String str = (String) list.get(0);  返回是object
		System.out.println(str); 
		list.set(1, "ababa"); //指定某个位置往里放
		list.remove(0);*/     //删除某个位置
		
 	}

}

class Dog {
	
}
ArrayList自己实现
/**
 * 自己实现一个ArrayList,帮助我们更好的理解ArrayList类的底层结构!
 
 *ArrayList底层是数组,可以支持数组扩容,
 * 传了索引就要查看索引是否越界rangeCheck,增加add就要检查扩容问题。
 */
public class SxtArrayList /*implements List*/ {
	
	private Object[] elementData;  //容器里面的对象常常称之为元素
	private int size;  
	
	
	public int size(){ //面向对象封装的思想,不能直接使用private变量
		return size;
	}
	
	public boolean isEmpty(){
		return size==0;
	}
	
	public SxtArrayList(){
		this(10);
	}
	
	public SxtArrayList(int initialCapacity){
		if(initialCapacity<0){
			try {
				throw new Exception();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		elementData = new Object[initialCapacity];
	}
	
	public void add(Object obj){
		//数组扩容和数据的拷贝
		if(size==elementData.length){
			Object[] newArray = new Object[size*2+1];
			System.arraycopy(elementData, 0, newArray, 0, elementData.length);//参数:原数组,从哪开始拷,目标数组,从哪开始拷,拷几个
//			for(int i=0;i<elementData.length;i++){  //和上面效果是一样的
//				newArray[i] = elementData[i];
//			}
			elementData = newArray; //扩容后把老的数组替换
		}
		
		elementData[size++]=obj; 
//		size++;
	}
	
	public Object get(int index){  //get不会扩容
		rangeCheck(index);
		
		return elementData[index];
	}
	
	public void remove(int index){
		rangeCheck(index);
		//删除指定位置的对象
		//a b d e
		int numMoved = size - index - 1;   (可以这么记size-1-index)
		if (numMoved > 0){
			System.arraycopy(elementData, index+1, elementData, index,
					numMoved);
		}
		elementData[--size] = null; // Let gc do its work,让最后一个元素为空
	}

	public void remove(Object obj){
		for(int i=0;i<size;i++){
			if(get(i).equals(obj)){  //注意:底层调用的equals方法而不是==.
				remove(i);
			}
		}
	}
	
	public Object set(int index,Object obj){
		rangeCheck(index);

		Object oldValue =  elementData[index];
		elementData[index] = obj;
		return oldValue;
	}
	
	public void add(int index,Object obj){
		rangeCheck(index);
		
		ensureCapacity();  //数组扩容
		
		System.arraycopy(elementData, index, elementData, index + 1,
				 size - index);   //增加为啥不是size-1-index。因为index处的元素不能丢。
		elementData[index] = obj;
		size++;
	}
	
	private void ensureCapacity(){
		//数组扩容和数据的拷贝
				if(size==elementData.length){
					Object[] newArray = new Object[size*2+1];
					System.arraycopy(elementData, 0, newArray, 0, elementData.length);
//							for(int i=0;i<elementData.length;i++){
//								newArray[i] = elementData[i];
//							}
					elementData = newArray;
				}
	}
	
	
	private void rangeCheck(int index){   //注意里面的方法要设成private
		if(index<0||index>=size){
			try {
				throw new Exception();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
	
	
	public static void main(String[] args) {
		SxtArrayList list = new SxtArrayList(3);
		list.add("333");
		list.add("444");
		list.add("5");
		list.add("344433");
		list.add("333");
		list.add("333");
		System.out.println(list.size()); 
//		System.out.println(list.get(6));
		list.remove("444");
		System.out.println(list.size());
	}

}
LindedList自己实现

双向链表:节点中有上下两个节点的引用
单向链表:节点中只有下一个节点的引用

//用来表示一个节点,单向链表就只有一个
public class Node {
	 Node previous;   //上一个节点
	 Object obj;
	 Node next;        //下一个节点   这里应该要设成private,但是要大量的写set get有点麻烦,这里暂且不加private
	
	public Node() {
	}
	
	public Node(Node previous, Object obj, Node next) {
		super();
		this.previous = previous;
		this.obj = obj;
		this.next = next;
	}

	public Node getPrevious() {
		return previous;
	}

	public void setPrevious(Node previous) {
		this.previous = previous;
	}

	public Object getObj() {
		return obj;
	}

	public void setObj(Object obj) {
		this.obj = obj;
	}

	public Node getNext() {
		return next;
	}

	public void setNext(Node next) {
		this.next = next;
	}
}
public class SxtLinkedList /*implements List*/ {
	private Node first;    //节点类可以单独定义,也可以定义在内部表示专门为外部类服务
	private Node last;		//为什么定义首尾,因为知道了首尾节点就可以顺着找到其他节点。
	
	private int size;
	
	public void add(Object obj){
		Node n = new Node();
	
		if(first==null){                      //脑子里想着链表的图,再赋值
			n.setPrevious(null);
			n.setObj(obj);
			n.setNext(null);  //先设节点的值
			
			first = n;		//再设链表的值
			last = n;
		}else{
			//直接往last节点后增加新的节点
			n.setPrevious(last);
			n.setObj(obj);
			n.setNext(null);		//先设节点的值
			
			last.setNext(n);		//再设链表的值,这里加在链表最后,影响的只是last
			last = n;
		}
		size++;
	}
	
	public int size(){
		return size;
	}
	
	private void rangeCheck(int index){
		if(index<0||index>=size){
			try {
				throw new Exception();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
	
	public Object get(int index){   //2  //遍历的时候记一个数,遍历第一个记为1,第二个记为2
		rangeCheck(index);
		
		// 0 1 2 3 4
		Node temp = node(index);
		if(temp!=null){
			return temp.obj;
		}
		return null;
	}
	
	public Node node(int index){
		Node temp = null;     //temp表示每次遍历当前指定的这个节点
		if(first!=null){
		// 一进来的话,让index索引和size/2比较,小于一半就从前遍历,大于一半从后遍历
		//>>表示移位符,右移一位表示除以2,左移表示乘以2。为啥不直接除,因为移位比除法快得多
			if (index < (size >> 1)) {
				temp = first;
				for(int i=0;i<index;i++){//找index次就可以,外层的if是优化用的
					temp = temp.next;
				}
			}else{
				temp = last;
	            for (int i = size - 1; i > index; i--){
	            	temp = temp.previous;
	            }
			}
			
		}
//		LinkedList l;
		return temp;
	}
	
	
	public void remove(int index){
		Node temp = node(index);
		
		if(temp!=null){
			Node up = temp.previous;
			Node down = temp.next;
			up.next = down;
			down.previous = up;
			size--;
		}
		
	}
	
	//脑子中有点形象思维:按照上一节点-->新节点-->下一节点的顺序,修改引用
	public void add(int index,Object obj){  //index索引处插入一个节点。思路:先获得该节点,然后修改引用
		Node temp = node(index);  //它是新节点的下一个
		
		Node newNode = new Node();
		newNode.obj = obj;
		
		if(temp!=null){
			Node up = temp.previous;  //它是新节点的上一个
			up.next = newNode;
			newNode.previous = up;
			
			newNode.next = temp;
			temp.previous = newNode;
			
			size++;
		}
	}

	public static void main(String[] args) {
		SxtLinkedList list = new SxtLinkedList();
		list.add("aaa");
		list.add("bbb");
//		list.add(1,"BBBB");
		list.add("ccc");
		list.add("ddd");
		list.add("eee");
//		list.remove(1);
		System.out.println(list.get(3)); 
	}
	

}
Map

映射的意思。。。
注意注意:容器里面存放的是地址,remove只是从容器中移除了地址,但是对象还在。
下面的代码中:执行了remove(“高琪”),也还是可以打印出wife的名字。 因为删地址前,已经把wife对象给w了。

public class TestMap {
    public static void main(String[] args){
        Map map = new HashMap();
        map.put("高琪",new Wife("高琪老婆"));
        map.put("老王",new Wife("老王老婆"));
        map.clear();  //清空map

//        map.remove("高琪"); 放这里就不行
        Wife wife = (Wife)map.get("高琪");
        map.remove("高琪");  //放这里依然可以取wife的值
        System.out.println(wife.name);
    }
}

class Wife{
    String name;
    public Wife(String name) {
        this.name = name;
    }
}

这里写图片描述

Map的子类中重点看下面两个:区别。。
Hash — 线程不安全,效率高
HashTable – 安全,效率低 和arraylist、vector & stringbuilder、stringbuffer的区别差不多。。
简单的实现 :

/**
 *自定义实现Map的功能!
 *暂不完美!  因为效率比较低,每次找元素都要遍历
 *Map:存放键值对,根据键对象找对应的值对象.键不能重复!
 *
 */
public class SxtMap001 {
	
	SxtEntry[]  arr  = new SxtEntry[990];
	int size;
	
	public void put(Object key,Object value){
		SxtEntry e = new SxtEntry(key,value);
		//解决键值重复的处理
		for(int i=0;i<size;i++){
			if(arr[i].key.equals(key)){
				arr[i].value=value;   //如果key相等,那就把value覆盖掉
				return ;
			}
		}
		
		arr[size++] = e;
	}
	
	//remove就不写了,道理类似
	
	public Object get(Object key){
		for(int i=0;i<size;i++){
			if(arr[i].key.equals(key)){
				return arr[i].value;
			}
		}
		return null;
	}
	
	public boolean containsKey(Object key){
		for(int i=0;i<size;i++){
			if(arr[i].key.equals(key)){
				return true;
			}
		}
		return false;
	}
	
	public boolean containsValue(Object value){
		for(int i=0;i<size;i++){
			if(arr[i].value.equals(value)){
				return true;
			}
		}
		return false;
	}
	
	
	
	public static void main(String[] args) {
		SxtMap001 m = new SxtMap001();
		m.put("高琪", new Wife("杨幂"));
		m.put("高琪", new Wife("李四"));
		Wife w = (Wife) m.get("高琪");
		System.out.println(w.name); 
	}

}

class  SxtEntry {   //Entry条目条款的意思。。
	Object key;
	Object value;
	
	public SxtEntry(Object key, Object value) {
		super();
		this.key = key;
		this.value = value;
	}	
}

复杂的自己实现:
思路:查询的时候为了不遍历,可以考虑用数组(索引是hashcode),那数组又不可能无限大,就可以想办法把hashcode转成一个数组长度内(假设是999)的数字,怎么转呢? 可以考虑取999的余数,那就在之间了。
//a:1000–>1 b:10000–>13 但是余数可能重复。
余数冲突了怎么办??那就引出结论
结论:
数组里存放链表,链表里面再放key和value。
这里写图片描述

/**
 * 自定义Map的升级版:
 * 1. 提高查询的效率
 *  //Map的底层结构就是:数组里面放的是链表,链表里放KV,始终记住一个图。。
 
 * 记住数组不为空,就拿到list去遍历KV.
 * 这里就写了个put和get
 */
public class SxtMap002 {

	LinkedList[]  arr  = new LinkedList[9];   //数组里面放的是链表,链表里放KV,始终记住一个图。。
	int size;
	
	public void put(Object key,Object value){
		SxtEntry  e = new SxtEntry(key,value);
		
		int hash = key.hashCode(); //每个对象都有个地址,这个方法会根据地址生成一个数(哈希码),
		hash = hash<0?-hash:hash; //哈希码有时候算出来是负值,这里先取反 再取余数。。数组index不能是负的
		
		int a = hash%arr.length;
		if(arr[a]==null){  //这个位置没有元素的时候,那就新建一个list,把元素放入list,再把list放入数组
			LinkedList list = new LinkedList();
			arr[a] = list;
			list.add(e);
		}else{
			LinkedList list = arr[a];   //如果已经有一个元素了,那就往链表后加,key重复就覆盖
			for(int i=0;i<list.size();i++){
				SxtEntry e2 = (SxtEntry) list.get(i);
				if(e2.key.equals(key)){
					e2.value = value;  //键值重复直接覆盖!
					return;
				}
			}
			
			arr[a].add(e);
		}
		//a:1000-->1   b:10000-->13
	}

	public Object get(Object key){
		int a = key.hashCode()%arr.length;
		if(arr[a]!=null){   //数组不为空,就拿到list去遍历key
			LinkedList list = arr[a];
			for(int i=0;i<list.size();i++){
				SxtEntry e = (SxtEntry) list.get(i);
				if(e.key.equals(key)){
					return e.value;
				}
			}
		}
		
		return null;  //数组[哈希码]等于空,说明list都没有,那就没有元素。。
	}
	
	public static void main(String[] args) {
		SxtMap002 m = new SxtMap002();
		m.put("高琪", new Wife("杨幂"));
		m.put("高琪", new Wife("李四"));
		Wife w = (Wife) m.get("高琪");
		System.out.println(w.name); 
	}

}

通过以上实现,我们频繁的使用了hashcode()和equals()方法。

java中有这个规定:两个内容相等的对象就是两个对象equals。也就是两个对象equals,那hashcode一定要相等。 反之则不然。而且,要重写的话,两个方法都要重写。脑子里始终想起那个图。都到了equals那数组位置肯定一样,但是数组位置一样的有很多元素啊,就不一样equals了。
数组有个专业名字叫:buget桶。
fdsafsdafsad
这里写图片描述

顺带插一点Obejct的东西:

object又不是抽象类,为啥有没有实现的方法呢。因为前面加了native,表示本地方法。
是调用本地的资源,和操作系统相关的方法。。
public final native Class<?> getClass();

public native int hashCode(); //object的hashcode方法默认的是 拿内存中的地址进行计算,所以声明了native

public final native void notify();

要重写equals的话,hashcode也要重写。可以根据IDE自动重写,也可以自己重写

IDE自动生成。。。。。
class Cat extends Object{
    String id;
    String name;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Cat)) return false;

        Cat cat = (Cat) o;

        if (id != null ? !id.equals(cat.id) : cat.id != null) return false;
        return name != null ? name.equals(cat.name) : cat.name == null;
    }

    @Override
    //这里做了一个散列算法。一般是取一个质数,再去散列,这样会分布的比较均匀
    public int hashCode() {
        int result = id != null ? id.hashCode() : 0;
        result = 31 * result + (name != null ? name.hashCode() : 0);
        return result;
    }
}
2、map存值:分拣思路
/**
 * this is a cat and that is a mice and where is the food?
 * 统计每个单词出现的次数,这才是map强大的地方
 * 
 * 存储到Map中
 * key :String 
 * value:自定义类型
 * 
 * "分拣" 思路
 * 方法一 1、为所有key创建容器
 *    之后容器中存放对应value
 * 方法二 2、第一次创建容器,并存放值value
 *    第二次之后,直接使用容器存放值
 * @author Administrator
 *
 */
public class Demo01 {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		//这里只看方法二
		String str ="this is a cat and that is a mice and where is the food";
		//分割字符串
		String[] strArray=str.split(" ");
		//存储到Map中,散列存在
		Map<String,Letter>  letters = new HashMap<String,Letter>();
		for(String temp:strArray){	 		
			/*
			 //1、为所有key创建容器		 
			if(!letters.containsKey(temp)){
				Letter col = new Letter();
				col.setCount(1); //第一次值存放容器中
				letters.put(temp, col);
			}else{
				//2、	 第二次之后,直接使用容器存放值
				Letter col =letters.get(temp); //直接使用容器
				col.setCount(col.getCount()+1);
			}*/
			//下面换一种写法,和上面大同小异
			Letter col = null;
			if(null==(col=letters.get(temp))){
				col = new Letter();
				col.setCount(1); //第一次值存放容器中
				letters.put(temp, col);
			}else{
				//2、	 第二次之后,直接使用容器存放值				
				col.setCount(col.getCount()+1);
			}
		}
		//  遍历输出Map的值
		Set<String> keys = letters.keySet();
		for(String key:keys){
			Letter col =letters.get(key);
			System.out.println("字母:"+key+",次数"+col.getCount());
		}
	}
}
咕泡学院HashMap源码讲解JDK1.8

HashMap是java中最为常用的集合类之一,也是诸多集合类中实现最为复杂的,涉及的知识点最多。
HashMap没有排序,TreeMap才排序
这里写图片描述

JDK1.8相比于JDK1.7就HashMap来讲,多了很多东西。
准备点知识:
无符号右移(>>>)规则和右移运算是一样的,只是填充时不管左边的数字是正是负都用0来填充,无符号右移运算只针对负数计算,因为对于正数来说这种运算没有意义
无符号右移运算符>>> 只是对32位和64位的值有意义
https://www.cnblogs.com/blog-cq/p/5793529.html

重点的点:1、i = (n - 1) & hash] 做与运算。。。
2、为啥数组长度一定是2的N次幂。第一个是做与运算,会减少hash冲突几率,bit位为1的位置不会浪费。
第一:如果不是2的次幂,也就是低位不是全为1,这个时候相同的index就会有不同的hash值与之对应,这样的话hash冲突的几率又变大了(key找到对应的index的算法就是为了不冲突,这样的话又冲突了,相当于是增大了冲突的几率)。第二:同时,index对应的这个bit位无论如何不会等于1了,而对应的那些数组位置也就被白白浪费了。
第二个是扩容的时候效率更高,小于16的hash值就不动,大于16的才迁移。
扩容为啥效率高?见博客。
而扩容后只有一位差异,调换几率比较少,也就是多出了最左位的1,这样在通过 h&(length-1)的时候,只要h对应的最左边的那一个差异位为0,就能保证得到的新的数组索引和老数组索引一致,就不需要调换。这样就大大减少了之前已经散列良好的老数组的数据位置重新调换。

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable {
/**
	先看看几个常量和成员变量
     * The default initial capacity - MUST be a power of two.
     */
     //左移4位是1的右面加四个零,变成10000,2的四次方=16.
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16.
	//左移30位。int的最大取值是2的31次方-1;
	static final int MAXIMUM_CAPACITY = 1 << 30;
	、、负载因子,默认是0.75,作用是控制hashmap的容量
	static final float DEFAULT_LOAD_FACTOR = 0.75f;

、、链表节点转成树形结构的阈值
static final int TREEIFY_THRESHOLD = 8;
、、反操作,树形结构转成链表的阈值
static final int UNTREEIFY_THRESHOLD = 6;
、、树的最小容量是64
static final int MIN_TREEIFY_CAPACITY = 64;
、、数组初始为0
transient Node<K,V>[] table;


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

static final int hash(Object key) {  //经过这个方法之后,将会拿到一个int的hash值,然后再put
        int h;
        //从这里可以看出来key是可以为nul的
        //key.hashCode()-- 这里有类重写就用重写,没有重写就用Object的,会拿到hashcode的一个值
        //先执行左括号再执行右括号,再异或^  为什么右移16位,这个很精髓。想象一个这个图。int是32位(分一半左边是高位右边是低位),右移16就是右边16位(低位)就没有了,高位全部用0来填充。
        、、然后拿hashcode和移位后的数据 进行异或。相当于是数据的前半截和后半截去做异或运算。(把图画出来就知道了)
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
	
	再到put  putVal  
	Node是里面的一个静态内部类。
	
	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)
		//hashmap刚创建的时候这个是空的,并没有创建node数组,第一次put的时候才生成一个node数组(长度是16,见resize方法,也就是tab = resize()),类似于懒加载的思想
            n = (tab = resize()).length; 第一次n是16 
        //计算出hash值要落在数组的哪个索引上呢? 这句经典的代码可以实现i = (n - 1) & hash
        //n一定是二次幂,二次幂特征是1后面全部是0,那n-1就全部变成1了。(画二进制图理解)
        //全部变成1的话,才能进行&运算(相同结果就是1),进而才能取模。所以通过i = (n - 1) & hash就找到table数组的下标了。
        if ((p = tab[i = (n - 1) & hash]) == null)  -- 第一次node肯定是空
            tab[i] = newNode(hash, key, value, null); -- 第一次的next也是空,因为才一个元素,没有下一个
		}else {  -- 如果数组下标的这个位置不为null,就执行else了。
			//这里挑重要的看
			Node<K,V> e; K k;
			if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
			else if (p instanceof TreeNode) //看这个节点是不是TreeNode的类型,现在还不是,它是一个普通的Node
	            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);	
            else {
                for (int binCount = 0; ; ++binCount) {//循环整个链表,直到发现next为空,就把元素放到链表最后
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        //TREEIFY_THRESHOLD 是转换成红黑树 的阈值。当链表长度到达8就会转成一个树形结构
                        //为什么要转成红黑树?循环链表的话时间复杂度是O(n),而树是O(logN).对数阶的时间复杂度要比线性阶要好,为什么是8呢。8以内的O(N)可能还挺快,超过了就慢
                        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;//这里判断老的value值是不是等于当前写进来的value,等于就覆盖并把老值返回
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
		}
		++modCount;
        if (++size > threshold) 
            resize();
        afterNodeInsertion(evict);
        return null;
    }
	
	static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;  --从这可以看出Node是一个链表结构,指向下一个相同的对象
	}

	//扩容。。扩容是个业内通用问题,resid啊,mysql啊,都用碰到
	final Node<K,V>[] resize() {
		Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap = 0;
        newThr = 0;  //阈值一开始是12,并赋给了threshold
        newCap = DEFAULT_INITIAL_CAPACITY;  数组容量初始大小给16
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); 容量的阈值=初始容量*负载因子。初始阈值是16*0.75=12.


		扩容的话是左移一位,也就是*2,数组容量是翻倍(16-32-64),阈值也翻倍(12-24-)
		扩容之后要重新计算索引值,就要对32取模了。newTab[e.hash & (newCap - 1)] = e;
		存放的位置就要变了。这个过程叫做数据迁移,也叫做rebalance。
		因为扩容的目的就是要把数据平均到不同的位置上去,这样才能提升检索速度。扩容相当于是横向扩展,链表转红黑树相当于是纵向扩展。 小于16的还是在左边,大于16的才搬家搬到右边。
							以下是扩容的部分源码。
		解释下:index小于16的和(16-1)去与运算的时候,结果都是0,那就不用动了。
		index大于16的(高位肯定是1,否则怎么大于16呢??)和(16-1)去与运算的时候,结果就不为0了,就需要移动到新位置。
							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;
                            }
	}

}

超过16*0.75=12就数组长度扩容,链表长度超过8也扩容变成tree。。
负载因子决定了扩容的阈值,最好别搞。。。负载因子变大,就不容易扩容,空间就小,那查找时间就慢了。时间换空间
负载因子变小,就经常扩容,空间就很大,那查找需要遍历几率就少了,就快了。空间换时间

concurrentHashMap就是上了锁,把锁的粒度变的比较小,做的挺好的。 总的来说,concurrentHashMap就是解决了hashmap线程安全的问题,而且锁的粒度做的比较小。

Hashtable源码分析以及和HashMap的区别:
见博客《【源码】Hashtable源码剖析》:
https://blog.csdn.net/chdjj/article/details/38581035

set

这里写图片描述
haspset的底层是通过hashmap实现的。

public class HashSet<E>
    extends AbstractSet<E>
    implements Set<E>, Cloneable, java.io.Serializable
{
    static final long serialVersionUID = -5024744406713321676L;
	//haspset的底层是通过hashmap实现的。
    private transient HashMap<E,Object> map;

	public boolean add(E e) {
        return map.put(e, PRESENT)==null; //set的不可重复就是利用了map里面键对象的不可重复!
    }
}

set没有get,因为是无序的,拿数据出来只能遍历。

迭代器Iterator

集合类都实现了Collection接口的Iterator iterator();方法。返回一个Iterator帮你遍历。。。

public class Test01 {
	public static void main(String[] args) {
		List list = new ArrayList();
		list.add("aaa");
		list.add("bbb");
		list.add("ccc");
		
		//通过索引遍历List
		for(int i=0;i<list.size();i++){
			System.out.println(list.get(i));
		}
		//通过迭代器遍历List
		for(Iterator iter2 = list.iterator();iter2.hasNext();){
			String str = (String) iter2.next();
			System.out.println(str);
			iter2.remove();
			iter2.remove();
		}
		
		System.out.println(list.size()+"******");
		
		Set set = new HashSet();
		set.add("高1");
		set.add("高2");
		set.add("高3");
		
		//通过迭代器遍历Set,,遍历的时候用while和for都可以。
//		Iterator iter = set.iterator();  
//		while(iter.hasNext()){
		for(Iterator iter = set.iterator();iter.hasNext();){
			String str = (String) iter.next();  //这个next就相当于i++,迭代的功能
			System.out.println(str);
		}
		
	}

}

Iterator遍历map

Map<Integer, Integer> map = new HashMap<Integer, Integer>();  
  
Iterator<Map.Entry<Integer, Integer>> entries = map.entrySet().iterator();  
  
while (entries.hasNext()) {  
  
    Map.Entry<Integer, Integer> entry = entries.next();  
  
    System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());  
  
}  
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值