集合框架-Set集合

代码:
Collection c = new ArrayList();
c.add("hello");
c.add("world");
c.add("java");

System.out.println(c);

为什么c输出的不是地址值呢?
A:Collection c = new ArrayList();
这是多态,所以输出c的toString()方法,其实是输出ArrayList的toString()
B:看ArrayList的toString()
而我们在ArrayList里面却没有发现toString()。
以后遇到这种情况,也不要担心,你认为有,它却没有,就应该去它父亲里面看看。
C:toString()的方法源码

         public String toString() {
Iterator<E> it = iterator(); //集合本身调用迭代器方法,得到集合迭代器
if (! it.hasNext())
return "[]"; StringBuilder sb = new StringBuilder();
sb.append('[');
for (;;) {
E e = it.next(); //e=hello,world,java
sb.append(e == this ? "(this Collection)" : e);
if (! it.hasNext())
//[hello, world, java]
return sb.append(']').toString();
sb.append(',').append(' ');
}
}

需求:用户登录注册案例。

假如用户类的内容比较对,将来维护起来就比较麻烦,为了更清晰的分类,我们就把用户又划分成了两类

用户基本描述类

成员变量:用户名,密码

构造方法:无参构造

成员方法:getXxx()/setXxx()

用户操作类

登录,注册

分包:
A:功能划分
B:模块划分
C:先按模块划分,再按功能划分

今天我们选择按照功能划分:
用户基本描述类包 cn.itcast.pojo
用户操作接口 cn.itcast.dao
用户操作类包 cn.itcast.dao.impl
今天是集合实现,过几天是IO实现,再过几天是GUI实现,就业班我们就是数据库实现
用户测试类 cn.itcast.test

// 为了让多个方法能够使用同一个集合,就把集合定义为成员变量
// 为了不让外人看到,用private
// 为了让多个对象共享同一个成员变量,用static
private static ArrayList<User> array = new ArrayList<User>();

* Collection
* |--List
* 有序(存储顺序和取出顺序一致),可重复
* |--Set
* 无序(存储顺序和取出顺序不一致),唯一
*
* HashSet:它不保证 set 的迭代顺序;特别是它不保证该顺序恒久不变。
* 注意:虽然Set集合的元素无序,但是,作为集合来说,它肯定有它自己的存储顺序,
* 而你的顺序恰好和它的存储顺序一致,这代表不了有序,你可以多存储一些数据,就能看到效果。

         // 创建集合对象
Set<String> set = new HashSet<String>(); // 创建并添加元素
set.add("hello");
set.add("java");
set.add("world");
set.add("java");
set.add("world"); // 增强for
for (String s : set) {
System.out.println(s);
}

* HashSet:存储字符串并遍历
* 问题:为什么存储字符串的时候,字符串内容相同的只存储了一个呢?
* 通过查看add方法的源码,我们知道这个方法底层依赖 两个方法:hashCode()和equals()。
* 步骤:
* 首先比较哈希值
* 如果相同,继续走,比较地址值或者走equals()
* 如果不同,就直接添加到集合中
* 按照方法的步骤来说:
* 先看hashCode()值是否相同
* 相同:继续走equals()方法
* 返回true: 说明元素重复,就不添加
* 返回false:说明元素不重复,就添加到集合
* 不同:就直接把元素添加到集合
* 如果类没有重写这两个方法,默认使用的Object()。一般来说不同相同。
* 而String类重写了hashCode()和equals()方法,所以,它就可以把内容相同的字符串去掉。只留下一个。

* 需求:存储自定义对象,并保证元素的唯一性
* 要求:如果两个对象的成员变量值都相同,则为同一个元素。
*
* 目前是不符合我的要求的:因为我们知道HashSet底层依赖的是hashCode()和equals()方法。
* 而这两个方法我们在学生类中没有重写,所以,默认使用的是Object类。
* 这个时候,他们的哈希值是不会一样的,根本就不会继续判断,执行了添加操作。

