java容器之Map接口

HashMap类

在介绍hashMap之前,有必要介绍下关于散列表的知识。(太久没用到散列表,没想到一时竟然忘记了它的存在的意义了,看来不多做笔记真不行)

散列表:支持以常数时间对任何命名项的检索或删除。为什么能够这么会有这种效果呢?原理是:定义一个空的tablesize大小数组,每个要插入元素根据散列函数取得数组的下标,所以要能根据元素进行线性的检索;

冲突:就是不同的项通过散列函数取得相同的下标(专业术语:相撞),就会造成冲突,我们可以通过线性探测,二次探测等方法来获得新的下标,也可以用hashMap类中的解决方案,如果出现不同的项有相同的下标,将第二项挂在第一项下,(就是第一项的next指向第二项)依次类推;

但是散列表牺牲了元素插入的有序性;


hashMap简单介绍:

          hashMap是基于散列表的原理,类中定义了变量table数组来存放数据,数组类型为Entry类型,(Entry是hashMap的一个静态内部类);包含这些基本的元素,capacity(容量),loadFactor(加载因子),size,threshold;Capacity:即数组的长度(初始值为16),loadFactor来决定threshold的大小(默认为0.75),Threshold = loadFactor* Capacity;也就是说如果size>=Threshold,那么我们需要扩充数组的大小capacity,如果此时的capacity的大小已经达到最大值Integer.MAX_VALUE,那么不需改变数据大小,继续添加数组即可;

         每组数据,对应一个Entry类和一个table的下标,这个小标的取值跟key的hash值和table的长度有关;所以有一点特别重要,在扩充数组的时候,定义了一个新的数组后,由于table的长度变化,需要对每一个Entry对象重新计算小标值,重新将数据填入数组中;

hashMap的实现不是同步的。

         它的部分源代码如下:我们一一分析:

//继承类AbstractMap和接口Map,Cloneable,Serializable;

public class HashMap<K,V>
    extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable

类包含的成员

//散列表
static final Entry<?,?>[] EMPTY_TABLE = {};
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
//元素的个数
transient int size;
//加载因子,就是说如果要存放size=4的数据,根据加载因子,我们定义散列表的大小为4/loadFactor;
//加载因子的默认值是0.75
final float loadFactor;
//值为 (capacity * load factor).
int threshold;


存放一组元素的操作如下:
//向map里头存放数据
public V put(K key, V value) {
	//如果数组为空,初始化数组inflateTable(),第一次存放//时出现
    if (table == EMPTY_TABLE) {
        inflateTable(threshold);
    }
    //允许key为空,此时的数组下标为0,(剩下的)操作相同;
    if (key == null)
        return putForNullKey(value);
    int hash = hash(key);
    int i = indexFor(hash, table.length);
    //这段代码是重点,告诉我们散列表存数的形式,找到下标后,判断该位置是否已经有数据,如果有挂载在已有的数据下;
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
        Object k;
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }
modCount++;
//该方法,将这组数据创建一个Entry对象保存,同时存放了hash值和小标值,当然要先判断是否数组达到了上限,具体请看方法的内容
    addEntry(hash, key, value, i);
    return null;
}
void addEntry(int hash, K key, V value, int bucketIndex) {
    if ((size >= threshold) && (null != table[bucketIndex])) {
    	//用来扩充数组的,如果数组已经达到上限,立刻return;
    	//如果没有,那么遍历原来的数组,重新定义下标。。。
        resize(2 * table.length);
        hash = (null != key) ? hash(key) : 0;
        //重新定义下标
        bucketIndex = indexFor(hash, table.length);
    }
    
    createEntry(hash, key, value, bucketIndex);
}
//新建一个Entry对象,然后存放在table中
void createEntry(int hash, K key, V value, int bucketIndex) {
    Entry<K,V> e = table[bucketIndex];
    table[bucketIndex] = new Entry<>(hash, key, value, e);
    size++;
}

接下来,是时候介绍小Entry类了,其实它仅仅就是用来存放一组数据和一些基本信息的结构而已哈;

//内部类 Entry,继承接口Map.Entry<K,V>,简化了部分代码
static class Entry<K,V> implements Map.Entry<K,V> {
    final K key;
    V value;
    Entry<K,V> next;
    int hash;
    Entry(int h, K k, V v, Entry<K,V> n) {
        value = v;
        next = n;
        key = k;
        hash = h;
    }

