简单了解红黑树
Collection
Collection方法
注意contains(All),remove(All)这种,比较的是内容而不是地址(前提是你这个类重写了equals方法),所以有下图:
contains方法是用传入参数的equals方法与集合中的每个元素进行比较,也就是说,集合中的每个元素都要进入contains的参数的equals方法中比较,直到找到相同元素才停止,见下图:
- equals:判断当前集合和形参元素是否相同(相同则返回True)
- retain:差集
- toArray:集合转数组
- Arrays.asLIst(待转换数组):数组转集合
List<String> list = Arrays.asList(new String[]{"ha", "hei", "ho"});
System.out.println(list);
结果是:
[ha, hei, ho]
但是,如果你想这样:
List<int[]> list1 = Arrays.asList(new int[]{12, 34, 56});
System.out.println(list1);
结果:
[[I@10f87f48]
java会认为new int[]{12, 34, 56}是一个元素,如果想让java识别为多个元素,目前为止可以用如下方法:
List list1 = Arrays.asList(new Integer[]{12, 34, 56});
// 或 List list1 = Arrays.asList(12,34,56);
System.out.println(list1);
结果:
[12, 34, 56]
- iterator:迭代器
Iterator iterator = c2.iterator();
// 方式1
for (int i = 0; i < c2.size(); i++) {
System.out.println(iterator.next());
}
// 方式 2
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
- iterator的remove方法
LIst(jdk1.2出现)接口及其实现类:ArrayList、LinkedList以及Vector
首先这三者都实现类List接口,因此都存储有序的、可重复的数据。
ArrayList (主要实现类 jdk1.2及之后)
线程不安全,效率高,底层使用Object[]存储。
我们之前用的数组一旦定义了大小就无法改变,而ArrayList这个玩意儿可以动态的扩容,下面来看一下它是如何扩容的:
注意: 其扩容机制在jdk7和jdk8稍有不同,先来看jdk7:
- 当List list = new ArrayList()之后,这时是调用了空参构造器,那么底层是创建了长度为10的Object类型数组,叫elementData
- 当list.add(元素)时,如果elementData的容量不够了,默认情况扩容到原来容量的1.5倍,同时将原来数组中的元素拷贝到新的数组中
**建议:**如果我们能大致的估计出元素个数,那么可以用带参的构造器,省得他总扩容复制元素。
再来看看jdk8:
- 当List list = new ArrayList()之后,这时是调用了空参构造器,那么底层会将Object[] elementData初始化为{},并没有创建长度
- 当第一次调用add时,底层才会创建长度为10的Object数组
后续的操作与jdk7是相同的。
LinkedList(jdk1.2及之后)
底层使用双向链表,插入、删除效率比ArrayList高。
它的源码倒是没啥东西:
- 里面没有ArrayList的Object数组,而是定义了Node类型的first和last指针,默认值是null
Vector(jdk1.0, 比较古老不常用)
线程安全,效率低,底层使用Object[]存储。
它的源码没咋改动过,通过Vector()构造器创建对象时,在底层创建了长度为10的数组,另外在扩容时,默认扩为原来数组的2倍。
List是有序的,因此一些操作不能在Collection中定义。
注意这个remove,它和Collection里那个remove的区别,后者参数是Object类型的,而这里的remove参数是int类型的,此外,ArrayList有两个remove方法
一道List的面试题
说出结果:
这道题考察的是remove的两个方法:remove(int)和remove(Object),add方法传入参数只能是Object,而remove传一个int参数不会自动装箱(因为人家就有int的那个方法),所以这个remove(2)删除的是索引,结果为12;如果你想删内容,那么要这样:remove(new Integer(2));
https://www.bilibili.com/video/BV1Kb411W75N?p=534
Set
我们发现使用add方法时,如果是添加多个相同的元素,如Integer、String这种,则只会保存一个。但添加有些元素,比如我们自己定义了一个类时(若不指定hashCode方法),可以添加多个在equals看来相等的元素:
public class HashSetTest {
public static void main(String[] args) {
Set set = new HashSet();
set.add(1);
set.add(2);
set.add("3");
set.add("3");
set.add(1);
set.add(new Person("liu", 20));
set.add(new Person("liu", 20));
Iterator iterator = set.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
}
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age &&
name.equals(person.name);
}
// @Override
// public int hashCode() {
// return Objects.hash(name, age);
// }
}
结果:
1
2
3
Person{name='liu', age=20}
10
Person{name='liu', age=20}
我们来分析一下:
LinkedHashSet
我们把上述代码改成这样:
public class LinkedHashSet {
public static void main(String[] args) {
Set set = new java.util.LinkedHashSet();
set.add(10);
set.add(2);
set.add("3");
set.add("3");
set.add(1);
set.add(new Person1("liu", 20));
set.add(new Person1("liu", 20));
Iterator iterator = set.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
}
class Person1 {
private String name;
private int age;
public Person1(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person1 person = (Person1) o;
return age == person.age &&
name.equals(person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
只说把HashSet改成了LinkedHashSet,但结果变得有序了:
但这并不是说LinkedHashSet就是有序的,它是HashSet的子类。
TreeSet
这个集合可以给输入的数据排序,但你得实现比较器。也就是说在TreeSet中,判断两元素是否相等不再使用equals而是使用比较器。
我们还是用之前自定义的类:
public class TreeSetTest {
public static void main(String[] args) {
TreeSet treeSet = new TreeSet();
treeSet.add(new Person2("All", 20));
treeSet.add(new Person2("Peter", 17));
treeSet.add(new Person2("Bob", 36));
treeSet.add(new Person2("Emi", 55));
Iterator iterator = treeSet.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
结果:
class treeset.java.Person2 cannot be cast to class java.lang.Comparable
因为我们自定义的类没有实现比较器,下面来实现:
class Person2 implements Comparable{
private String name;
private int age;
public Person2(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
treeset.java.Person2 person = (treeset.java.Person2) o;
return age == person.age &&
name.equals(person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public int compareTo(Object o) {
if (o instanceof Person2) {
Person2 p = (Person2) o;
return this.name.compareTo(p.name);
}else {
throw new RuntimeException("类型不匹配!");
}
}
}
结果:
Person{name='All', age=20}
Person{name='Bob', age=36}
Person{name='Emi', age=55}
Person{name='Peter', age=17}
但当传入两个同名的人而年龄不同时,只会保存一个,这种情况下如果你想把两个都保存就要写二级排序:
@Override
public int compareTo(Object o) {
if (o instanceof Person2) {
Person2 p = (Person2) o;
int com = this.name.compareTo(p.name);
if (com != 0) {
return com;
}else {
return Integer.compare(this.age, p.age);
}
}else {
throw new RuntimeException("类型不匹配!");
}
}
我们当然也可以使用定制排序Comparator:
Comparator com = new Comparator(){
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof Person2 && o2 instanceof Person2) {
Person2 p1 = (Person2) o1;
Person2 p2 = (Person2) o2;
return Integer.compare(p1.getAge(), p2.getAge());
}else {
throw new RuntimeException("类型不匹配!");
}
}
};
TreeSet treeSet = new TreeSet(com);
treeSet.add(new Person2("All", 20));
treeSet.add(new Person2("Peter", 17));
treeSet.add(new Person2("Bob", 36));
treeSet.add(new Person2("Emi", 55));
treeSet.add(new Person2("Bob", 55));
Iterator iterator = treeSet.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
结果:
Person{name='Peter', age=17}
Person{name='All', age=20}
Person{name='Bob', age=36}
Person{name='Emi', age=55}
Set几道面试题
https://www.bilibili.com/video/BV1Kb411W75N?p=547
这题太骚了…
ArrayList 源码分析
LinkedList及源码分析
vector及源码
面试题 Collection List下三种结构的异同
Map
HashMap及底层原理
jdk7:
jdk8:
源码分析:
jdk7:
https://www.bilibili.com/video/BV1Kb411W75N?p=552
jdk8:
https://www.bilibili.com/video/BV1Kb411W75N?p=553
LinkedHashMap及底层原理
https://www.bilibili.com/video/BV1Kb411W75N?p=554
==其实HashSet和HashMap很像,new HashSet的时候也是用HashMap生成的,只不过把HashMap中的key作为HashSet中的值而已,至于value,设置成了static的Object对象,HashSet中所有的值的value都指向它(没啥意义)。
Map中的方法
在Map中没有迭代器了,那个只能在Collection中使用,但Map中的key是用set存储的,value是用collection存储的,键值对(entry/node)是用set存储的。因此只要能得到它们,想必也能访问Map了。
获取Map中的key:
public static void main(String[] args) {
Map map = new HashMap();
map.put(null, 5);
map.put("AA", 6);
map.put("Lu", 11);
map.put("HH", 37.5);
map.put("Pl", 0.44f);
Set set = map.keySet();
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
// System.out.println(map);
}
获取Map中的value:
Collection values = map.values();
for (Object obj : values) {
System.out.println(obj);
}
遍历所有的key-value:
Set set1 = map.entrySet();
Iterator iterator1 = set1.iterator();
while (iterator1.hasNext()) {
System.out.println(iterator1.next());
}
结果:
null=5
AA=6
HH=37.5
Lu=11
Pl=0.44
也可以做一个强制转换,分别得到key和value:
或者:
TreeMap
定制排序:
自然排序:
由于我们put的数据是User类型的,因此要我们自己实现CompareTo接口:
HashTable 及其子类 Properties
HashTable已经不怎么用了,关注一下它和HashMap的区别即可,类似Vector和ArrayList。
下面先简单了解一下Properties:
Collections工具类
这就和操作数组的工具类Arrays很相似了:
其中,使用copy的时候要注意目标List的size:
还有:
前面说过,ArrayList、HashSet和HashMap是线程不安全的,当你想使用线程安全时就把他们作为参数传到上面的方法中,得到的返回值就是线程安全的。
几道题目