HashTable
扩容
底层有数组 Hashtable$Entry[],初始化大小为 11。临界值 threshold 为8 = 11 * 0.75。
调用put方法里的 addEntry(hash, key, value, index); 当满足 if (count >= threshold) 扩容(rehash)
int newCapacity = (oldCapacity << 1) + 1; //新容量计算方法
threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
// 临界值计算方法
Properties
Java 读写Properties配置文件 - 旭东的博客 - 博客园 感兴趣可以看这篇文章。
注意事项
1. Properties 继承 Hashtable,是无序的。
2. 可以通过 k-v 存放数据,当然key和value不能为null。
3. 常用方法:增 put(key,value),删 remove(key),改 put(相同的key,value),查 get(key)。
开发中如何选择集合实现类
一组对象指的就是只有key,没有value。
TreeSet
构造方法
正常的TreeSet声明应该是这样的:
TreeSet a = new TreeSet();
但是TreeSet有一个构造器,可以传入一个比较器Comparator(匿名内部类)
public static void main(String[] args) {
TreeSet a = new TreeSet(new Comparator() { //匿名内部类
@Override
public int compare(Object o1, Object o2) {
return ((String)o1).compareTo((String)o2); //按照字符串大小比较
}
});
a.add("jack");
a.add("a");
a.add("sss");
a.add("mmm");
System.out.println(a);// [a, jack, mmm, sss]
}
要注意一个问题:假设要求按照字符串的length来从小到大排序
public static void main(String[] args) {
TreeSet a = new TreeSet(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
return ((String)o1).length() - ((String)o2).length(); //从小到大
}
});
a.add("jack");
a.add("a");
a.add("sss");
a.add("mmm");
System.out.println(a); // [a, sss, jack]
}
可以发现 "mmm" 并没有加入进去,那么我们就需要追一下源码了。
Entry<K,V> t = root;
if (t == null) {
compare(key, key); // type (and possibly null) check
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
} // 初始化,生成一个新结点
int cmp;
Entry<K,V> parent;
Comparator<? super K> cpr = comparator; //重点在这里,把比较器赋过去
if (cpr != null) {
do { //对整个链表(key)进行循环,给当前key找适当位置
parent = t;
cmp = cpr.compare(key, t.key); //比较原结点与要加入的结点的key
//这里会动态绑定到匿名内部类对象
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right; //按照比较结果移动指针
else //遍历过程中发现准备添加的key和当前已有的key相等
return t.setValue(value); //由于Set的value为PRESENT,因此相当于没加
} while (t != null); //循环结束后,t就指向结点应该加入的位置
//parent为上一次,因此结束后还需要再移动一次
}
... //没有比较器的情况
Entry<K,V> e = new Entry<>(key, value, parent);
if (cmp < 0)
parent.left = e;
else
parent.right = e; //按照比较结果把结点e放在正确位置(parent是原来的t)
fixAfterInsertion(e);
size++; //结点个数加一
modCount++; //修改次数加一
return null;
}
"sss" 是先加入的,长度为3。因为自定义的比较器是比较长度的,而 "mmm" 的长度也为3,因此结果为0,直接不加入了(参考do里面的else情况)。
由于TreeSet的底层是TreeMap,因此比较器初始化方法在TreeMap里。
public TreeSet(Comparator<? super E> comparator) {
this(new TreeMap<>(comparator));
}
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
TreeSet和TreeMap的底层都是TreeMap,因此TreeMap的源码不再做解析。
关于TreeSet加入自定义类
如果TreeSet没有重写Comparator,并且加入的类也没有实现Comparable接口,那么就会报错,因为add源码里需要赋予一个比较器。
TreeSet a = new TreeSet();
a.add(1); //这样是没有问题的,因为1相当于Integer,而Integer实现了Comparable接口
public static void main(String[] args){
TreeSet a = new TreeSet();
a.add(new Car("AAA",2331313)); //报错 ClassCastException
}
class Car{
String name;
double price;
public Car(String name, double price) {
this.name = name;
this.price = price;
}
}
TreeSet a = new TreeSet(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
return 0;
}
}); //这样写就没问题了
class Car implements Comparable{ //这样写也没问题,Car类实现了Comparable接口
String name;
double price;
public Car(String name, double price) {
this.name = name;
this.price = price;
}
@Override //重写compareTo方法
public int compareTo(Object o) {
return 0;
}
}