导航:
1.map
1.1 HashMap的底层实现原理?(以jdk7为例)
1.2 HashMap的底层实现原理?(以jdk8为例)
1.3 Map实现类:Properties
2.内部类
3.Java 8中关于接口的改进
Map 双列数据,存储key-value对的数据 接口就是规范
1/-Map:
双列数据,存储key-value对的数据 —类似于高中的函数: y = f(x)
2/-HashMap:
作为Map的主要实现类;线程不安全的,效率高;存储null的key和value
HashMap的底层:数组+链表(jdk7及之前)
数组+链表+红黑树(jdk 8)
3/-linkedHashMap:
保证在遍历map元素时,可以按照添加的顺序实现遍历。
原因:
在原有的HashMap 底层结构基础上,
添加了一对指针,指向前一个和后一个元素。
对于频繁的遍历操作,此类执行效率高于HashMap。
2/-TreeMap:
保证按照添加的key-value对进行排序,实现排序遍历。
此时考虑key的自然排序或定制排序
底层使用红黑树
2/-Hashtable:
作为古老的实现类;线程安全的,效率低;不能存储null 的key和value
3 /-Properties:
常用来处理配置文件。key和vaLue 都是String类型
快捷键:
ctrl shift t 查询类
ctrl o 查看类里面的所有方法
alt + 左右 向前,向后
面试题:
- HashMap的底层实现原理?
- HashMap和Hashtable的异同?
- CurrentHashMap与Hashtable的异同? ( 暂时不讲)
Map结构的理解:
Map中的key: 无序的、不可重复的,使用Set存储所有的key
—> key所在的类要重写equals()和hashCode() ( 以HashMap为例)
Map中的value: 无序的、可重复的,使用collection存储所有 的value
—>value所在的类要 重写equals()
一个键值对: key-value构成了一个Entry对象。
Map中的entry: 无序的、不可重复的,使用Set 存储所有的entry
HashMap的底层实现原理?(以jdk7为例)
HashMap map = new HashMap():
在实例化以后,底层创建了长度是16的一维数组Entry[] table.
…可能已经执行过多次put…
map. put (key1, value1):
首先, 调用key1 所在类的hashCode()计算key1哈希值,
此哈希值经过某种算法计算以后,得到在Entry 数组中的存放位置。
如果此位置上的数据为空 ,此时的key1-value1添加成功。----情况1
如果此位置上的数据不为空 ,
( 意味着此位置上存在一个或多个数据(以链表形式存在)),
比较key1和已经存在的一一个或多个数据的哈希值:
==》 如果 key1的哈希值与已经存在的数据的哈希值都不相同,
此时key1-value1 添加成功。---- 情况2
如果 key1的哈希值和已经存在的某一个数据(key2-value2)的哈希值相同,
继续比较: 调用key1所在类的equals (key2)方法,比较:
== 》如果 equals()返回false :此时key1-value1添加成功。---- 情况3
如果 equals().返回true:使用value1替换vaLue2。
补充:
关于情况2和情况3: 此时key1 -value1和原来的数据以链表的方式存储。
扩容:
在不断的添加过程中,会涉及到扩容问题,
当超出临界值(且要存放的位置非空)时,
默认的扩容方式: 扩容为原来容量的2倍,并将原有的数据复制过来。
(一般到了大于12,提前扩容)
HashMap的底层实现原理?(以jdk8为例)
jdk8相较于jdk7在底层实现方面的不同:
- new HashMap():底层没有创建一个长度为16的数组
- jdk 8底层的数组是: Node[], 而非Entry[]
- 首次调用put()方法时,底层创建长度为16的数组
- jdk7底层结构只有:数组+链表。jdk8中底层结构:数组+链表+红黑树。
当数组的某一个索引位置上的元素以链表形式存在的数据个数> 8
且当前数组的长度> 64时,
此时此索引位置上的所有数据改为使用红黑树存储。
HashMap源码中的重要常量:
- DEFAULT_ INITIAL CAPACITY : HashMap的默认容量, 16
- DEFANLT_ LOAD_FACTOR:HashMap的默认加载因子:0.75
- threshold:扩容的临界值,=容量*填充因子:16*0.75=>12
- TREEIFY_THRESHOLD:
Bucket中链表长度大于该默认值,转化为红黑树:8 - MIN_REEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量:64
Map实现类:Properties
-
Properties类是Hashtable的子类,该对象用于处理属性文件
-
由于属性文件里的key、 value 都是字符串类型,
所以Properties里的key和value都是字符串类型 -
存取数据时,建议使用setProperty(String key, String value)方法
和getProperty(String key)方法
Properties properties = new Properties();//实例化Properties对象
properties.load(new FileInputStream("jdbc.properties");//加载(读取)文件(以流的形式)
String user = properties.getProperty("user");//获取文件中的数据
利用反射,获取类的对象
Class c = Class.forName(user); //类类型的对象
Object o = c.newInstance();//类的对象
System.out.println(o);
==》 灵活性高 只需要改配置文件里的 不需要jdk环境
如果出现乱码:
打勾勾上,就不会出现乱码了,并且文件要重新造
内部类
当一个事物的内部,还有一个部分需要一个完整的结构进行描述,
而这个内部的完整的结构又只为外部事物提供服务,
那么整个内部的完整结构最好使用内部类。
●在Java中,允许一个类的定义位于另一个类的内部,
前者称为内部类,后者称为外部类。
●Inner class 一般用在定义它的类或语句块之内,
在外部引用它时必须给出完整的名称。
Inner class的名字不能与包含它的外部类类名相同;
分类:
成员内部类(static成员内部类和非static成员内部类)
局部内部类(不谈修饰符)、匿名内部类
- Java中允许将一个类A声明在另一个类B中,
则类A就是内部类,类B称为外部类 - 内部类的分类:成员内部类(静态、非静态)VS局部内部类(方法内、代码块内、构造器内)
- 成员内部类:
一方面,作为外部类的成员:
1.调用外部类的结构
2.可以被static修饰
3.可以被4种不同的权限修饰
另一方面,作为一个类:
1.类内可以定义属性、方法、构造器等
2.可以被final修饰, 表示此类不能被继承。
言外之意,不使用final,就可以被继承
3.可以被abstract修饰 - 关注如下的3个问题
4.1如何实例化成员内部类的对象
4.2如何在成员内部类中区分调用外部类的结构
4.3开发中局部内部类的使用 见 InnerClassTest1
public class InnerClassTest {
public static void main(String[] args) {
//创建Dog实例(静态成员内部类)
Person.Dog dog = new Person.Dog();
dog.show();// ==> 卡拉是条狗
// 创建Bird实例(非静态成员内部类)
// Person.Bird bird = new Person.Bird();错误的 外部类实例了才能调
Person p = new Person();
Person.Bird bird = p.new Bird();//有了对象之后调内部结构
bird.sing();// ==> 我是一只小小鸟 人吃饭 0
bird.display("z");// ==> z y x
}
}
class Person{//外部类不能被static修饰 并且只能是默认和公共
String name = "x";
int age;
public void eat(){
System.out.println("人吃饭");
}
//静态成员内部类
static class Dog{
String name;
int age;
public void show(){
System.out.println("卡拉是条狗");
}
}
//非静态成员内部类
class Bird{
String name = "y";
public Bird(){}
public void sing(){
System.out.println("我是一只小小鸟");
eat();
//没有重名 可以省略前面的 是等价于Person.this.eat(); 调用外部类的非静态属性
System.out.println(age);
}
public void display(String name){
System.out.println(name);//方法的形参 z
System.out.println(this.name);
// display 所在方法类的name 内部类的属性 y
System.out.println(Person.this.name);//外部类的属性 z
}
}
public void method(){
class AA{}//局部内部类
}
{
class BB{}//局部内部类
}
public Person(){
class CC{}//局部内部类
}
}
InnerClassTest1
public class InnerClassTest1 {
//开发中很少见
public void method(){
class AA{}//局部内部类
}
//里边用,外部不用
//返回了一个实现了Compareable接口的类的对象
public Comparable getComparable(){
//创建一个实现了Compareable接口的类:局部内部类
//方式一:
// class MyComparable implements Comparable {
// @Override
// public int compareTo(Object o) {
// return 0;
// }
// }
// return new MyComparable();//返回个对象
//方式二:创建了实现上面接口的匿名实现类的匿名对象
return new Comparable() {
@Override
public int compareTo(Object o) {
return 0;
}
};
}
}
Java 8中关于接口的改进
Java 8中,你可以为接口添加静态方法和默认方法。
从技术角度来说,这是完全合法的,
只是它看起来违反了接口作为一个抽象定义的理念。
静态方法: 使用static关键字修饰。可以通过接口直接调用静态方法,
并执行其方法体。我们经常在相互一起使用的类中使用静态方法。
你可以在标准库中找到像Collection/Collections
或者Path/Paths这样成对的接口和类。
默认方法: 默认方法使用default关键字修饰。
可以通过实现类对象来调用。
我们在已有的接口中提供新方法的同时,还保持了与旧版本代码的兼容性。
比如:
java 8 API中对CollectionListComparator等接口提供了丰富的默认方法。
如何定义接口: 定义接口中的成员
JDK7及以前: 只能定义全局常量和抽象方法
全局常量: public static final的。 但是书写时,可以省略不写
抽象方法: public abstract的。
JDK8:
除了定义全局常量和抽象方法之外,还可以定义静态方法、默认方法(略)
CompareA
public interface CompareA {
//静态方法
public static void method1(){
System.out.println("CompareA:北京");
}
//默认方法
public default void method2(){
System.out.println("CompareA:上海 ");
}//Public 可以省略
default void method3(){
System.out.println("CompareA:上海");
}
}
CompareB
public interface CompareB {
default void method3(){
System.out.println("CompareB:上海");
}
}
SuperClass
public class SuperClass {
public void method3(){
System.out.println("SuperClass:北京");
}
}
SubClassTest
public class SubClassTest {
public static void main(String[] args) {
SubClass s = new SubClass();
// s.method1();
// 1.接口中定义的静态方法,只能通过接口来调用 ==>利用这个可以变成工具类
CompareA.method1();// ==》CompareA:北京
// 2.通过实现类的对象, 可以调用接口中的默认方法
// 如果实现类重写了接口中的默认方法,调用时,依然调用的是重写的方法
s.method2();// ==》SubClass:上海
// 3.如果子类(或实现类)继承的父类和实现的接口中声明了同名同参数的方法,
// 那么子类在没有重写此方法的情况下,默认调用的是父类中的同名同参数的方法。--》类优先原则 仅仅 针对当前有效
s.method3();// ==》SuperClass:北京 ==> SubClass:深圳
// 4.如果实现类实现了多个接口,而这多个接口中定义了同名同参数的默认方法
// 那么在实现类没有重写此方法的情况下,报错. ==>接口冲突
// 如果你把extends SuperClass去掉 就会报错 因为不知道调哪个method3()
// 结果就是: 必须在实现类中重写此方法
}
}
class SubClass extends SuperClass implements CompareA,CompareB {
public void method2(){
System.out.println("SubClass:上海 ");
}
public void method3(){
System.out.println("SubClass:深圳");
}
// 5.如何在子类(实现类)的方法中调用父类接口中被重写的方法
public void myMethod(){
method3();//调用自己定义的重写的方法
super.method3();//调用的是父类中声明的
//调用接口中的默认方法
CompareA.super.method3();
CompareB.super.method3();
}
}
----2021.11.08



