Java-30 Set接口
- Collection(接口)
- List(接口) 元素有序,不唯一
List相关集合有三种遍历方式:
1、结合size()和get()方法有普通for循环
2、迭代器遍历方式
3、增强for循环
--ArrayList
底层是数组,查询快,增删慢
线程不安全,效率高
--Vector
底层是数组,查询快,增删慢
线程是安全的,效率低
--LinkedList
底层是链表,查询慢,增删快
线程是不安全的,效率高
Set(接口) 元素唯一,无序
Set相关集合有三种遍历方式:
1、迭代器方式遍历
2、增强for循环
--HashSet
底层依赖哈希表,依赖hashCode()和equals()方法来保证元素唯一的
线程是不安全的,效率高
--LinkedHashSet
底层依赖哈希表和链表,哈希表保证元素唯一,链表保证元素有序。
--TreeSet
底层是红黑树(自平衡二叉树)
排序依赖自然排序和比较器排序。
Hashset:底层依赖哈希表,线程是不安全的,效率高
依赖hashCode()和equals()方法来保证元素唯一
import java.util.HashSet; /* HashSet:此类实现Set接口,由哈希表(实际为HashMap实例)支持。线程不安全,效率高 有时候,你给的顺序可能是正好与取出遍历顺序一致,或者运行多次都一样,但是这并不代表有序 有可能是你给的元素比较少,可以多存一点看看 通过观察add源码发现,要想知道是否将元素插入到集合,先看元素的HashCode()值是否相同 如果相同,继续判断equals()方法 返回true,说明元素的内容一样,重复了,就不添加 返回false,说明元素的内容不重复,只是哈希值一样,就会添加集合中 如果连哈希值都不一样,就直接添加到集合中 由于String类重写了equals()方法,所以它这里比较的是内容,发现内容一样就不添加到集合 */ public class SetDemo1 { public static void main(String[] args) { HashSet<String> strings = new HashSet<>(); strings.add("hello"); strings.add("world"); strings.add("java"); strings.add("hello"); strings.add("world"); System.out.println(strings); } }
HashSet中add确保元素唯一性源码分析
public interface Set<E> extends Collection<E>{ } public class HashSet<E> implements Set<E>{ public boolean add(E e) { //"hello" return map.put(e, PRESENT)==null; } } public class HashMap<K,V> implements Map<K,V>{ //计算出传进来元素的哈希值 static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); //80121 } //底层调用的是HashMap中的put()方法,最终我们发现调用的是HashMap中的putVal()方法 //hash()方法是计算元素的哈希值,它底层是hashCode()方法 public V put(K key, V value) {//"hello" return putVal(hash(key), key, value, false, true); } 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) n = (tab = resize()).length; //根据元素对象计算好的哈希值再进行一次相关的计算,计算出该元素的存储位置 //如果该元素的位置是null,表示该位置没有元素,可以存储 //就创建新的结点,存储元素 //通过观察源码发现,HashSet的add()方法一定和HashCode()有关系 if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else { Node<K,V> e; K k; //存入的元素和以前该位置的元素的哈希值进行比较 //如果哈希值不同,继续向下执行,把元素添加集合中 //如果哈希值一,会调用对象的equals()方法进行比较 //如果比较equals()方法返回的是false,会继续向下执行,然后将元素插入到集合中 //如果比较equals()方法返回的是true,说明元素的哈希值和内容都一样,表示元素重复了 //其实也就是不赋值 if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; else if (p instanceof TreeNode) e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else { for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); 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; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; if (++size > threshold) resize(); afterNodeInsertion(evict); return null; } }
LinkedHashSet:底层是哈希表和链表组成,线程是不安全的,效率高
哈希表保证元素的唯一性
链表保证的元素的顺序、有序(存储和取出的顺序一致)
import java.util.LinkedHashSet; /* LinkedHashSet:底层是哈希表和链表组成 哈希表保证元素的唯一性 链表保证的元素的顺序、有序(存储和取出的顺序一致) */ public class LinkeSerDemo { public static void main(String[] args) { LinkedHashSet<String> strings = new LinkedHashSet<>(); strings.add("hello"); strings.add("world"); strings.add("bigdata"); strings.add("java"); strings.add("hello"); strings.add("hello"); for (String string : strings) { System.out.println(string); } } } /* 运行结果: hello world bigdata java */
TreeSet:底层数据结构是红黑树(红黑树是一种自平衡的二叉树),线程不安全,效率高
-
请注意,此实现不同步。 如果多个线程并发访问树,并且至少有一个线程修改该集合,则必须在外部进行同步。
- 排序的方式有两种:
自然排序 comparable (无参构造 传入的数据类型具备实现了Comparable接口)
比较器排序 comparator (在创建集合的时候,调用带参数的构造方法,这个参数是实现了Comparator接口的子类对象)
- TreeSet的俩个特点:排序和唯一
TreeSet中add源码分析:
public interface Collection<E>{ ... } public interface Set<E> extends Collection<E>{ ... } public abstract class AbstractSet<E> implements Set<E> { ... } public class TreeSet<E> extends AbstractSet<E> implements NavigableSet<E>{ private transient NavigableMap<E,Object> m; public TreeSet() { this(new TreeMap<E,Object>()); } public boolean add(E e) { return m.put(e, PRESENT)==null; } } public class TreeMap<K,V> implements NavigableMap<K,V>{ public TreeMap() { comparator = null; } public V put(K key, V value) { //造一个根 Entry<K,V> t = root; //在还没有元素插入的时候,树的根是null //造一个实际的根出来 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的值是null 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 { //key就是想要添加到集合中的元素值 if (key == null) throw new NullPointerException(); @SuppressWarnings("unchecked") //String k = (String) key; //key本身例子是Integer,Integer类实现了Comparable < Integer >接口 //所以可以进行向下转型 //Comparable是自然比较 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; } }
自然排序
/*
TreeSet: 元素唯一,且元素的顺序可以按照某种规则进行排序
排序的方式有两种:
自然排序 comparable 这里通过无参构造默认comparator为null
比较器排序 comparator
TreeSet的俩个特点:排序和唯一
*/
public class TreeSet {
public static void main(String[] args) {
//创建集合对象
//构造一个新的,空的树组,根据其元素的自然排序进行排序
//无参构造内部元素默认是自然排序,无参构造默认comparator为null
java.util.TreeSet<Integer> integers = new java.util.TreeSet<>();
integers.add(11);
integers.add(23);
integers.add(15);
integers.add(8);
integers.add(89);
integers.add(11);
integers.add(18);
integers.add(23);
System.out.println(integers);//[8, 11, 15, 18, 23, 89]
}
}
TreeSet集合存储自定义对象并遍历
/* TreeSet集合存储自定义对象并保证排序和唯一 1、如果什么都没告诉我们排序规则,是什么排序? 自然排序,按照年龄从小到大进行排序 2、元素什么什么情况下才算唯一呢? 成员变量的值都相同的情况算唯一的,就是同一个元素 */ public class TreeSet1 { public static void main(String[] args) { //创建集合对象 TreeSet<Student> students = new TreeSet<>(); //创建学生类对象 Student s1 = new Student("张三", 18); Student s2 = new Student("李四", 19); Student s3 = new Student("王五", 20); Student s4 = new Student("赵六", 17); Student s5 = new Student("田七", 25); Student s6 = new Student("张三", 19); //将学生类对象存入集合中 students.add(s1); students.add(s2); students.add(s3); students.add(s4); students.add(s5); students.add(s6); //遍历集合 for (Student student : students) { System.out.println(student.getName() + "---" + student.getAge()); } } }
public class Student implements Comparable<Student>{ private String name; private int age; public Student() { } public Student(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", age=" + age + '}'; } @Override public int compareTo(Student o) { // return 0; // return 1 ; // return -1; //这里返回什么,其实应该是根据我的规则来进行排序 //我们明确知道是按照年龄的大小,从小到大排序 //在分析的时候,要注意什么是主要条件,什么是次要条件,缺一不可 int i = this.age - o.age; //如果年龄相同,不代表是同一个人,还要继续比较姓名是否相同 int i1 = i == 0 ? this.name.compareTo(o.name):i; return i1; } }
-
- 比较器排序
在创建集合的时候,调用带参数的构造方法,这个参数是实现了Comparator接口的子类对象
/* TreeSet集合保证元素的排序和唯一: 保证唯一的原理:是根据比较的结果是否是0来决定的 排序: 1、自然排序(传入的数据类型具备实现了Comparable接口) 2、比较器排序 在创建集合的时候,调用带参数的构造方法,这个参数是实现了Comparator接口的 子类对象 */ import java.util.Comparator; import java.util.TreeSet; public class TreeSet3 { public static void main(String[] args) { //TreeSet(Comparator<? super E> comparator) //构造一个新的,空的树集,根据指定的比较器进行排序。 // TreeSet<Student3> students = new TreeSet<>(new Mycomparator()); //匿名内部类的形式实现Comparator接口 省去创建一个实现接口类MyComparator TreeSet<Student3> students = new TreeSet<>(new Comparator<Student3>() { @Override public int compare(Student3 o1, Student3 o2) { // return 0; int i = o1.getName().length() - o2.getName().length(); //姓名的长度相同不代表内容也相同 int i1 = i == 0 ? o1.getName().compareTo(o2.getName()) : i; //姓名的长度和内容都一样,但是年龄可能不一样 //只有当姓名和年龄都一样的时候才判断为同一个人,才算重复 int i2 = i1 == 0 ? o1.getAge() - o2.getAge() : i1; return i2; } }); //创建学生类对象 Student3 s1 = new Student3("张老三", 18); Student3 s2 = new Student3("李四", 19); Student3 s3 = new Student3("王五六七", 20); Student3 s4 = new Student3("赵六", 17); Student3 s5 = new Student3("田", 25); Student3 s6 = new Student3("张三", 19); //将学生类对象存入集合中 students.add(s1); students.add(s2); students.add(s3); students.add(s4); students.add(s5); students.add(s6); //根据姓名的长度来排序 for (Student3 student : students) { System.out.println(student); } } }
public class Student3 { private String name; private int age; public Student3() { } public Student3(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Student2{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
import java.util.Comparator; public class Mycomparator implements Comparator<Student3> { @Override public int compare(Student3 o1, Student3 o2) { // return 0; int i = o1.getName().length()-o2.getName().length(); //姓名的长度相同不代表内容也相同 int i1 = i == 0 ? o1.getName().compareTo(o2.getName()) : i; //姓名的长度和内容都一样,但是年龄可能不一样 //只有当姓名和年龄都一样的时候才判断为同一个人,才算重复 int i2 = i1 == 0 ? o1.getAge() - o2.getAge() : i1; return i2; } }
题目:
键盘录入5个学生信息(姓名,语文成绩,数学成绩,英语成绩),
按照总分从高到低输出到控制台
import java.util.Comparator; import java.util.Scanner; import java.util.TreeSet; /* 键盘录入5个学生信息(姓名,语文成绩,数学成绩,英语成绩), 按照总分从高到低输出到控制台 1.定义学生类 2.创建集合 */ public class TreeSetText { public static void main(String[] args) { //创建一个TreeSet集合对象 TreeSet<Student4> students = new TreeSet<>(new Comparator<Student4>() { @Override public int compare(Student4 o1, Student4 o2) { //总分从高到低 int i = o2.getNum() - o1.getNum(); //总分一样不一定语文成绩一样 int i2 = i == 0 ? o1.getChinese() - o2.getChinese() : i; //总分一样不一定数学成绩一样 int i3 = i2 == 0 ? o1.getMath() - o2.getMath() : i2; //总分一样不一定英语成绩一样 int i4 = i3 == 0 ? o1.getEnglish() - o2.getEnglish() : i3; //各科成绩都一样的话,姓名不一定一样 int i5 = i4 == 0 ? o1.getName().compareTo(o2.getName()) : i4; return i5; } }); //创建Scanner对象 Scanner sc = new Scanner(System.in); System.out.println("开始录入学生成绩"); for (int i = 1; i < 6; i++) { System.out.println("请输入第 "+ i +" 个学生的姓名:"); String name = sc.next(); System.out.println("请输入学生 "+ name +" 的语文成绩:"); int chinese = sc.nextInt(); System.out.println("请输入学生 "+ name +" 的数学成绩:"); int math = sc.nextInt(); System.out.println("请输入学生 "+ name +" 的英语成绩:"); int english = sc.nextInt(); //创建学生对象,将输入值赋值给成员变量 Student4 s = new Student4(); s.setName(name); s.setChinese(chinese); s.setMath(math); s.setEnglish(english); //将学生对象添加到集合中 students.add(s); if(i == 5){ System.out.println("学生信息录入完毕!"); } } System.out.println("============================================"); System.out.println("学生信息如下:"); System.out.println("姓名\t语文成绩\t数学成绩\t英语成绩\t总分"); //遍历学生信息 for (Student4 student : students) { System.out.println(student.getName()+"\t"+student.getChinese()+"\t" +student.getMath()+"\t"+student.getEnglish()+"\t"+student.getNum()); } } }
public class Student4 { private String name; private int chinese; private int math; private int english; public Student4() { } public Student4(String name, int chinese, int math, int english) { this.name = name; this.chinese = chinese; this.math = math; this.english = english; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getChinese() { return chinese; } public void setChinese(int chinese) { this.chinese = chinese; } public int getMath() { return math; } public void setMath(int math) { this.math = math; } public int getEnglish() { return english; } public void setEnglish(int english) { this.english = english; } public int getNum() { return chinese + math + english; } @Override public String toString() { return "Student4{" + "name='" + name + '\'' + ", chinese=" + chinese + ", math=" + math + ", english=" + english +", 总分="+getNum()+ '}'; } }