HashSet集合的add()方法的源码:

 interface Collection {
...
} interface Set extends Collection {
...
} class HashSet implements Set {
private static final Object PRESENT = new Object();
private transient HashMap<E,Object> map; public HashSet() {
map = new HashMap<>();
} public boolean add(E e) { //e=hello,world
return map.put(e, PRESENT)==null;
}
} class HashMap implements Map {
public V put(K key, V value) { //key=e=hello,world //看哈希表是否为空,如果空,就开辟空间
if (table == EMPTY_TABLE) {
inflateTable(threshold);
} //判断对象是否为null
if (key == null)
return putForNullKey(value); int hash = hash(key); //和对象的hashCode()方法相关 //在哈希表中查找hash值
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
//这次的e其实是第一次的world
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++;
addEntry(hash, key, value, i); //把元素添加
return null;
} transient int hashSeed = 0; final int hash(Object k) { //k=key=e=hello,
int h = hashSeed;
if (0 != h && k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
} h ^= k.hashCode(); //这里调用的是对象的hashCode()方法 // This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
} hs.add("hello");
hs.add("world");
hs.add("java");
hs.add("world");

* LinkedHashSet:底层数据结构由哈希表和链表组成。
* 哈希表保证元素的唯一性。
* 链表保证元素有素。(存储和取出是一致)

         // 创建集合对象
LinkedHashSet<String> hs = new LinkedHashSet<String>(); // 创建并添加元素
hs.add("hello");
hs.add("world");
hs.add("java");
hs.add("world");
hs.add("java"); // 遍历
for (String s : hs) {
System.out.println(s);
}

* TreeSet:能够对元素按照某种规则进行排序。
* 排序有两种方式
* A:自然排序
* B:比较器排序
*
* TreeSet集合的特点:排序和唯一
*
* 通过观察TreeSet的add()方法,我们知道最终要看TreeMap的put()方法。

TreeSet的add()方法的源码解析:

 interface Collection {...}

 interface Set extends Collection {...}

 interface NavigableMap {

 }

 class TreeMap implements NavigableMap {
public V put(K key, V value) {
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;
// split comparator and comparable paths
Comparator<? super K> cpr = comparator;
if (cpr != null) {
do {
parent = t;
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
else {
if (key == null)
throw new NullPointerException();
Comparable<? super K> k = (Comparable<? super K>) key;
do {
parent = t;
cmp = k.compareTo(t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
Entry<K,V> e = new Entry<>(key, value, parent);
if (cmp < 0)
parent.left = e;
else
parent.right = e;
fixAfterInsertion(e);
size++;
modCount++;
return null;
}
} class TreeSet implements Set {
private transient NavigableMap<E,Object> m; public TreeSet() {
this(new TreeMap<E,Object>());
} public boolean add(E e) {
return m.put(e, PRESENT)==null;
}
} 真正的比较是依赖于元素的compareTo()方法,而这个方法是定义在 Comparable里面的。
所以,你要想重写该方法,就必须是先 Comparable接口。这个接口表示的就是自然排序。

* 如果一个类的元素要想能够进行自然排序,就必须实现自然排序接口

* TreeSet集合保证元素排序和唯一性的原理
* 唯一性:是根据比较的返回是否是0来决定。
* 排序:
* A:自然排序(元素具备比较性)
* 让元素所属的类实现自然排序接口 Comparable
* B:比较器排序(集合具备比较性)
* 让集合的构造方法接收一个比较器接口的子类对象 Comparator

         // 如果一个方法的参数是接口,那么真正要的是接口的实现类的对象
// 而匿名内部类就可以实现这个东西
TreeSet<Student> ts = new TreeSet<Student>(new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
// 姓名长度
int num = s1.getName().length() - s2.getName().length();
// 姓名内容
int num2 = num == 0 ? s1.getName().compareTo(s2.getName())
: num;
// 年龄
int num3 = num2 == 0 ? s1.getAge() - s2.getAge() : num2;
return num3;
}
});

HashSet存储元素保证唯一性的代码及图解:

集合框架-Set集合

TreeSet存储元素自然排序和唯一的图解:

集合框架-Set集合

上一篇:浩瀚PDA无线POS盘点机(安装盘点程序):盘点结果实时上传到PC电脑端


下一篇:配置jenkins,并把iOS包自动上传至fir.im