Set
? Set集合与Collection集合基本相同,没有提供额外的方法。只是行为上略有不同,Set集合不允许添加重复的元素(add()方法会返回false,且元素不会被加入)
HashSet
HashSet使用Hash算法来存储集合中的元素,因此有良好的存取和查找性能。 HashSet的特点:
- HashSet不是线程安全的;
- HashSet中元素存储的顺序可能与元素添加的顺序不同;
- 集合的元素值可以为null;
?除此之外,HashSet判断元素是否相等的标准也是其一大特点:HashSet集合判断两个元素相等的标准是hashcode() 方法的返回值相等且equals() 方法返回true。
equals()
? equals()方法定义在Object类中,其作用是:通过判断两个对象的地址是否相等来判断两个对象是否相等。 equals()源码如下: ?既然equals()方法定义在Object类中,那么意味着Java中所有的类都实现了equals()方法,所有的类都可以使用equals()方法来判断两个对象是否相等。但是默认的equals()等价于"=="。 ?我们也可以重写子类的equals()方法,定义满足自己需求的逻辑。下面我们以Person类为例子,假设身高和姓名相等的两个人相等。
代码如下:
public class Person {
public int age;
public int height;
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Person other = (Person) obj;
if (age != other.age)
return false;
if (height != other.height)
return false;
return true;
}
}
?我们可以将"类是否重写equals()方法",将equals()的作用分为两类:
- 没有重写equals():这时候使用equals()方法去比较两个对象是否相等,实际上比较的是这两个对象的内存地址是否相等(等价于"=="),也就是这两个对象是不是一个对象;
- 重写了equals():重写了equals()方法的情况通常是:如果比较的这两个对象的内容相等,那么这两个对象相等;
hashCode()
? hashCode()的作用是获取哈希值\散列码;hashCode()返回一个int型整数,这个hash值的作用是确定元素在散列表中的索引位置。 ?hashCode()方法定义在Object类中,这意味着Java中所有的类中都有hashCode()方法。但是仅仅在创建某个"类"的散列表时,这个类的hashCode()方法才会发挥作用。更通俗的讲,只有创建包含该类的Hashset,Hashtable,HashMap集合时,hashCode()才有用。HashMap,HashSet,Hashtable就是散列表集合。
类似于equals()方法,hashCode()也分两种情况:
- 没有重写hashCode():若一个类没有重写hashCode(),那么当它通过hashCode()方法比较是否相等的时候,等价于使用"=="比较两个对象是否相等;
- 重写了hashCode():通常的做法是 如果两个对象的内容相等,那么其hashCode()的返回值相等;
HashSet判断集合元素是否相等
?HashSet中不能存储重复元素,当调用add(Object)方法的时候,首先会调用Object的hashCode()方法判断hash值是否已经存在,如果不存在那么就直接插入元素;如果存在那么就调用equals()方法来判断,如果返回false,那么就插入元素;返回true则插入失败。 ?HashSet判断两个元素相等的标准:hashCode()返回值相等且equals()返回true。 注意: HashSet是根据元素的hash值来快速定位的,如果出现hash值相等但是equals()返回false的情况,HashSet会把它们存储在相同的位置上,在这个位置上用链表式结构存储多个对象,这样会导致性能下降。 所以重写方法时,尽量保证:hash值相等的情况下,equals()返回true。否则如果将该元素插入到Hash表中,哈希表的性能会降低。
补充:HashSet的实质是一个HashMap。HashSet的所有集合元素构成了HashMap的key,其value是一个静态的Object对象。因此HashSet的所有性质,HashMap的key全部具备。
LinkedHashSet
LinkedHashSet是HashSet的子类,也是根据hash值来确定元素在hash表中存储的位置,同时,LinkedHashSet使用链表来维护元素的次序,使得元素是按添加的顺序来保存的。 当遍历LinkedHashSet中的元素时,LinkedHashSet会以元素的添加顺序来访问集合中的元素。但是由于维护了元素的插入顺序,在性能上略低于HashSet,但是在迭代访问Set集合中的元素时有良好的性能。 注意: LinkedHashSet判断元素是否重复的标准与HashSet一致;
TreeSet
TreeSet是SortedSet接口 的实现类,正如SortedSet所暗示的,TreeSet中的元素都处于有序状态。 TreeSet中的方法:
comparator():返回对此 set 中的元素进行排序的比较器;如果此 set 使用其元素的自然顺序(类实现了Comparable接口),则返回null。
first():返回此 set 中当前第一个(最低)元素。
last(): 返回此 set 中当前最后一个(最高)元素。
lower(E e):返回此 set 中严格小于给定元素的最大元素;如果不存在这样的元素,则返回 null。
higher(E e):返回此 set 中严格大于给定元素的最小元素;如果不存在这样的元素,则返回 null。
subSet(E fromElement, E toElement):返回此 set 的部分视图,其元素从 fromElement(包括)到 toElement(不包括)。
headSet(E toElement):返回此 set 的部分视图,其元素小于toElement。
tailSet(E fromElement):返回此 set 的部分视图,其元素大于等于 fromElement。
TreeSet的排序方式
自然排序 Comparable接口
public interface Comparable<T> {
public int compareTo(T o);
}
Java中提供了一个Comparable接口,如果试图将一个对象添加到TreeSet集合中,那么就必须实现Comparable接口。 TreeSet会调用集合中元素的compare()方法,然后将元素按照升序排序,即 通过compare()方法比较后比较大的元素放在后面。
定制排序 Comparator比较器
@FunctionalInterface
public interface Comparator<T> {
int compare(T o1, T o2)
}
要实现定制排序,需要在创建TreeSet时,调用一个带参构造器,传入Comparator对象。并由该Comparator对象负责集合元素的排序逻辑,集合元素可以不必实现Comparable接口。
注意:如果向TreeSet中添加了一个可变对象后,在后面程序中修改了该对象,这将导致它与其他对象的大小顺序发生了改变,但TreeSet并不会再次调整它们。 例如:
@Test
public void test1(){
TreeSet<Person> set = new TreeSet<>((o1, o2) -> o1.age-o2.age);
Person p1 = new Person();
p1.setAge(10);
Person p2 =new Person();
p2.setAge(30);
Person p3 =new Person();
p3.setAge(40);
set.add(p1);
set.add(p2);
set.add(p3);
System.out.println("初始年龄排序");
System.out.println(set);
p1.age = 60;
System.out.println("修改p1年龄后集合排序");
System.out.println(set);
p2.age = 40;
System.out.println("修改p2年龄后集合排序");
System.out.println(set);
}
可以看到元素的位置并没有发生变化,而且修改后元素的删除操作也可能不成功。 总之,尽量不要修改放入TreeSet集合中元素的关键实例变量。TreeSet的底层是TreeMap,所以说TreeSet的底层也是红黑树 另外,TreeSet也是非线程安全的。
TreeSet如何判断集合元素相等
TreeSet集合判断元素是否相等的唯一标准是:两个对象通过compare() 方法是否返回0。如果返回0,那么TreeSet就认为这两个元素相等,不予加入集合内。
Set的性能
HashSet以Hash算法进行位置存储,特别适合添加 查询操作;LinkedHashSet要用链表来维护元素插入顺序,性能稍差,LinkedHashSet特别适合用于插入 删除 以及遍历操作。而TreeSet需要使用红黑树来维护元素的位置,性能最差。
Map
Map与Set 和 List的关系
与Set集合的关系
如果把Map里面的所有key放在一起看,它们就组成了一个Set集合(所有的key没有顺序,且不可重复)。事实上,Map中确实有一个keySet() 方法,返回Map中所有的key组成的Set集合。
与List集合的关系
如果把Map里面的所有value放在一起看,它们又很像一个List(元素与元素可以重复,可以根据索引找到每个值),只是Map中不是使用整数而是使用另一个对象作为索引。
Map中的方法
由于Map中存放的元素都是键值对,所以每一个键值对必然存在一个映射关系。Map中采用Entry内部类来表示一个映射项,映射项包含key和value(我们总说键值对键值对 每一个键值对实际上就是一个Entry)
Entry中的方法:
Map的四种遍历方式
@Test
public void test1(){
Map<String, String> map = new HashMap<String, String>();
map.put("1", "value1");
map.put("2", "value2");
map.put("3", "value3");
Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
while (iterator.hasNext()){
Map.Entry<String, String> next = iterator.next();
System.out.println("key: "+next.getKey()+" value: "+next.getValue());
}
for (Map.Entry<String, String> entry : map.entrySet()) {
System.out.println("key: "+entry.getKey()+" value: "+entry.getValue());
}
for (String key : map.keySet()) {
System.out.println("key: "+key+" value: "+map.get(key));
}
Set<Map.Entry<String, String>> entrySet = map.entrySet();
entrySet.forEach(System.out::println);
entrySet.stream().map(Map.Entry::getKey).forEach(System.out::println);
}
HashMap
HashMap判断key与value相等的标准
- key的判断标准:与HashSet相同。(HashSet其实就是一个特殊的HashMap);
- value的判断标准:两个元素equals()返回true就认为相等;
注意: 如果加入到HashMap中的key是一个可变对象,在加入到集合后又修改key的成员变量的值,可能导致hashCode()和equals()的比较结果发生变化,无法访问到该key。一般情况下不要修改。
@Test
public void test2(){
Map<Person, String> map = new HashMap<>();
Person lzh1 = new Person("lzh1", 20);
Person lzh2 = new Person("lzh2", 20);
map.put(lzh1, "lzh1");
map.put(lzh2, "lzh2");
map.entrySet().forEach(System.out::println);
lzh2.setName("lzh1");
System.out.println();
map.entrySet().forEach(System.out::println);
String s = map.get(lzh2);
System.out.println();
System.out.println(s);
String s1 = map.get(lzh1);
System.out.println(s1);
lzh2.setName("lzh2");
String s2 = map.get(lzh2);
System.out.println();
System.out.println(s2);
}
执行结果:
HashMap的本质
HashMap的构造函数
HashMap()
HashMap(int capacity)
HashMap(int capacity, float loadFactor)
HashMap(Map<? extends K, ? extends V> map)
- 容量(默认为16)即hash表的长度;
- 装载因子(默认为0.75)是hash表在自动扩容之前容量可以达到多满的一种尺度。
当hash表中的条目数大于hash表容量乘以装在因子时,hash表会进行resize操作(重建内部的数据结构),进而hash表将具有大约两倍的桶数;
|