    public final K getKey() {
        return key;
    }
    public final V getValue() {
        return value;
    }
    public final V setValue(V newValue) {
        V oldValue = value;
        value = newValue;
        return oldValue;
    }
}

接下来介绍下,如果根据key来取得value值,知道原理后,我们应该知道怎么取值了吧,由key获得hash值,在与table.length计算出下标,从该位置上找出key对应的value。

  public V get(Object key) {
        if (key == null)
            return getForNullKey();
        Entry<K,V> entry = getEntry(key);
        return null == entry ? null : entry.getValue();
    }
    final Entry<K,V> getEntry(Object key) {
        if (size == 0) {
            return null;
        }

        int hash = (key == null) ? 0 : hash(key);
        for (Entry<K,V> e = table[indexFor(hash, table.length)];
             e != null;
             e = e.next) {
            Object k;
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k))))
                return e;
        }
        return null;
}

    ListH ashMap类


简单的介绍:

LinkedHashMap继承类HashMap,在HashMap的基础上提供了记录输入顺序的方法,也就是用链表的方式,对每一次输入进行记录;


//定义了成员变量,记录链表的头部

private transient Entry<K,V> header;

//覆盖了HashMap的init()的方法,原本在HashMap中它是一个空方法,但是在第一次初始化数组的时候会被调用;

  @Override
    void init() {
        header = new Entry<>(-1, null, null, null);
        header.before = header.after = header;
    }

//当数组需要调整大小的时候,会调用这个方法,(覆盖的原因仅仅是因为用链表来遍历效率更高一点哈)

  @Override
    void transfer(HashMap.Entry[] newTable, boolean rehash) {
        int newCapacity = newTable.length;
        for (Entry<K,V> e = header.after; e != header; e = e.after) {
            if (rehash)
                e.hash = (e.key == null) ? 0 : hash(e.key);
            int index = indexFor(e.hash, newCapacity);
            e.next = newTable[index];
            newTable[index] = e;
        }
}

//这是ListHashMap中类Entry<K,V>一个方法,当插入新数据的时候会调用该方法,此方法在HashMap. Entry类中是一个空的方法;在此,该方法是为了维护数据的有序性

void recordAccess(HashMap<K,V> m) {
            LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
            if (lm.accessOrder) {
                lm.modCount++;
                remove();
                addBefore(lm.header);
            }
        }

TreeMap


TreeMap是基于红黑树实现的,红黑树的具体分析在以后的章节会详细介绍,现在大致说下红黑树的特点:

二叉查找树特点:1,父节点的值大于两个儿子的值;2,同一个父亲,右儿子的值大于左儿子的值;二叉查找树有个明显的缺陷,就是不断的操作最终会导致树非常不平衡(也就是树的左右孩子的深度不一样),所以诞生了平衡二叉树,但是平衡二叉树对树的要求比较高;由此产生了红黑树;对于红黑树的操作的时间复杂度,构造树的方法,以后再详细介绍;

针对TreeMap分析:

类成员变量
private final Comparator<? super K> comparator;
private transient Entry<K,V> root = null;
构造函数:
//如果不传入参数,则默认按照自然顺序(就是hashcode的大小)排序
    public TreeMap() {
        comparator = null;
    }
    public TreeMap(Comparator<? super K> comparator) {
        this.comparator = comparator;
    }

如果compare后,返回0那么认为这两个数是一样的,在TreeMap中对同样的数,覆盖原则哈,

测试代码

public class person1{

		int height;
		int age;
		
		person1(int height,int age){
			this.height = height;
			this.age = age;
		}
		
	
	public static void main(String args[]){
	
		TreeMap<person1,String> tr =  new TreeMap<person1, String>(	new personComparator());
		person1 p1 = new person1(1,1);
		person1 p2 = new person1(1,2);
		person1 p3 = new person1(2,3);
		tr.put(p1, "p1");
		tr.put(p2, "p2");
		tr.put(p3, "p3");
		
		System.out.print((tr.get(p1)+tr.get(p2)+tr.get(p3)));
	}
	
}
class personComparator implements Comparator<person1>{

	@Override 
	public int compare(person1 o1, person1 o2) {
		// TODO Auto-generated method stub
		if(o1.height==o2.height){
			return 0;
		}else if(o1.height>o2.height){
			return 1;
		}else{
			return -1;
		}
	}
	
}

测试结果如下:

p2p2p3
























  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值