Java基础细节(持续更新中)
一、JDK 和 JRE
二.开发步骤
步骤:
1. 将 Java 代码编写到扩展名为 .java 的文件中。
2. 通过 javac 命令对该 java 文件进行编译。
3. 通过 java 命令对生成的 class 文件进行运行。
三、所在内存位置
- 局部变量 -> 栈
- 常量 -> 常量池
- 类变量(静态变量) -> 静态存储区
- new 出来的对象 -> 堆区
四、Java数据类型
上图说明:
1. java 数据类型分为两大类基本数据类型, 引用类型
2. 基本数据类型有8 中数值型[byte , short , int , long , float ,double] char , boolean
3. 引用类型[类,接口, 数组]
1、整型类型
2、浮点型
五、 基本数据类型转换
1、自动类型转化
//自动类型转换细节
public class AutoConvertDetail {
//编写一个main 方法
public static void main(String[] args) {
//细节1: 有多种类型的数据混合运算时,
//系统首先自动将所有数据转换成容量最大的那种数据类型,然后再进行计算
int n1 = 10; //ok
//float d1 = n1 + 1.1;//错误n1 + 1.1 => 结果类型是double
//double d1 = n1 + 1.1;//对n1 + 1.1 => 结果类型是double
float d1 = n1 + 1.1F;//对n1 + 1.1 => 结果类型是float
//细节2: 当我们把精度(容量)大的数据类型赋值给精度(容量)小的数据类型时,
//就会报错,反之就会进行自动类型转换。
//
//int n2 = 1.1;//错误double -> int
//细节3: (byte, short) 和char 之间不会相互自动转换
//当把具体数赋给byte 时,(1)先判断该数是否在byte 范围内,如果是就可以
byte b1 = 10; //对, -128-127
// int n2 = 1; //n2 是int
// byte b2 = n2; //错误,原因: 如果是变量赋值,判断类型
// char c1 = b1; //错误, 原因byte 不能自动转成char
//细节4: byte,short,char 他们三者可以计算,在计算时首先转换为int 类型
byte b2 = 1;
byte b3 = 2;
short s1 = 1;
//short s2 = b2 + s1;//错, b2 + s1 => int
int s2 = b2 + s1;//对, b2 + s1 => int
//byte b4 = b2 + b3; //错误: b2 + b3 => int
//
//boolean 不参与转换
boolean pass = true;
//int num100 = pass;// boolean 不参与类型的自动转换
//自动提升原则: 表达式结果的类型自动提升为操作数中最大的类型
//看一道题
byte b4 = 1;
short s3 = 100;
int num200 = 1;
float num300 = 1.1F;
double num500 = b4 + s3 + num200 + num300; //float -> double
}
}
2、强制类型转化
介绍
自动类型转换的逆过程,将容量大的数据类型转换为容量小的数据类型。使用时要加上强制转换符( ),但可能造成精度降低或溢出,格外要注意。
3、练习
六、运算符
% 取模,取余
在% 的本质看一个公式!!!! a % b = a - a / b * b
/**
* 演示算术运算符的使用
*/
public class ArithmeticOperator {
//编写一个main 方法
public static void main(String[] args) {
// /使用
System.out.println(10 / 4); //从数学来看是2.5, java 中2
System.out.println(10.0 / 4); //java 是2.5
// 注释快捷键ctrl + /, 再次输入ctrl + / 取消注释
double d = 10 / 4;//java 中10 / 4 = 2, 2=>2.0
System.out.println(d);// 是2.0
// % 取模,取余
// 在% 的本质看一个公式!!!! a % b = a - a / b * b
// -10 % 3 => -10 - (-10) / 3 * 3 = -10 + 9 = -1
// 10 % -3 = 10 - 10 / (-3) * (-3) = 10 - 9 = 1
// -10 % -3 = (-10) - (-10) / (-3) * (-3) = -10 + 9 = -1
System.out.println(10 % 3); //1
System.out.println(-10 % 3); // -1
System.out.println(10 % -3); //1
System.out.println(-10 % -3);//-1
//++的使用
//
int i = 10;
i++;//自增等价于i = i + 1; => i = 11
++i;//自增等价于i = i + 1; => i = 12
System.out.println("i=" + i);//12
/*
作为表达式使用
前++:++i 先自增后赋值
后++:i++先赋值后自增
*/
int j = 8;
//int k = ++j; //等价j=j+1;k=j;
int k = j++; // 等价k =j;j=j+1;
System.out.println("k=" + k + "j=" + j);//8 9
}
}
练习
//练习
public class ArithmeticOperatorExercise01 {
//编写一个main 方法
public static void main(String[] args) {
// int i = 1;//i->1
// i = i++; //规则使用临时变量: (1) temp=i;(2) i=i+1;(3)i=temp;
// System.out.println(i); // 1
// int i=1;
// i=++i; //规则使用临时变量: (1) i=i+1;(2) temp=i;(3)i=temp;
// System.out.println(i); //2
//
// 测试输出
int i1 = 10;
int i2 = 20;
int i = i1++;
System.out.print("i="+i);//10
System.out.println("i2="+i2);//20
i = --i2;
System.out.print("i="+i);//19
System.out.println("i2="+i2);//19
}
}
七、逻辑运算符
1、&& 和& 使用区别
1) &&短路与:如果第一个条件为false,则第二个条件不会判断,最终结果为false,效率高
2) & 逻辑与:不管第一个条件是否为false,第二个条件都要判断,效率低
2、|| 和| 使用区别
1) ||短路或:如果第一个条件为true,则第二个条件不会判断,最终结果为true,效率高
2) | 逻辑或:不管第一个条件是否为true,第二个条件都要判断,效率低
3) 开发中,我们基本使用||
3、练习1:
4、练习2:
八、排序
(一)、内部排序:
指将需要处理的所有数据都加载到内部存储器中进行排序。包括(交换式排序法、选择
式排序法和插入式排序法)。
(二)、外部排序:
数据量过大,无法全部加载到内存中,需要借助外部存储进行排序。包括(合并排序法和直接合并排序法)。
(三)、冒泡排序:
冒泡排序(Bubble Sorting)的基本思想是:通过对待排序序列从后向前(从下标较大的元素开始),依次比较相邻元素的值,若发现逆序则交换,使值较大的元素逐渐从前移向后部,就象水底下的气泡一样逐渐向上冒。
思路:
代码:
参考:Java十种排序实现
九、查找
1、顺序查找
2、二分法查找
十、方法的调用机制原理:(重要!-示意图!!!)
十一、成员方法传参机制(非常非常重要)
实列参考:Java的传参机制
1、基本数据类型的传参机制
内存图:
2、引用数据类型的传参机制
内存图:
3、练习
十二、构造器
十三、this关键字
十四、super关键字
十五、访问修饰符
修饰符 | 类内部 | 同一个包 | 不同包的子类 | 同一个工程 |
private | Yes | |||
default(缺省) | Yes | Yes | ||
protected | Yes | Yes | Yes | |
public | Yes | Yes | Yes | Yes |
十六、继承
继承的深入讨论/细节问题
十七、重写和重载
1、重写(Overriding)
重写(override):一般都是表示子类和父类之间的关系,其主要的特征是:方法名相同,参数相同,但是具体的实现不同。
重写的特征:
(1):方法名必须相同,返回值类型必须相同
(2):参数列表必须相同
(3):访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为public,那么在子类中重写该方法就不能声明为protected。
(4):子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为private和final的方法。
(5):构造方法不能被重写
2、重载(Overload)
重载发生在本类,方法名相同,参数列表不同,与返回值无关,只和方法名,参数列表,参数的类型有关.
重载(Overload):首先是位于一个类之中或者其子类中,具有相同的方法名,但是方法的参数不同,返回值类型可以相同也可以不同。
重载的特征:
(1):方法名必须相同
(2):方法的参数列表一定不一样。
(3):访问修饰符和返回值类型可以相同也可以不同。
3、区别
十八、多态
多态的前提是 :两个对象 ( 类 )存在继承关系
1、多态向上转型
1)本质:父类的引用指向了子类的对象
2)语法:父类类型 名称 = new 子类类型();
3)特点:编译类型看左边,运行类型看右边。
可以调用父类中的所以成员(需要遵守访问权限)
不能调用子类中特有成员
最终运行看子类的具体实现
2、多态向下转型
1)语法:子类类型 名称 = (子类类型) 父类引用;
2)只能强转父类的引用,不能强转父类的对象
3)要求父类的引用必须指向的是当前目标类型的对象
4)当向下转型后,可以调用子类类型中所有成员
十九、instanceOf
1、a instanceof A:判断对象a是否为A的实例,如果是返回true。
2、使用情景:避免在向下转型时出现异常,先进行判断。
二十、动态绑定与静态绑定机制
例题一
思考: 将子类方法sum()和sum1()注释后的结果是什么?
public class DynamicBinding {
public static void main(String[] args) {
//a 的编译类型 A, 运行类型 B
A a = new B();//向上转型System.out.println(a.sum());//40 -> 30
System.out.println(a.sum1());//?30-> 20
}
}class A {//父类public int i = 10;
//动态绑定机制:
public int sum() {//父类 sum()
return getI() + 10;//20 + 10
}
public int sum1() {//父类 sum1()
return i + 10;//10 + 10
}
public int getI() {//父类 getI
return i;
}
}class B extends A {//子类public int i = 20;
// public int sum() {
// return i + 20;
// }
public int getI() {//子类 getI()
return i;
}
// public int sum1() {
// return i + 10;
// }
}
总结:1、动态绑定发生在多态里并且要进行重写,当调用方法后会动态绑定到子类的方法,当注释掉子类重写方法后会动态绑定找到父类的方法。2、属性没有动态绑定
例题二
class Son extends Person{//子类public void say(){System.out.println("这是子类的say()");}
}public class Person{//父类private void say(){System.out.println("这是父类的say()");}public static void main(String[] args){Person person = new Son();person.say();//这是父类的say()}
}
为什么是父类的say()?
原因:它与继承无关。当say()是private时,方法通过编译器直接静态绑定实现的,编译器不知道一个叫Son的子类以及其他子类,所以只能调用Person的方法。如果将父类private改为public,此时的结果为:这是子类的say().
总结:静态绑定:private、static、final修饰的方法都是静态绑定的。静态方法(因为静态方法与类本身相关而不是与对象相关)、final方法(final修饰符表示方法在子类中不能被覆盖)和private方法(private修饰符表示方法在子类中不可见)是不能实现动态绑定效果的。
二十一、==和 equals
1、==
1)==:既可以判断基本数据类型,有可以判断引用数据类型
2)==:如果判断基本数据类型,判断的是值是否相等
3)==:如果判断引用数据类型,判断的是地址是否相等,即判断是否为同一个对象
2、eqauls
1)equals:是Object类中的方法,只能判断引用类型
2)默认判断的是地址是否相等,子类中往往重写该方法,用于判断内容是否相等
3、基本类型和包装类==和equals比较(重要)
实例参考:Java中基本类型和包装类的各类比较(==),以及包装类的对象缓存池
二十二、static、final、abstract、接口
1、static
总结:
1)static可以修饰属性和方法,不能修饰构造器
2)类方法和普通方法都随着类加载而加载:
类方法不能有this、super参数(this、super与对象有关)
普通方法隐含this、super参数
3)类方法可通过类名调用,也可通过对象名调用
4)类方法只能访问静态属性和静态方法(需遵守访问权限)
5)普通方法,既可以访问静态成员,也可以访问非静态成员
2、final
总结:
1)final可以修饰类、属性、方法,不能修饰构造器
2)final修饰的属性又叫常量,一般用大写XX_XX_XX来命名
3)final修饰的属性在定义时,必须赋初值,并且以后不能修改,赋值可以在以下位置:
①定义时:如public final double TAX_RATE=0.08;
②在构造器中
③在代码块中
4)如果final修饰的属性是静态的,则初始化位置只能是:
①定义时 ②在静态代码块 不能在构造器中赋值
5)final类不能继承,但可以实例化对象
6)如果类不是final类,但是含有final方法,则该方法虽然不能重写,但是可以被继承
7)一般来说,如果一个类已经是final类,就没有必要将方法修饰成final方法
8)包装类(Integer、Double、Float、Boolean等都是final),String也是fianl类
3、abstract
总结:
1)abstract可以修饰类、方法,不能修饰构造器
2)抽象类不能被实例化
3)抽象类不一定要有abstract方法
4)一旦类包含了abstract方法,则这个类必须声明成abstract类
5)抽象类可以有任意成员(本质还是类),比如:非抽象方法、构造器、静态属性等
6)抽象方法不能有实体
7)如果一个类继承了抽象类,则它必须重写抽象类的所以方法,或者将这个类声明成abstract类
8)抽象方法不能使用private、final和static修饰,因为这些关键字都是和重写相违背
4、接口
总结:
1)接口不能被实例化
2)接口中所有方法是public,abstract方法,可以省略。例如:public abstract void get()可以定义为void get()
3)一个普通类实现接口,就必须实现该接口的所有方法
4)抽象类实现接口,可以不用实现接口方法
5)一个类可实现多个接口
6)接口中的属性是public static final的,可以省略,而且必须初始化。例如:int a = 1,实际上是public static final int a = 1
7)接口中属性的访问形式:接口名.属性名
8)接口不能继承其他类,但可以继承多个接口
9)接口的修饰符只能是public和默认的,这点和类的修饰符一样
二十三、 代码块
练习
public class CodeBlockDetail04 {
public static void main(String[] args) {//(1) 进行类的加载//1.1 先加载 父类 A02 1.2 再加载 B02
//(2) 创建对象//2.1 从子类的构造器开始//new B02();//对象new C02();
}
}
class A02 { //父类private static int n1 = getVal01();
static {
System.out.println("A02 的一个静态代码块..");//(2)
}
{
System.out.println("A02 的第一个普通代码块..");//(5)
}
public int n3 = getVal02();//普通属性的初始化public static int getVal01() {
System.out.println("getVal01");//(1)
return 10;
}public int getVal02() {
System.out.println("getVal02");//(6)
return 10;
}
public A02() {//构造器//隐藏//super()
//普通代码和普通属性的初始化...... System.out.println("A02 的构造器");//(7)
}
}
class C02 {
private int n1 = 100;
private static int n2 = 200;
private void m1() {
}
private static void m2() {
}static {
//静态代码块,只能调用静态成员//System.out.println(n1);错误System.out.println(n2);//ok
//m1();//错误m2();
}
{
//普通代码块,可以使用任意成员System.out.println(n1);
System.out.println(n2);//ok
m1();
m2();
}
}
class B02 extends A02 { //
private static int n3 = getVal03();
static {
System.out.println("B02 的一个静态代码块..");//(4)
}
public int n5 = getVal04();
{
System.out.println("B02 的第一个普通代码块..");//(9)}
public static int getVal03() {
System.out.println("getVal03");//(3)
return 10;
}
public int getVal04() {
System.out.println("getVal04");//(8)
return 10;
}
//一定要慢慢的去品.. public B02() {//构造器//隐藏了//super()
//普通代码块和普通属性的初始化... System.out.println("B02 的构造器");//(10)
// TODO Auto-generated constructor stub
}
}
二十四、内部类
如果定义类在局部位置(方法中/代码块) :(1) 局部内部类 (2) 匿名内部类
定义在成员位置 (1) 成员内部类 (2) 静态内部类
1、局部内部类
/*** 演示局部内部类的使用*/
public class LocalInnerClass {public static void main(String[] args) {
//演示一遍Outer02 outer02 = new Outer02();outer02.m1();System.out.println("outer02 的 hashcode=" + outer02);}
}class Outer02 {//外部类private int n1 = 100;private void m2() {System.out.println("Outer02 m2()");}//私有方法public void m1() {//方法
//1.局部内部类是定义在外部类的局部位置,通常在方法
//3.不能添加访问修饰符,但是可以使用 final 修饰
//4.作用域 : 仅仅在定义它的方法或代码块中final class Inner02 {//局部内部类(本质仍然是一个类)
//2.可以直接访问外部类的所有成员,包含私有的private int n1 = 800;public void f1() {
//5. 局部内部类可以直接访问外部类的成员,比如下面 外部类 n1 和 m2()
//7. 如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,
// 使用 外部类名.this.成员)去访问
// Outer02.this 本质就是外部类的对象, 即哪个对象调用了 m1, Outer02.this 就是哪个对象System.out.println("n1=" + n1 + " 外部类的 n1=" + Outer02.this.n1);//100System.out.println("n1=" + n1 + " 内部类的 n1=" + Inner02.this.n1);//800System.out.println("Outer02.this hashcode=" + Outer02.this);//Outer02.this本质就是这个类m2();}}
//6. 外部类在方法中,可以创建 Inner02 对象,然后调用方法即可Inner02 inner02 = new Inner02();inner02.f1();}
}
2、匿名内部类(重要)
public static void main(String[] args) {Interface01 interface01 = new Interface01() {//多态,底层就是创建一个类实现interfacepublic void show() {//重写show方法System.out.println("这里使用了匿名内部类");}};//调用接口方法interface01.show();
}//定义一个接口
public interface Interface01 {void show();
}
3、成员内部类
public static void main(String[] args) {
Outer08 outer08 = new Outer08();
outer08.t1();
//外部其他类,使用成员内部类的三种方式// 第一种方式// outer08.new Inner08(); 相当于把 new Inner08()当做是 outer08 成员// 这就是一个语法,不要特别的纠结. Outer08.Inner08 inner08 = outer08.new Inner08();
inner08.say();
// 第二方式 在外部类中,编写一个方法,可以返回 Inner08 对象Outer08.Inner08 inner08Instance = outer08.getInner08Instance();
inner08Instance.say();
}
}
class Outer08 { //外部类private int n1 = 10;
public String name = "张三";
private void hi() {
System.out.println("hi()方法...");
}//1.注意: 成员内部类,是定义在外部内的成员位置上//2.可以添加任意访问修饰符(public、protected 、默认、private),因为它的地位就是一个成员public class Inner08 {//成员内部类private double sal = 99.8;
private int n1 = 66;
public void say() {
//可以直接访问外部类的所有成员,包含私有的//如果成员内部类的成员和外部类的成员重名,会遵守就近原则. //,可以通过 外部类名.this.属性 来访问外部类的成员System.out.println("n1 = " + n1 + " name = " + name + " 外部类的 n1=" + Outer08.this.n1);
hi();
}
}
//方法,返回一个 Inner08 实例public Inner08 getInner08Instance(){
return new Inner08();
}
//写方法public void t1() {
//使用成员内部类//创建成员内部类的对象,然后使用相关的方法Inner08 inner08 = new Inner08();
inner08.say();System.out.println(inner08.sal);
}
}
4、静态内部类
public class StaticInnerClass01 {
public static void main(String[] args) {
Outer10 outer10 = new Outer10();
outer10.m1();
//外部其他类 使用静态内部类//方式 1
//因为静态内部类,是可以通过类名直接访问(前提是满足访问权限)
Outer10.Inner10 inner10 = new Outer10.Inner10();
inner10.say();
//方式 2
//编写一个方法,可以返回静态内部类的对象实例. Outer10.Inner10 inner101 = outer10.getInner10();
System.out.println("============");
inner101.say();
Outer10.Inner10 inner10_ = Outer10.getInner10_();
System.out.println("************");
inner10_.say();
}
}
class Outer10 { //外部类private int n1 = 10;
private static String name = "张三";
private static void cry() {}
//Inner10 就是静态内部类//1. 放在外部类的成员位置//2. 使用 static 修饰//3. 可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员//4. 可以添加任意访问修饰符(public、protected 、默认、private),因为它的地位就是一个成员//5. 作用域 :同其他的成员,为整个类体static class Inner10 {
private static String name = "韩顺平教育";
public void say() {
//如果外部类和静态内部类的成员重名时,静态内部类访问的时,//默认遵循就近原则,如果想访问外部类的成员,则可以使用 (外部类名.成员)System.out.println(name + " 外部类 name= " + Outer10.name);
cry();
}
}
public void m1() { //外部类---访问------>静态内部类 访问方式:创建对象,再访问Inner10 inner10 = new Inner10();
inner10.say();
}
public Inner10 getInner10() {
return new Inner10();
}
public static Inner10 getInner10_() {
return new Inner10();
}
}
二十五、枚举类
1、自定义类实现枚举
public class Enumeration02 {
public static void main(String[] args) {
System.out.println(Season.AUTUMN);
System.out.println(Season.SPRING);
}
}
//演示字定义枚举实现class Season {//类private String name;
private String desc;//描述//定义了四个对象, 固定. public static final Season SPRING = new Season("春天", "温暖");
public static final Season WINTER = new Season("冬天", "寒冷");
public static final Season AUTUMN = new Season("秋天", "凉爽");
public static final Season SUMMER = new Season("夏天", "炎热");
//1. 将构造器私有化,目的防止 直接 new
//2. 去掉 setXxx 方法, 防止属性被修改//3. 在 Season 内部,直接创建固定的对象//4. 优化,可以加入 final 修饰符private Season(String name, String desc) {
this.name = name;
this.desc = desc;
}
public String getName() {
return name;
}
public String getDesc() {
return desc;
}
@Override
public String toString() {
return "Season{" +
"name='" + name + '\'' +
", desc='" + desc + '\'' +
'}';
}
}
2、使用 enum 关键字实现枚举
public class Enumeration03 {
public static void main(String[] args) {
System.out.println(Season2.AUTUMN);
System.out.println(Season2.SUMMER);
}
}
//演示使用 enum 关键字来实现枚举类enum Season2 {//类//定义了四个对象, 固定. // public static final Season SPRING = new Season("春天", "温暖");
// public static final Season WINTER = new Season("冬天", "寒冷");
// public static final Season AUTUMN = new Season("秋天", "凉爽");
// public static final Season SUMMER = new Season("夏天", "炎热");
//如果使用了 enum 来实现枚举类//1. 使用关键字 enum 替代 class
//2. public static final Season SPRING = new Season("春天", "温暖") 直接使用// SPRING("春天", "温暖") 解读 常量名(实参列表)
//3. 如果有多个常量(对象), 使用 ,号间隔即可//4. 如果使用 enum 来实现枚举,要求将定义常量对象,写在前面//5. 如果我们使用的是无参构造器,创建常量对象,则可以省略 ()
SPRING("春天", "温暖"), WINTER("冬天", "寒冷"), AUTUMN("秋天", "凉爽"), SUMMER("夏天", "炎热")/*, What()*/;
private String name;
private String desc;//描述private Season2() {//无参构造器}
private Season2(String name, String desc) {
this.name = name;
this.desc = desc;
}public String getName() {
return name;
}
public String getDesc() {
return desc;
}
@Override
public String toString() {
return "Season{" +
"name='" + name + '\'' +
", desc='" + desc + '\'' +
'}';
}
}
二十六、包装类
1、包装类和基本数据的转换
基本类型 → 包装类:调用valueOf方法,比如Integer.valueOf()
包装类 → 基本类型:调用xxxValue方法,比如i.intValue()
public class Integer01 {
public static void main(String[] args) {
//演示 int <--> Integer 的装箱和拆箱//jdk5 前是手动装箱和拆箱//手动装箱 int->Integer
int n1 = 100;
Integer integer = new Integer(n1);
Integer integer1 = Integer.valueOf(n1);
//手动拆箱//Integer -> int
int i = integer.intValue();
//jdk5 后,就可以自动装箱和自动拆箱int n2 = 200;
//自动装箱 int->Integer
Integer integer2 = n2; //底层使用的是 Integer.valueOf(n2)
//自动拆箱 Integer->int
int n3 = integer2; //底层仍然使用的是 intValue()方法}
}
2、包装类型和 String 类型的相互转换
包装类 → String:1)调用toString方法。2)调用String.valueOf()方法
String → 包装类:调用parsexxx方法,比如Integer.parseInt(str)
public class WrapperVSString {
public static void main(String[] args) {
//包装类(Integer)->String
Integer i = 100;//自动装箱
//方式 1
String str1 = i + "";
//方式 2
String str2 = i.toString();
//方式 3
String str3 = String.valueOf(i);
//String -> 包装类(Integer)
String str4 = "12345";
Integer i2 = Integer.parseInt(str4);//使用到自动装箱Integer i3 = new Integer(str4);//构造器System.out.println("ok~~");
}
}
二十七、String、StringBuffer、StringBuilder
(一)、String
1、创建 String 对象的两种方式
内存分布图:
练习一:
练习二
练习三
练习四
练习五
常用方法
public class StringMethod02 {
public static void main(String[] args) {
// 1.toUpperCase 转换成大写String s = "heLLo";
System.out.println(s.toUpperCase());//HELLO
// 2.toLowerCase
System.out.println(s.toLowerCase());//hello
// 3.concat 拼接字符串String s1 = "宝玉";
s1 = s1.concat("林黛玉").concat("薛宝钗").concat("together");
System.out.println(s1);//宝玉林黛玉薛宝钗 together
// 4.replace 替换字符串中的字符s1 = "宝玉 and 林黛玉 林黛玉 林黛玉";
//在 s1 中,将 所有的 林黛玉 替换成薛宝钗// s1.replace() 方法执行后,返回的结果才是替换过的. // 注意对 s1 没有任何影响String s11 = s1.replace("宝玉", "jack");
System.out.println(s1);//宝玉 and 林黛玉 林黛玉 林黛玉System.out.println(s11);//jack and 林黛玉 林黛玉 林黛玉// 5.split 分割字符串, 对于某些分割字符,我们需要 转义比如 | \\等String poem = "锄禾日当午,汗滴禾下土,谁知盘中餐,粒粒皆辛苦";// 1. 以 , 为标准对 poem 进行分割 , 返回一个数组// 2. 在对字符串进行分割时,如果有特殊字符,需要加入 转义符 \
String[] split = poem.split(",");
poem = "E:\\aaa\\bbb";
split = poem.split("\\\\");
System.out.println("==分割后内容===");
for (int i = 0; i < split.length; i++) {
System.out.println(split[i]);
}
// 6.toCharArray 转换成字符数组s = "happy";
char[] chs = s.toCharArray();
for (int i = 0; i < chs.length; i++) {
System.out.println(chs[i]);
}
// 7.compareTo 比较两个字符串的大小,如果前者大,// 则返回正数,后者大,则返回负数,如果相等,返回 0// (1) 如果长度相同,并且每个字符也相同,就返回 0
// (2) 如果长度相同或者不相同,但是在进行比较时,可以区分大小// 就返回 if (c1 != c2) {
// return c1 - c2;
// }
// (3) 如果前面的部分都相同,就返回 str1.len - str2.len
String a = "jcck";// len = 3
String b = "jack";// len = 4
System.out.println(a.compareTo(b)); // 返回值是 'c' - 'a' = 2 的值// 8.format 格式字符串/* 占位符有:
* %s 字符串 %c 字符 %d 整型 %.2f 浮点型*
*/
String name = "john";
int age = 10;
double score = 56.857;
char gender = '男';//将所有的信息都拼接在一个字符串. String info =
"我的姓名是" + name + "年龄是" + age + ",成绩是" + score + "性别是" + gender + "。希望大家喜欢我!";
System.out.println(info);//1. %s , %d , %.2f %c 称为占位符//2. 这些占位符由后面变量来替换//3. %s 表示后面由 字符串来替换//4. %d 是整数来替换//5. %.2f 表示使用小数来替换,替换后,只会保留小数点两位, 并且进行四舍五入的处理//6. %c 使用 char 类型来替换String formatStr = "我的姓名是%s 年龄是%d,成绩是%.2f 性别是%c.希望大家喜欢我!";
String info2 = String.format(formatStr, name, age, score, gender);
System.out.println("info2=" + info2);
}
}
总结
1)String类是一个final类,不能被继承
2)String底层是一个final类型的char数组,例如:private final char[]
3)String实现了Serializable接口和Comparable接口
关于String底层不可变的理解
1)final char[]是指地址不可变,但是里面的值可以更改
2)当对字符串重新赋值时,需要重新指定内存区域。例如上图:0x1212不能修改为0x1213,只能在常量池中新建一个0x1213
(二)、StringBuffer、StringBuilder异同
同
方法
方法 | 说明 |
StringBuff append(String str) |
在尾部追加,相当于String的+=,可以追加:boolean、char、char[]、 double、float、int、long、Object、String、StringBuff的变量 |
char charAt(int index) | 获取index位置的字符 |
int length() | 获取字符串的长度 |
int capacity() | 获取底层保存字符串空间总的大小 |
void ensureCapacity(int mininmumCapacity) |
扩容 |
void setCharAt(int index, char ch) |
将index位置的字符设置为ch |
int indexOf(String str) | 返回str第一次出现的位置 |
int indexOf(String str, int fromIndex) |
从fromIndex位置开始查找str第一次出现的位置 |
int lastIndexOf(String str) | 返回最后一次出现str的位置 |
int lastIndexOf(String str, int fromIndex) |
从fromIndex位置开始找str最后一次出现的位置 |
StringBuff insert(int offset, String str) |
在offset位置插入:八种基类类型 & String类型 & Object类型数据 |
StringBuffer deleteCharAt(int index) |
删除index位置字符 |
StringBuffer delete(int start, int end) |
删除[start, end)区间内的字符 |
StringBuffer replace(int start, int end, String str) |
将[start, end)位置的字符替换为str |
String substring(int start) | 从start开始一直到末尾的字符以String的方式返回 |
String substring(int start,int end) |
将[start, end)范围内的字符以String的方式返回 |
StringBuffer reverse() | 反转字符串 |
String toString() | 将所有字符按照String的方式返回 |
扩容机制
1)都继承AbstractStringBuffer类
2)底层创建了一个长度为16的字符数组
总结:StringBuffer sb1= new StringBuffer() 底层创建了一个长度为16的字符数组
3)创建指定长度的str(str<16和str>=16都行),容量为str.length + 16
总结:StringBuffer sb = new StringBuffer(“str”); 底层创建了一个 “str”.length()+16 的字节数组(前提是字符串里无中文字符,有中文字符就比较复杂,感兴趣的可以去看看源码,和coder这个编码有关)
4)当我们不断append添加字符,这个字符串长度超过这个数组长度保存不下了,这就需要给数组扩容,容量为:
- 一次追加长度超过当前容量,则会按照 当前容量*2+2 扩容一次
- 一次追加长度不仅超过初始容量,而且按照当前容量*2+2扩容一次也不够,其容量会直接扩容到与所添加的字符串长度相等的长度。之后再追加的话,还会按照当前容量*2+2进行扩容
StringBuffer和StringBuilder扩容机制详解
异
1、StringBuffer与StringBuilder中的方法和功能完全是等价的。
2、只是StringBuffer中的方法大都采用了 synchronized 关键字进行修饰,因此是线程安全的,而StringBuilder没有这个修饰,可以被认为是线程不安全的。
3、在单线程程序下,StringBuilder效率更快,因为它不需要加锁,不具备多线程安全而StringBuffer则每次都需要判断锁,效率相对更低
(三)、三者异同
String:不可变的字符序列:底层使用char[]存储
StringBuffer:可变的字符序列:线程安全的,效率偏低:底层使用char[]存储
StringBuilder:可变的字符序列:线程不安全的,效率高:底层使用char[]存储
二十八、日期类
1、第一代日期类
public class Date01 {
public static void main(String[] args) throws ParseException {//1. 获取当前系统时间//2. 这里的 Date 类是在 java.util 包//3. 默认输出的日期格式是国外的方式, 因此通常需要对格式进行转换Date d1 = new Date(); //获取当前系统时间System.out.println("当前日期=" + d1);
Date d2 = new Date(9234567); //通过指定毫秒数得到时间System.out.println("d2=" + d2); //获取某个时间对应的毫秒数//1. 创建 SimpleDateFormat 对象,可以指定相应的格式//2. 这里的格式使用的字母是规定好,不能乱写SimpleDateFormat sdf = new SimpleDateFormat("yyyy 年 MM 月 dd 日 hh:mm:ss ");
String format = sdf.format(d1); // format:将日期转换成指定格式的字符串System.out.println("当前日期=" + format);
//老韩解读//1. 可以把一个格式化的 String 转成对应的 Date
//2. 得到 Date 仍然在输出时,还是按照国外的形式,如果希望指定格式输出,需要转换//3. 在把 String -> Date , 使用的 sdf 格式需要和你给的 String 的格式一样,否则会抛出转换异常String s = "1996 年 01 月 01 日 10:20:30 星期一";
Date parse = sdf.parse(s);
System.out.println("parse=" + sdf.format(parse));}
}
2、第三代日期类
因第二代日期类很少用到且问题很大,所有直接用第三代日期类
第二代日期类问题分析:
第三代日期类:
public class LocalDate_ {
public static void main(String[] args) {
//第三代日期//1. 使用 now() 返回表示当前日期时间的 对象LocalDateTime ldt = LocalDateTime.now(); //LocalDate.now();//LocalTime.now()
System.out.println(ldt);
//2. 使用 DateTimeFormatter 对象来进行格式化// 创建 DateTimeFormatter 对象DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String format = dateTimeFormatter.format(ldt);
System.out.println("格式化的日期=" + format);
System.out.println("年=" + ldt.getYear());
System.out.println("月=" + ldt.getMonth());
System.out.println("月=" + ldt.getMonthValue());
System.out.println("日=" + ldt.getDayOfMonth());
System.out.println("时=" + ldt.getHour());
System.out.println("分=" + ldt.getMinute());
System.out.println("秒=" + ldt.getSecond());
LocalDate now = LocalDate.now(); //可以获取年月日LocalTime now2 = LocalTime.now();//获取到时分秒//提供 plus 和 minus 方法可以对当前时间进行加或者减//看看 890 天后,是什么时候 把 年月日-时分秒LocalDateTime localDateTime = ldt.plusDays(890);
System.out.println("890 天后=" + dateTimeFormatter.format(localDateTime));
//看看在 3456 分钟前是什么时候,把 年月日-时分秒输出LocalDateTime localDateTime2 = ldt.minusMinutes(3456);
System.out.println("3456 分钟前 日期=" + dateTimeFormatter.format(localDateTime2));
}
二十九、集合
1、集合的框架体系
2、集合常用方法
方法 | 说明 |
add(Object obj) |
向集合中添加一个元素 |
addAll(Collection<? extends E>) |
向集合中添加另一个集合 |
remove(Object obj) |
移除一个数据 |
removeAll(Collection<? extends E>) |
在一个集合中删除另一个集合。当然删除的是两个集合的交集 |
clear() |
清除集合中全部的数据 |
size |
获取集合中已经存储的元素 |
toArray() |
将集合转换为数组 |
contains() |
是否包含某一元素 |
containsAll() |
是否包含某一种集合 |
isEmpty |
是否为空 |
3、Collection 接口遍历元素方式
(1)迭代器
public class CollectionIterator {
@SuppressWarnings({"all"})
public static void main(String[] args) {
Collection col = new ArrayList();
col.add(new Book("三国演义", "罗贯中", 10.1));
col.add(new Book("小李飞刀", "古龙", 5.1));
col.add(new Book("红楼梦", "曹雪芹", 34.6));
//System.out.println("col=" + col);//1. 先得到 col 对应的 迭代器Iterator iterator = col.iterator();
//2. 使用 while 循环遍历// while (iterator.hasNext()) {//判断是否还有数据// //返回下一个元素,类型是 Object
// Object obj = iterator.next();
// System.out.println("obj=" + obj);
// }
//老师教大家一个快捷键,快速生成 while => itit
//显示所有的快捷键的的快捷键 ctrl + j
while (iterator.hasNext()) {
Object obj = iterator.next();
System.out.println("obj=" + obj);}
//3. 当退出 while 循环后 , 这时 iterator 迭代器,指向最后的元素// iterator.next();//NoSuchElementException
//4. 如果希望再次遍历,需要重置我们的迭代器iterator = col.iterator();
System.out.println("===第二次遍历===");
while (iterator.hasNext()) {
Object obj = iterator.next();
System.out.println("obj=" + obj);
}
}
}
class Book {
private String name;
private String author;
private double price;
public Book(String name, String author, double price) {
this.name = name;
this.author = author;
this.price = price;
}
(2)增强for
4、List接口基本介绍
List 的三种遍历方式
(1)ArrayList 底层结构和源码分析
ArrayList 的注意事项
ArrayList 的底层操作机制源码分析(重点,难点.)
//1.无参构造器
public ArrayList() {this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;// 创建一个空的elementData数组}//2.list.add
public boolean add(E e) {ensureCapacityInternal(size + 1); // 确定是否扩容elementData[size++] = e; // 然后再执行赋值return true;}//3.确定minCapacity
private void ensureCapacityInternal(int minCapacity) {ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));}private static int calculateCapacity(Object[] elementData, int minCapacity) {if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { // 如果elementData为空return Math.max(DEFAULT_CAPACITY, minCapacity); // 返回10}return minCapacity;// 反之,返回minCapacity}//4.确定是否扩容
private void ensureExplicitCapacity(int minCapacity) {modCount++; // 记录被修改次数if (minCapacity - elementData.length > 0)grow(minCapacity);}//5.用grow()扩容
private void grow(int minCapacity) {int oldCapacity = elementData.length;int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity < 0)newCapacity = minCapacity;if (newCapacity - MAX_ARRAY_SIZE > 0)newCapacity = hugeCapacity(minCapacity);elementData = Arrays.copyOf(elementData, newCapacity); // 使用Arrays.copyOf()方法}
(2)Vector 底层结构和源码剖析
//1. new Vector() 底层
public Vector() {this(10);
}补充:如果是 Vector vector = new Vector(8);走的方法:
public Vector(int initialCapacity) {this(initialCapacity, 0);
}2. vector.add(i)
2.1 //下面这个方法就添加数据到 vector 集合
public synchronized boolean add(E e) {modCount++;ensureCapacityHelper(elementCount + 1);elementData[elementCount++] = e;return true;
}
2.2 //确定是否需要扩容 条件 : minCapacity - elementData.length>0
private void ensureCapacityHelper(int minCapacity) {if (minCapacity - elementData.length > 0)grow(minCapacity);
}
2.3 //如果 需要的数组大小 不够用,就扩容 , 扩容的算法
//newCapacity = oldCapacity + ((capacityIncrement > 0) ?
// capacityIncrement : oldCapacity);
//就是扩容两倍. private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement:oldCapacity);
if (newCapacity - minCapacity < 0)newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)newCapacity = hugeCapacity(minCapacity);elementData = Arrays.copyOf(elementData, newCapacity);
}
Vector 和 ArrayList 的比较
(3)LinkedList 底层结构
模拟简单双向链表
public class LinkedList01 {
public static void main(String[] args) {
//模拟一个简单的双向链表Node jack = new Node("jack");
Node tom = new Node("tom");
Node hsp = new Node("老韩");
//连接三个结点,形成双向链表//jack -> tom -> hsp
jack.next = tom;
tom.next = hsp;
//hsp -> tom -> jack
hsp.pre = tom;
tom.pre = jack;
Node first = jack;//让 first 引用指向 jack,就是双向链表的头结点Node last = hsp; //让 last 引用指向 hsp,就是双向链表的尾结点//演示,从头到尾进行遍历System.out.println("===从头到尾进行遍历===");
while (true) {if(first == null) {break;
}
//输出 first 信息
System.out.println(first);
first = first.next;
}
//演示,从尾到头的遍历System.out.println("====从尾到头的遍历====");
while (true) {if(last == null) {break;
}
//输出 last 信息System.out.println(last);
last = last.pre;
}
//演示链表的添加对象/数据,是多么的方便//要求,是在 tom --------- 老韩直接,插入一个对象 smith
//1. 先创建一个 Node 结点,name 就是 smith
Node smith = new Node("smith");
//下面就把 smith 加入到双向链表了smith.next = hsp;
smith.pre = tom;
hsp.pre = smith;
tom.next = smith;
//让 first 再次指向 jack
first = jack;//让 first 引用指向 jack,就是双向链表的头结点System.out.println("===从头到尾进行遍历===");
while (true) {if(first == null) {break;
}
//输出 first 信息
System.out.println(first);
first = first.next;
}
last = hsp; //让 last 重新指向最后一个结点//演示,从尾到头的遍历System.out.println("====从尾到头的遍历====");
while (true) {if(last == null) {break;}
//输出 last 信息
System.out.println(last);
last = last.pre;}}
}
//定义一个 Node 类,Node 对象 表示双向链表的一个结点class Node {
public Object item; //真正存放数据public Node next; //指向后一个结点public Node pre; //指向前一个结点public Node(Object name) {this.item = name;
}
public String toString() {return "Node name=" + item;
}
}
ArrayList 和 LinkedList 比较
5、Set接口基本介绍
(1)HashSet的全面说明
(2)HashSet底层源码
@SuppressWarnings({"all"})
public class HashSetSource {
public static void main(String[] args) {
HashSet hashSet = new HashSet();
hashSet.add("java");//到此位置,第 1 次 add 分析完毕. hashSet.add("php");//到此位置,第 2 次 add 分析完毕hashSet.add("java");
System.out.println("set=" + hashSet);1. 执行 HashSet()
public HashSet() {
map = new HashMap<>();
}
2. 执行 add()
public boolean add(E e) {//e = "java"
return map.put(e, PRESENT)==null;//(static) PRESENT = new Object();
}
3.执行 put() , 该方法会执行 hash(key) 得到 key 对应的 hash 值 算法 h = key.hashCode()) ^ (h >>> 16)
public V put(K key, V value) {//key = "java" value = PRESENT 共享return putVal(hash(key), key, value, false, true);
}4.执行 putVal
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i; //定义了辅助变量//table 就是 HashMap 的一个数组,类型是 Node[]
//if 语句表示如果当前 table 是 null, 或者 大小=0
//就是第一次扩容,到 16 个空间. if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//(1)根据 key,得到 hash 去计算该 key 应该存放到 table 表的哪个索引位置//并把这个位置的对象,赋给 p
//(2)判断 p 是否为 null
//(2.1) 如果 p 为 null, 表示还没有存放元素, 就创建一个 Node (key="java",value=PRESENT)
//(2.2) 就放在该位置 tab[i] = newNode(hash, key, value, null)
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
//一个开发技巧提示: 在需要局部变量(辅助变量)时候,在创建Node<K,V> e; K k; //
//如果当前索引位置对应的链表的第一个元素和准备添加的 key 的 hash 值一样//并且满足 下面两个条件之一:
//(1) 准备加入的 key 和 p 指向的 Node 结点的 key 是同一个对象//(2) p 指向的 Node 结点的 key 的 equals() 和准备加入的 key 比较后相同//就不能加入if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//再判断 p 是不是一颗红黑树, //如果是一颗红黑树,就调用 putTreeVal , 来进行添加else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {//如果 table 对应索引位置,已经是一个链表, 就使用 for 循环比较//(1) 依次和该链表的每一个元素比较后,都不相同, 则加入到该链表的最后// 注意在把元素添加到链表后,立即判断 该链表是否已经达到 8 个结点// , 就调用 treeifyBin() 对当前这个链表进行树化(转成红黑树)
// 注意,在转成红黑树时,要进行判断, 判断条件// if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY(64))
// resize();
// 如果上面条件成立,先 table 扩容. // 只有上面条件不成立时,才进行转成红黑树//(2) 依次和该链表的每一个元素比较过程中,如果有相同情况,就直接 break
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD(8) - 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;
//size 就是我们每加入一个结点 Node(k,v,h,next), size++
if (++size > threshold)
resize();//扩容afterNodeInsertion(evict);
return null;
}
}
}
@SuppressWarnings({"all"})
public class HashSetIncrement {
public static void main(String[] args) {
/*
HashSet 底层是 HashMap, 第一次添加时,table 数组扩容到 16,
临界值(threshold)是 16*加载因子(loadFactor)是 0.75 = 12如果 table 数组使用到了临界值 12,就会扩容到 16 * 2 = 32,新的临界值就是 32*0.75 = 24, 依次类推*/
HashSet hashSet = new HashSet();
// for(int i = 1; i <= 100; i++) {
// hashSet.add(i);//1,2,3,4,5...100
// }
/*在 Java8 中, 如果一条链表的元素个数到达 TREEIFY_THRESHOLD(默认是 8 ),
并且 table 的大小 >= MIN_TREEIFY_CAPACITY(默认 64),就会进行树化(红黑树),
否则仍然采用数组扩容机制*/
// for(int i = 1; i <= 12; i++) {
// hashSet.add(new A(i));//
// }
/*当我们向 hashset 增加一个元素,-> Node -> 加入 table , 就算是增加了一个 size++
*/
for(int i = 1; i <= 7; i++) {//在 table 的某一条链表上添加了 7 个 A 对象hashSet.add(new A(i));//}
for(int i = 1; i <= 7; i++) {//在 table 的另外一条链表上添加了 7 个 B 对象hashSet.add(new B(i));//
}
}
}
class B {
private int n;
public B(int n) {
this.n = n;
}
@Override
public int hashCode() {
return 200;
}
}
class A {
private int n;public A(int n) {
this.n = n;
}
@Override
public int hashCode() {
return 100;
}
}
(3)HashSet子类-----LinkedHashSet
(4)TreeSet
public class TreeSet_ {
public static void main(String[] args) {//1. 当我们使用无参构造器,创建 TreeSet 时,仍然是无序的//2. 希望添加的元素,按照字符串大小来排序//3. 使用 TreeSet 提供的一个构造器,可以传入一个比较器(匿名内部类)
// 并指定排序规则//4. 简单看看源码/*
1. 构造器把传入的比较器对象,赋给了 TreeSet 的底层的 TreeMap 的属性 this.comparator
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
2. 在 调用 treeSet.add("tom"), 在底层会执行到if (cpr != null) {//cpr 就是我们的匿名内部类(对象)do {
parent = t;
//动态绑定到我们的匿名内部类(对象)compare
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else //如果相等,即返回 0,这个 Key 就没有加入return t.setValue(value);
} while (t != null);
}
*/
// TreeSet treeSet = new TreeSet();
TreeSet treeSet = new TreeSet(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
//下面 调用 String 的 compareTo 方法进行字符串大小比较//如果要求加入的元素,按照长度大小排序//return ((String) o2).compareTo((String) o1);
return ((String) o1).length() - ((String) o2).length();
}
});
//添加数据. treeSet.add("jack");treeSet.add("tom");//3
treeSet.add("sp");
treeSet.add("a");
treeSet.add("abc");//3
System.out.println("treeSet=" + treeSet);
}
}
public class TreeMap_ {
public static void main(String[] args) {//使用默认的构造器,创建 TreeMap, 是无序的(也没有排序)
/*要求:按照传入的 k(String) 的大小进行排序*/
// TreeMap treeMap = new TreeMap();
TreeMap treeMap = new TreeMap(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
//按照传入的 k(String) 的大小进行排序//按照 K(String) 的长度大小排序//return ((String) o2).compareTo((String) o1);
return ((String) o2).length() - ((String) o1).length();
}
});
treeMap.put("jack", "杰克");
treeMap.put("tom", "汤姆");
treeMap.put("kristina", "克瑞斯提诺");
treeMap.put("smith", "斯密斯");
treeMap.put("hsp", "韩顺平");//加入不了System.out.println("treemap=" + treeMap);
/*1. 构造器. 把传入的实现了 Comparator 接口的匿名内部类(对象),传给给 TreeMap 的 comparator
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
2. 调用 put 方法2.1 第一次添加, 把 k-v 封装到 Entry 对象,放入 root
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;
}
2.2 以后添加Comparator<? super K> cpr = comparator;
if (cpr != null) {
do { //遍历所有的 key , 给当前 key 找到适当位置parent = t;
cmp = cpr.compare(key, t.key);//动态绑定到我们的匿名内部类的 compare
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else //如果遍历过程中,发现准备添加 Key 和当前已有的 Key 相等,就不添加return t.setValue(value);
} while (t != null);
}
*/
}
}
TreeSet底层原理
5、Map接口和常用方法基本介绍
6、Map 接口遍历方法
@SuppressWarnings({"all"})
public class MapFor {
public static void main(String[] args) {Map map = new HashMap();map.put("邓超", "孙俪");map.put("王宝强", "马蓉");map.put("宋喆", "马蓉");map.put("刘令博", null);map.put(null, "刘亦菲");map.put("鹿晗", "关晓彤");
//第一组: 先取出所有的Key , 通过Key 取出对应的ValueSet keyset = map.keySet();
//(1) 增强forSystem.out.println("-----第一种方式-------");for (Object key : keyset) {System.out.println(key + "-" + map.get(key));}
//(2) 迭代器System.out.println("----第二种方式--------");Iterator iterator = keyset.iterator();while (iterator.hasNext()) {Object key = iterator.next();System.out.println(key + "-" + map.get(key));}
//第二组: 把所有的values 取出Collection values = map.values();
//这里可以使用所有的Collections 使用的遍历方法
//(1) 增强forSystem.out.println("---取出所有的value 增强for----");for (Object value : values) {System.out.println(value);}
//(2) 迭代器System.out.println("---取出所有的value 迭代器----");Iterator iterator2 = values.iterator();while (iterator2.hasNext()) {Object value = iterator2.next();System.out.println(value);}
//第三组: 通过EntrySet 来获取k-vSet entrySet = map.entrySet();// EntrySet<Map.Entry<K,V>>
//(1) 增强forSystem.out.println("----使用EntrySet 的for 增强(第3 种)----");for (Object entry : entrySet) {
//将entry 转成Map.EntryMap.Entry m = (Map.Entry) entry;System.out.println(m.getKey() + "-" + m.getValue());}
//(2) 迭代器System.out.println("----使用EntrySet 的迭代器(第4 种)----");Iterator iterator3 = entrySet.iterator();while (iterator3.hasNext()) {Object entry = iterator3.next();
//System.out.println(next.getClass());//HashMap$Node -实现-> Map.Entry (getKey,getValue)
//向下转型Map.EntryMap.Entry m = (Map.Entry) entry;System.out.println(m.getKey() + "-" + m.getValue());}
}
(1)Map 接口实现类-----HashMap
HashMap 小结
HashMap 底层机制及源码剖析
HashMap 底层机制及源码剖析
@SuppressWarnings({"all"})
public class HashMapSource1 {public static void main(String[] args) {HashMap map = new HashMap();map.put("java", 10);//okmap.put("php", 10);//okmap.put("java", 20);//替换valueSystem.out.println("map=" + map);//
1. 执行构造器new HashMap()
初始化加载因子loadfactor = 0.75HashMap$Node[] table = null
2. 执行put 调用hash 方法,计算key 的hash 值(h = key.hashCode()) ^ (h >>> 16)public V put(K key, V value) {//K = "java" value = 10return putVal(hash(key), key, value, false, true);}
3. 执行putValfinal V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {Node<K,V>[] tab; Node<K,V> p; int n, i;//辅助变量
//如果底层的table 数组为null, 或者length =0 , 就扩容到16if ((tab = table) == null || (n = tab.length) == 0)n = (tab = resize()).length;
//取出hash 值对应的table 的索引位置的Node, 如果为null, 就直接把加入的k-v
//, 创建成一个Node ,加入该位置即可if ((p = tab[i = (n - 1) & hash]) == null)tab[i] = newNode(hash, key, value, null);else {Node<K,V> e; K k;//辅助变量
// 如果table 的索引位置的key 的hash 相同和新的key 的hash 值相同,
// 并满足(table 现有的结点的key 和准备添加的key 是同一个对象|| equals 返回真)
// 就认为不能加入新的k-vif (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))e = p;else if (p instanceof TreeNode)//如果当前的table 的已有的Node 是红黑树,就按照红 黑树的方式处理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);
//加入后,判断当前链表的个数,是否已经到8 个,到8 个,后
//就调用treeifyBin 方法进行红黑树的转换if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1sttreeifyBin(tab, hash);break;}if (e.hash == hash && //如果在循环比较过程中,发现有相同,就break,就只是替换value((k = e.key) == key || (key != null && key.equals(k))))break;p = e;}}if (e != null) { // existing mapping for keyV oldValue = e.value;if (!onlyIfAbsent || oldValue == null)e.value = value; //替换,key 对应valueafterNodeAccess(e);return oldValue;}}++modCount;//每增加一个Node ,就size++if (++size > threshold[12-24-48])//如size > 临界值,就扩容resize();afterNodeInsertion(evict);return null;}
5. 关于树化(转成红黑树)
//如果table 为null ,或者大小还没有到64,暂时不树化,而是进行扩容.
//否则才会真正的树化-> 剪枝final void treeifyBin(Node<K,V>[] tab, int hash) {int n, index; Node<K,V> e;if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)resize();}}
}
模拟HashMap触发内容、树化情况,Debug验证
@SuppressWarnings({"all"})
public class HashMapSource2 {
public static void main(String[] args) {
HashMap hashMap = new HashMap();
for(int i = 1; i <= 12; i++) {
hashMap.put(i, "hello");
}
hashMap.put("aaa", "bbb");
System.out.println("hashMap=" + hashMap);//12 个k-v
//布置一个任务,自己设计代码去验证,table 的扩容
//0 -> 16(12) -> 32(24) -> 64(64*0.75=48)-> 128 (96) ->
//自己设计程序,验证-》增强自己阅读源码能力. 看别人代码.
}
}
class A {
private int num;
public A(int num) {
this.num = num;
}
//所有的A 对象的hashCode 都是100
// @Override
// public int hashCode() {
// return 100;
// }
@Override
public String toString() {
return "\nA{" +
"num=" + num +
'}';
}
}
(2)Map 接口实现类-----Hashtable
HashTable 的基本介绍
HashTable底层实现
Hashtable 和 HashMap 对比
(3)Map 接口实现类-Properties
基本使用
public class Properties_ {
public static void main(String[] args) {//1. Properties 继承 Hashtable
//2. 可以通过 k-v 存放数据,当然 key 和 value 不能为 null
//增加Properties properties = new Properties();
//properties.put(null, "abc");//抛出 空指针异常//properties.put("abc", null); //抛出 空指针异常properties.put("john", 100);//k-v
properties.put("lucy", 100);
properties.put("lic", 100);
properties.put("lic", 88);//如果有相同的 key , value 被替换System.out.println("properties=" + properties);//通过 k 获取对应值System.out.println(properties.get("lic"));//88
//删除properties.remove("lic");
System.out.println("properties=" + properties);
//修改properties.put("john", "约翰");
System.out.println("properties=" + properties);}
}
三十、泛型
Java基础细节(持续更新中)相关推荐
- JAVA基础(持续更新中)
JAVA基础 2020年11月27日 21:01 1 预科 a. 什么是计算机 能够按照程序运行,自动.高速处理海量数据的现代化智能电子设备. 由硬件常见的形式有台式计算机.笔记本计算机.大型计算机等 ...
- JAVA面试大全(持续更新中...)
本文旨在收集Java面试过程中出现的问题,力求全面,仅作学习交流,欢迎补充,持续更新中-,部分段落选取自网上,部分引用文章已标注,部分已记不清了,如侵权,联系本人 Java基础 1.面向对象的概述 面 ...
- 幻想-FLEX 3基础视频教程 持续更新中
欢迎点击此处订阅本Blog title="RSS 2.0" type="application/rss+xml" href="http://feed. ...
- Java基础入门(持续更新)
目录 Java基础入门1 1.Hello Java 1.1 Java简介 1.2 Java体系与特点 java的特性 1.3 Java 跨平台原理 Java 技术两种核心机制 Java 虚拟机(JVM ...
- Java知识点汇总 持续更新中~~~
一.什么是面向对象? 是基于面向过程而言,面向对象是将功能通过对象来实现,将功能封装进对象之中,让对象去实现具体的细节. 二.标识符的格式? 1.可以使用字母.数字._.$来组成,不能使用特殊符号. ...
- Node.js零基础自学(持续更新中)
1. Node.js时基于Chrome V8 引擎的JavaScript运行环境.官网:Node.jsNode.js® is a JavaScript runtime built on Chrome' ...
- java基础必备持续更新优化
java的程序分有2类: 1.嵌入在网页中,通过浏览器运行的程序,被称为Applet,译为小应用程序. 2.除1之外Java程序,被称为Application,译为应用程序. 第一个java ...
- Java学习笔记(持续更新中)
文章目录 项目实战 mall项目(SpringBoot项目) 1. 添加Swagger-UI配置,修改MyBatis Generator注释的生成规则 2. redis基础配置 3. SpringSe ...
- 前端使用Canvas绘图(基础知识)--持续更新中
文章目录 前言 canvas文档 一.canvas代码提示(插件和注释) 1.1.使用插件方式(推荐这种方式) =>canvas-snippets 1.2.使用注释方式 二.初始canvas 2 ...
- 微信支付先享后付java实现(持续更新中)
由于网上资料少,而且微信的文档我知道的就有4-5个版本,各个不一样,所以做这个的时候还是挺坑的,还好已经实现了,特此记录一下,让后来人,有个参考 一.先注册微信商户平台,那一堆乱七八糟的,就不说了,自 ...
最新文章
- linux 检查权限,检查目录下 文件的权限-linux shell脚本,
- Beep()之我迷糊了……
- Silverlight游戏设计(Game Design):目录
- C++ 中的 new/delete 和 new[]/delete[]
- 如何在typescript中使用axios来封装一个HttpClient类
- linux htb 源代码,LINUX TC:HTB相关源码
- 路由器端口映射实现远程桌面
- matplotlib+numpy绘制二维条形直方图
- java中after什么意思_Java中的即时isAfter()方法
- 评价目标检测区域的准确性——IoU
- java 状态机_Java 数据持久化系列之池化技术
- 腾讯自动驾驶新动作!与现代合作开发无人车系统
- Java基础之如何修改字符串?
- Requirements Analysis with 'pseud-Formal' Method
- matlab 异常,Matlab 2017b 异常信息。程序奔溃。
- ES6中的模块化编程
- 做课题与科研项目常用的研究方法
- Python OOP 项目实践:烤地瓜,搬家具
- 2.2.7Python-异常处理
- safair浏览器 在回调中跳转 window.open 打不开页面 但是有判断,跳转不了