Java SE
1. 基础
- javac .java :编译Java源文件
- java 生成的.class文件(不带后缀) :运行Java文件。
- 文件名称和类名要相同运行java
- 程序时不需要带.class后缀
public class Hello { public static void main(String[] args){ System.out.println("Hello,World!"); } }
注意:
- Java文件 ——编译器——> .class文件 ——类加载器——> JVM ——字节码校验器——>解释器 ———->操作系统
- 标识符,以字母、下划线、美元符开头; 关键字:语言中已经被使用的标识符
- Java是一种强类型语言:变量的使用严格遵循规范。即 int=1.0 将会出错
- java数据类型:
- 基本类型:
- 8种数值类型:
- 整数类型:byte(1Byte)、short(2)、int(4)、long(8)
- 浮点类型:float(4)、double(8)
- 字符类型:char(2)只能是单个字符
- boolean类型:true (1bit)、false
- 引用类型:类、接口、数组
注:float 和 double 会产生精度问题,所以使用BigDecimal数学计算类
- 强制类型转化 :高 ——> 低(byte,short,char —> int —> long —> float —>double)
- 转换的数据类型必须是兼容的。
- 不能对布尔值进行转换
- 不能把对象类型转化为不相干的类型
- 强制转换可能存在溢出或者精度问题
- 自动类型转化: 低 ——> 高
- 转义字符:\t 制表、\n 换行
//s1,s2指向不同对象 String s1 = new String("hello"); String s2 = new String("hello"); //s3,s4指向同一个对象 String s3 = "hello"; String s4 = "hello"; System.out.println(s1 == s2);false System.out.println(s3 == s4);true
- JDK7+ 中可以将数字之间用下划线分割:10_000
- 变量:变量名+变量类型+作用域;变量名是对某块地址的统称,该地址占多少空间,取决于此变量名的类型。而该变量的地址是该块存储空间的首地址。
- 每个变量都要有类型,可以是基本类型,也可以是引用类型。
- 变量名必须是合法的标识符
- 默认值:数值类型一般为0、0.0,char为 u0000,boolean为 false,除了基本类型,其余都为null
变量命名规范:
变量 | 规范 |
类成员变量 | 首字母小写和驼峰原则(studentName) |
局部变量 | 首字母小写和驼峰原则 |
常量 | 大写字母和下划线 (MAX_NUM) |
类名 | 首字母大写和驼峰原则 (Variable) |
方法名 | 首字母小写和驼峰原则 (runMethod()) |
- 常量:值不会也不能改变的量,使用 final 修饰(常量名用大写字母标识)
public class Variable { //类变量 static String id; //常量 注:修饰符不区分前后顺序(public、static、final……) static final double PI = 3.14; //实例变量 String name; public static void main(String[] args) { //局部变量 int i = 0; final double Pi = 3.1415926; System.out.println(i); //0 Variable variable = new Variable();//使用实例变量需要拿到该类的对象实例 System.out.println(variable.name); // null System.out.println(id); //null System.out.println(Pi); //3.1415926 } }
- 运算符
运算符 | 符号 |
算数运算符 | +,-,*,/,%,++,-- |
赋值运算符 | = |
关系运算符 | >, < , == , >= , <= , != , instanceof |
逻辑运算符 | && , || , ! |
位运算符 | &, | , ^ , ~ , >> , << |
条件运算符 | a ? b : a/b |
扩展赋值运算符 | += , -= , /= , *= |
注:取余就是模运算,&&、|| 为短路运算符
int a = 10; int b = 20; System.out.println(a+b+""); //1020 System.out.println(""+a+b); //30
- 包机制:定义包 package (程序在那个包下),导入包 import(需要具体的类)包名一般为域名倒置
- JavaDoc:生成API帮助文档
执行命令:javadoc -encoding UTF-8 -charset UTF-8 Doc.java
- @author 作者
- @version 版本号
- @since 指明需要最早使用的 JDK 版本
- @param 参数名
- @return 返回值情况
- @throws 异常抛出情况
2. 流程控制
- Scanner类,通过next() 和 nextLine() 方法获取输入的字符串,在读取之前一般需要使用hasNext() 与 hasNextLine()判断是否还有输入的数据。
- 需要读到有效字符后才可以结束输入。
- 对输入有效字符之前遇到的空白,next() 方法会自动将其忽略。
- 只有输入有效字符后才会将其后面的空格作为结束符
例:
Hello World!
只会输出Hello
- 以Enter回车为结束符,即读取回车之前的所有字符
- 可以读到空格
注:IO类用完需要关闭资源
next(): 不能得到带有空格的字符串。
nextLine() :
- 顺序、选择(if else、switch(JDK7之后支持除8大基本类型之外的String类型))、循环(while,do while,for,JDK5之后还有增强for循环)
for(; ; ){} //死循环
增强for循环用来遍历数组和集合for( Type var : array)
- break : 终止循环,continue:只跳过本次循环,goto:不建议使用,破环程序局部性原理
2. 方法
值传递和引用传递
int num = 10; String str = "hello";
num是基本类型,值就直接保存在变量中。而str是引用类型,变量中保存的只是实际对象的地址。一般称这种变量为"引用",引用指向实际对象,实际对象中保存着内容。
- 方法是解决一种问题的步骤的有序组合,保持方法的原子性,即一个方法实现一个功能
- 方法重载:在同一个类中,方法名相同,但参数类型不同,或者参数个数不同,或参数的排列顺序不同
- 给agrs数组传参
- 可变参数
public void test(int... i){} public void test(int j,int... i){ for (i = 0; i < i.length;i++) System.out.println(i[i]); }
- 递归:递归边界,递归体
4. 数组
- 一组相关类型变量的集合
int[] array; //声明一个数组,目前使用这种写法 int array[]; array = new int[3]; //创建一个数组
- 堆:存放new的对象和数组,可以被所有线程共享 栈:存放基本变量类型,存放对象的引用变量 方法区:可以被所有线程共享,包含了所有class和static变量。
- 数组初始化
int[] a = {1,2,3}; //静态初始化 //动态初始化 int[] b = new int[1]; b[0] = 1; //默认初始化 int[] c = new int[3];//数值类型默认为0,boolean类型默认为false,引用类型默认为null
- for each 循环、数组作参数、数组作返回值
- 多维数组:
int[][][] array = new int[][][]{{{1,2,3},{4,5,6},{7,8,9}},{{9,8,7},{6,5,4},{3,2,1}}}; for (int i = 0; i < array.length; i++) { for (int j = 0; j < array[i].length; j++) { for (int k = 0; k < array[i][j].length; k++) { System.out.print(array[i][j][k] +" "); } System.out.print(" "); } System.out.println(); } // 1 2 3 4 5 6 7 8 9 // 9 8 7 6 5 4 3 2 1
- Arrays类
Arrays.toString(object[] a) //打印数组元素 Arrays.sort(int[] a) // 对数组元素进行排序
- 冒泡排序:
public static int[] bubbleSort(int[] a){ for (int i = a.length -1 ; i > 0 ; i--) { boolean flag = true; // 优化,数组是有序的情况下,省略比较操作 for (int j = 0; j < i; j++) { if (a[j] > a[j+1]){ int temp = a[j]; a[j] = a[j+1]; a[j+1] = temp; flag = false; } } if (flag) //当数组是有序的情况下,直接返回即可 return a; } return a; }
- 稀疏数组的构建与还原
5. 面向对象
(Object-Oriented Programming)
- 以类的方式组织代码,以对象的形式封装数据。
- 三大特性:封装、继承、多态
5.1 修饰符
修饰符用来定义类、方法或者变量,通常放在语句的最前端。
5.1.1 访问控制修饰符
使用访问控制符来保护对类、变量、方法和构造方法的访问
- default (即默认,什么也不写): 在同一包内可见,不使用任何修饰符。使用对象:类、接口、变量、方法。
- private : 在同一类内可见。使用对象:变量、方法。 注意:不能修饰类(内部类除外)
- public : 对所有类可见。使用对象:类、接口、变量、方法
- protected : 对同一包内的类和所有子类可见。使用对象:变量、方法。 注意:不能修饰类(内部类除外)。
父类中声明为 public 的方法在子类中也必须为 public。父类中声明为 protected 的方法在子类中要么声明为 protected,要么声明为 public,不能声明为 private。父类中声明为 private 的方法,不能够被继承。
5.1.2 非访问修饰符
- static 修饰符,用来修饰类方法和类变量。
- 静态变量:
- static 关键字用来声明独立于对象的静态变量,无论一个类实例化多少对象,它的静态变量只有一份拷贝。 静态变量也被称为类变量。局部变量不能被声明为 static 变量。
- 静态变量储存在静态存储区。经常被声明为常量,很少单独使用 static 声明变量。
- 静态变量在第一次被访问时创建,在程序结束时销毁。
- 静态方法:
- static 关键字用来声明独立于对象的静态方法。静态方法不能使用类的非静态变量。静态方法从参数列表得到数据,然后计算这些数据。
- 对类变量和方法的访问可以直接使用 classname.variablename 和 classname.methodname 的方式访问。
静态方法(Static Method)与静态成员变量一样,属于类本身,在类装载的时候被装载到内存(Memory),不自动进行销毁,会一直存在于内存中,直到JVM关闭。非静态方法(Non-Static Method)又叫实例化方法,属于实例对象,实例化后才会分配内存,必须通过类的实例来引用。不会常驻内存,当实例对象被JVM回收之后,也跟着消失。静态成员属于类所有,非静态成员属于类的实例所有。
- final修饰符
- final 变量
- final 方法父类中的 final 方法可以被子类继承,但是不能被子类重写。
- final 类final 类不能被继承,没有类能够继承 final 类的任何特性。
表示"最后的、最终的"含义,变量一旦赋值后,不能被重新赋值。被 final 修饰的实例变量必须显式指定初始值。final 修饰符通常和 static 修饰符一起使用来创建类常量.
- abstract 修饰符
- 抽象类:
- 抽象类不能用来实例化对象,声明抽象类的唯一目的是为了将来对该类进行扩充。
- 一个类不能同时被 abstract 和 final 修饰。如果一个类包含抽象方法,那么该类一定要声明为抽象类,否则将出现编译错误。
- 抽象类可以包含抽象方法和非抽象方法。
- 抽象方法
- 抽象方法是一种没有任何实现的方法,该方法的的具体实现由子类提供。
- 抽象方法不能被声明成 final 和 static。
- 任何继承抽象类的子类必须实现父类的所有抽象方法,除非该子类也是抽象类。
- 如果一个类包含若干个抽象方法,那么该类必须声明为抽象类。抽象类可以不包含抽象方法。
- 抽象方法的声明以分号结尾,例如:public abstract sample();。
- synchronized 修饰符
synchronized 关键字声明的方法同一时间只能被一个线程访问。synchronized 修饰符可以应用于四个访问修饰符。
- transient 修饰符
序列化的对象包含被 transient 修饰的实例变量时,java 虚拟机(JVM)跳过该特定的变量。该修饰符包含在定义变量的语句中,用来预处理类和变量的数据类型。
- volatile 修饰符
volatile 修饰的成员变量在每次被线程访问时,都强制从共享内存中重新读取该成员变量的值。而且,当成员变量发生变化时,会强制线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。一个 volatile 对象引用可能是 null。
5.2 值传递和引用传递
- 值传递:
方法调用时,实际参数把它的值传递给对应的形式参数,函数接收的是原始值的一个copy,此时内存中存在两个相等的基本类型,即实际参数和形式参数,后面方法中的操作都是对形参这个值的修改,不影响实际参数的值。
- 引用传递:
也称为传地址。方法调用时,实际参数的引用(地址,而不是参数的值)被传递给方法中相对应的形式参数,函数接收的是原始值的内存地址;
在方法执行中,形参和实参内容相同,指向同一块内存地址,方法执行中*对引用的操作*将会影响到实际对象。
5.3 类和对象
类是具有相同属性和方法的集合,对象是类的实例化。
- 使用new创建对象,除了分配内存空间之外,还会给创建好的对象进行默认的初始化以及对类中构造器的调用。
- 构造方法,如果没有显示定义构造方法,编译器会默认加上无参构造方法,构造方法名和类名相同,且没有返回类型,连void也不能有。如果加了void,则java会将其当作一个方法来处理。
this: 通过this可以调用本类的构造方法,成员变量,成员方法 this() 调用构造方法 this.变量名 调用成员变量 this.方法名 调用成员方法
- 栈、堆、方法区、静态方法区
- java中对象引用放在栈中,对象的实例放于堆中,一个对象要是只声明不赋值,则只会在内存的栈区创建引用,堆中并无此引用的指向。
Person student ; Person student = null;
二者都在栈中存储了student的引用,且都没有在堆中创建对象的实例。
5.3.1 构造方法
- 构造方法作用:(1)构造出来一个类的实例 (2)对构造出来个一个类的实例(对象)初始化
- 构造方法的名字必须与定义他的类名完全相同,没有返回类型,甚至连void也没有
- 主要完成对象的初始化工作,构造方法的调用是在创建一个对象时使用new操作进行的
- 类中必定有构造方法,若不写,编译器自动为其添加无参构造方法。接口不允许被实例化,所以接口中没有构造方法
- 不能被static、final、synchronized、abstract和native修饰
- 如果一个派生类的构造方法定义没有包含对基类的构造方法的调用,则编译器自动地调用基类的无参构造,如果基类中没有无参构造,则需要显示的调用父类的有参构造。
- 调用父类的构造方法是为了初始化从基类继承的属性和行为等特征。
5.4 封装
- 主要对于属性来说,让属性私有,使用 get、set 方法操作属性
- 禁止直接访问一个对象中数据,而应该通过接口来访问,从而实现信息隐藏。
5.5 继承 (is a)
- extends意思是“扩展”,子类是父类的扩展。
- 继承是类和类之间的一种关系,除此之外,类和类之间的关系还有依赖、组合、聚合等
- java就是子类继承父类的特征和行为,使得子类对象具有父类的实例和方法,或子类从父类继承方法,使得子类具有父类相同的行为。
- java中只能单继承,java中的所有类都继承自 Object 类
5.5.1 Super
- super调用父类的方法、属性。
- 子类在实例化执行构造方法时,会默认使用super调用父类的无参构造方法。如果想调用父类的有参构造,则需显式在子类构造方法中调用,且必须放在第一行。
- super 和 this 不能同时调用构造方法
- super 只能在有继承的情况下调用
- super() 父类的构造。
5.5.2 方法重写
- 重写是子类对父类的允许访问的方法进行重写,返回值和形参都不能改变
- 子类可以根据需要,定义特定于自己的行为。
- 修饰符:范围可以扩大,但不能缩小
- 重写方法不能抛出新的检查异常或者比被重写方法申明更加宽泛的异常。
- jdk1.5及以上 支持返回改变为原类型的子类
5.6 多态
- 同一行为有多种表现形态。
- 多态存在的三个必要条件:继承、重写、父类引用指向子类对象。(static、final、private方法都无法被子类重写)。
- 当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,再去调用子类的同名方法。如果子类没有重写父类的方法,则使用父类的方法。
- 当子类对象调用重写的方法时,调用的是子类的方法,而不是父类中被重写的方法。
- 要想调用父类中被重写的方法,则必须使用关键字 super。
- 多态的好处:可以使程序有良好的扩展,并可以对所有类的对象进行通用处理。
5.7 枚举(enum)
Java 枚举是一个特殊的类,一般表示一组常量,比如一年的 4 个季节,一个年的 12 个月份,一个星期的 7 天,方向有东南西北等。
Java 枚举类使用 enum 关键字来定义,各个常量使用逗号 , 来分割。
enum Color { RED, GREEN, BLUE; }
6. instanceof 和类型转换
- instanceof 用来测试一个对象是否为一个类的实例。
List obj = new ArrayList(); System.out.println(obj instanceof List) //true
- 类对象之间转换,前提条件是源和目的类之间必须通过继承相联系。隐式转换:低 (子类) ——> 高 (父类); 多态显示转换:(类名)对象名 ; 高 ——> 低
- 将父类对象转换成子类时,编译器首先要检查这种转换的可行性,如果可行,则必须进行显示转换。
7. static
- static 定义的变量,在内存中只有一份,类中的所有对象均可共享该变量,可以直接通过 ClassName.var 来调用。
- 非静态方法可以调用非静态方法和静态方法中的所有东西。而静态方法只能调用静态方法
- 静态代码块、匿名代码块、构造器(执行顺序) 如果子类继承父类的话,则是:父类中的静态代码块、子类中的静态代码块、父类中的匿名方法,父类中的构造方法、子类中的匿名方法、子类中的构造方法。
- 静态代码块:类一加载就执行。只执行一次
- 匿名代码块:对象初始化,会被执行,用来赋初值,每new一个对象,执行一次,且在构造方法之前执行
{} //匿名代码块 static{} //静态代码块
- 静态导入包
import static java.lang.Math.PI
8. 抽象类
- 如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。使用 abstract 关键字修饰
- 方法中只有方法名,没有方法的实现。且方法也被abstract修饰 ,方法的实现交给子类来实现。子类如果没有全部实现父类中的抽象方法的话,子类也要被声明为抽象类。
- 抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样。
9. 接口
使用接口来构建松散耦合的可扩展和可测试的程序
- 接口是抽象方法的集合,并不是类,接口使用 interface 修饰。类描述对象的属性和方法。
- 除非实现类是抽象类,否则的话实现类必须实现接口中的全部抽象方法。
- Java 8 之后 接口中可以使用 default 关键字修饰的非抽象方法。
- 接口中每一个方法也是隐式抽象的,接口中的方法会被隐式的指定为 public abstract
- 接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量,主要是为了避免魔法数注:
10. 内部类
- 成员内部类:在类的内部定义
- 静态内部类:使用static修饰的内部类
- 局部内部类:在方法中
- 匿名内部类:没有初始化类的名字,不用将对象实例保存到变量中
Java 异常
1. 异常类
- 检查性异常:除过 Error 和 Runtime Exception 类及其子孙类之外的其它类。需要进行捕获处理
- 运行时异常 :Runtime Exception 类及其子孙类,可以进行捕获处理,也可以不处理,通常是由标准库类中的方法抛出。
- 错误:Error
- 除过运行时异常 和 错误 外,其余的异常都是检查异常,需要在方法中进行捕获处理或者是在方法上使用throws进行声明,其它方法调用该方法则必须对该异常进行相应处理。如果愿意也可以对非检查异常进行捕获处理。
- 如果继承的异常类只有一个含参的构造方法,则子类的构造方法中必须使用 super 关键词调用父类的该构造方法,且必须放在第一行,否则 javac 编译不通过
2. 异常处理
try-throw-catch , finally, throws
- 捕获异常
try{ //监控 } catch (Exception:小范围 e){ //捕获 e.printStackTrace(); //打印错误栈信息 } catch (Exception:大范围 e){ e.printStackTrace(); System.Exit(1); //程序终结 } finally { //善后 }
- 抛出异常
throw new IndexOutOfBoundsException();//在方法中使用
- throws/throw 关键字:
如果一个方法没有捕获到一个检查性异常,那么该方法必须使用 throws 关键字来声明。throws 关键字放在方法签名的尾部。也可以使用 throw 关键字抛出一个异常,无论它是新实例化的还是刚捕获到的。
- 自定义自己的异常类
- 在多重catch下,异常范围使用层层扩大来捕获。防止异常被遗漏。如果在最初的catch块放的是一个较大的异常范围,后面的catch块都是较小范围的异常,那么后面的catch将永远不会被执行到。
- 在finally中释放被占用的资源
Java IO
1. 流
流指的是数据的流动,如果数据流入程序,则为输入流,如果数据流出程序,则为输出流。
2. 字节流和字符流
字节流:数据的处理的是二进制对象,以字节为单位。主要处理二进制文件的项目。如果是音频文件、图片、歌曲,就用字节流好点。字节流不需要进行缓冲。
字符流:数据的处理的 Unicode 字符,一个单元为 2 个字节,主要处理文本文件的项目。如果是关系到中文(文本)的,用字符流好点。字符流需要缓冲区,通过缓冲区再操作文件。
在硬盘上的所有文件都是以字节形式存在的(图片,声音,视频),而字符值在内存中才会形成。
不同的文件读取方式因以不同的方法检查文件的结束,如果以错误的方式检查文件的结束,则可能会使程序进入死循环,或者抛出 EOFException 异常。
java 容器
1. 哈希表
2. Hash Tables
2.1 解决 hash 碰撞
2.1.1 使用链表策略
初始哈希函数 hash1(key) = key % table_size
让数组中的每个单元格指向一个链接列表,不把值存储到数组本身,而是存储到链表中
- hash表,类似于数组,假设有5个存储单元,每个单元格称为桶或槽。最初所有桶都是null
- 当我们要存储 k = 6 ,value = A 这个键值对时,哈希函数将会对 6 进行模 5 运算,即返回 1 ,所以在hash表索引为1的位置处,将A通过链表连接在索引为1处。
- 当我们要存储 k = 8 ,value = B 这个键值对时,哈希函数将会对 8 进行模 5 运算,即返回 3 ,所以在hash表索引为1的位置处,将 B 通过链表连接在索引为 3处。
- 当要存储 k = 11 ,value = C 键值对时,hash函数对 11 模 5 运算,得到还是 1 ,而此时索引为 1 处是一个链表,并且连接了一个 A在上面,此时 C 就将存储在链表的末尾,即 A 的后面。
2.1.2 开放寻址
不在链表中存储值,直接将其存储在数hash桶中
- 还是假设 hash 表有5个存储单元
- 当我们要存储 k = 6 ,value = A 这个键值对时,哈希函数将会对 6 进行模 5 运算,即返回 1 ,所以 A 将直接存储在索引为 1 的桶中
- 当我们要存储 k = 8 ,value = B 这个键值对时,哈希函数将会对 8 进行模 5 运算,即返回 3 ,所以 B 将存储在索引为 1 的桶中
- 现在要存储 k = 11 ,value = C 键值对时,hash函数对 11 模 5 运算,得到还是 1 ,此时索引为 1 的位置已经存放A了,此时必须寻找另外一个空位置。这个操作叫做 Probing (探测),即搜索,我们需要搜索另一个位置来存放 C 。
三种探测算法
- 线性探测(探测公式 ( hash(key)+i)%table_sizes )i:正整数
如果 1 号位置已经存放了数据,就去查看下一个位置,即索引为 2 的位置,直至找到一个空的位置,如果找完整张表,没找到空位,则说明表满了
缺点:当这样加入元素时,加入的元素连续,构成一个簇,后续往进添加元素,且这个元素的索引在这个簇中,就需要跳过这些位置,并在簇的末尾添加该元素,所以将会耗费太多时间在探测上,随着数据量的增大,效率会大大的降低。
使用线性探测法,每次迭代 i 增加 1 ,新的键值对彼此相邻存储并形成一个簇,而簇的产生导致插入效率大大降低,为了解决线性探测形成簇的问题,则使用接下来的二次探测方法
- 二次探测 (公式 : hash(key) + i2 )% table_seize )
使用 i2 将使键值对分布的越来越稀疏,但也造成了另外一个问题,即需要大量的跳跃来寻找空位,可能会出现在几个索引之间跳跃,导致无限循环的尴尬局面
- 双重散列
- 继续之前的例子
- 当我们要存储 k = 6 ,value = A 这个键值对时,初始哈希函数将会对 6 进行模 5 运算,即返回 1 ,所以 A 将直接存储在索引为 1 的桶中
- 当我们要存储 k = 8 ,value = B 这个键值对时,初始哈希函数将会对 8 进行模 5 运算,即返回 3 ,所以 B 将存储在索引为 1 的桶中
- 现在要存储 k = 11 ,value = C 键值对时,hash函数对 11 模 5 运算,得到还是 1 ,此时索引为 1 的位置已经存放A了,则使用双哈希算法来实现索引位置的确定 首先计算hash1,接着计算hash2,代入双哈希算法公式,得到索引位置。
使用双哈希算法来计算索引。
在存储时,不同的key通过hash函数可能产生相同的索引值,这种情况称之为hash碰撞,应对hash碰撞有几种不同的策略,在数组中使用链表,这种策略叫链接,或者直接在数组中保存键值对,这种情况遇到碰撞的话我们会进行探测一个新的槽位,这种策略叫开放寻址,而探测方法有三种,分别为线性探测、二次探测、双哈希探测。线性探测由于直接找相邻的槽位,多以会产生簇的情况,导致插入和检索时性能下降。二次探测使寻址进行更大的跳跃,从而避免形成簇,但是可能会产生无限循环跳跃的问题。双哈希中,使用hash函数来计算步长
3. Tree
- 二叉树的建立有两种方法
- 顺序存储:使用数组(需要构造完全二叉树,浪费空间)
- 链式存储:使用二叉链表(leftChild、data、rightChild);使用三叉链表(eftChild、data、rightChild、parent)。
- 遍历二叉树
先序遍历
中序遍历
后序遍历
- 遍历算法
- 递归
- 非递归
- 二叉树的深度和高度
- 深度是从根节点数到它的叶节点,高度是从叶节点数到它的根节点。
- 对于树中相同深度的每个结点来说,它们的高度不一定相同,这取决于每个结点下面的叶结点的深度。
树的高度:
节点的深度是从节点到树根节点的边数。 根节点的深度为 0。节点的高度是从节点到叶子的最长路径上的边数。 叶节点的高度为 0。
Q&A
判斷是否為 二叉搜索树(BST)
AVL 树
平衡二叉树:
它是一 棵空树或它的左右两个子树的高度差的绝对值不超过1
Java 多线程
用更少的时间干更多的事情,
- 造成多个线程尝试并发的修改对象,导致程序运行结果出现问题或者是导致程序崩溃。
- 还有就是产生一些可见性问题,一个线程改变了对象,但是该改变对其它线程不可见。
解决多线程产生的问题
- Confinement:限制约束,不能在不同线程之间共享数据
- Synchronization:同步,使用锁使代码按顺序运行
- Atomic types原子类型:incrementAndGet 比较和交换,比较当前值和期望值,如果不相等就交换
- Partitioning:分区,在Java集合中使用,并发访问不同的段或者是分区。
volatile:不稳定的,使用该关键词即告诉jvm,该值是不稳定的,不要一直使用cache中的缓存值,而是从内存中去读取,虽然这样会使得效率降低。
线程属于一次性消耗品,在执行完 run() 方法之后线程便会正常结束了,线程结束后便会销毁,不能再次 start,只能重新建立新的线程对象。
线程操作
- jdk 1.5 之后提出了线程池
Java中的引用
java内存管理分为内存分配和内存回收,都不需要程序员负责,垃圾回收的机制主要是看对象是否有引用指向该对象。
Java中提供这四种引用类型主要有两个目的:
第一是可以让程序员通过代码的方式决定某些对象的生命周期;第二是有利于JVM进行垃圾回收。
1. 强引用
- 指创建一个对象并把这个对象赋给一个引用变量。强引用有引用变量指向时永远不会被垃圾回收,JVM宁愿抛出OutOfMemory错误也不会回收这种对象。
- 如果想中断强引用和某个对象之间的关联,可以显示地将引用赋值为null,这样一来的话,JVM在合适的时间就会回收该对象。
2. 软引用
- 如果一个对象具有软引用,内存空间足够,垃圾回收器就不会回收它;
- 如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存,比如网页缓存、图片缓存等。使用软引用能防止内存泄露,增强程序的健壮性。
- 垃圾收集线程会在虚拟机抛出OutOfMemoryError之前回收软可及对象,而且虚拟机会尽可能优先回收长时间闲置不用的软可及对象,对那些刚刚构建的或刚刚使用过的“新”软可反对象会被虚拟机尽可能保留。
3.弱引用(WeakReference)
- 弱引用也是用来描述非必需对象的,当JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。在java中,用java.lang.ref.WeakReference类来表示。
- 注意,这里所说的被弱引用关联的对象是指只有弱引用与之关联,如果存在强引用同时与之关联,则进行垃圾回收时也不会回收该对象(软引用也是如此)。
4.虚引用(PhantomReference)
- 虚引用和前面的软引用、弱引用不同,它并不影响对象的生命周期。在java中用java.lang.ref.PhantomReference类表示。如果一个对象与虚引用关联,则跟没有引用与之关联一样,在任何时候都可能被垃圾回收器回收。
在使用软引用和弱引用的时候,我们可以显示地通过System.gc()来通知JVM进行垃圾回收,但是要注意的是,虽然发出了通知,JVM不一定会立刻执行,也就是说这句是无法确保此时JVM一定会进行垃圾回收的。
滑动窗口协议有
1、停止等待协议,发送窗口=1,接受窗口=1;
2、退后N帧协议,发送>1,接收=1;
3、选择重传协议,发送>1,接收>1;
export 用于修改环境变量
env 用于创建环境变量
备忘录模式:适用于恢复某个类的先前状态,有点类似于快照这类型功能。
原型模式:通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。就是java中的克隆技术,以某个对象为原型,复制出新的对象。显然,新的对象具备原型对象的特点。
状态模式:当一个对象,它具有很多种状态的,需要进行繁琐复杂的逻辑处理时,我们可以利用状态模式,将对象的各种状态进行类的封装,用于避免if else过于繁琐的情况。
命令模式:是一种数据驱动的设计模式,它属于行为型模式。请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。
建造者模式:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
适配器模式:将一个类的接口转换成另外一个客户希望的接口。Adapter 模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
桥接模式:将抽象部分与它的实现部分分离,使它们都可以独立地变化。
Object类有哪些方法
Object是所有类的父类,任何类都默认继承Object。Object类到底实现了哪些方法?
1.clone方法
保护方法,实现对象的浅复制,只有实现了Cloneable接口才可以调用该方法,否则抛出CloneNotSupportedException异常。
2.getClass方法
final方法,获得运行时类型。
3.toString方法
该方法用得比较多,一般子类都有覆盖。
4.finalize方法
该方法用于释放资源。因为无法确定该方法什么时候被调用,很少使用。
5.equals方法
该方法是非常重要的一个方法。一般equals和==是不一样的,但是在Object中两者是一样的。子类一般都要重写这个方法。
6.hashCode方法
该方法用于哈希查找,重写了equals方法一般都要重写hashCode方法。这个方法在一些具有哈希功能的Collection中用到。
一般必须满足obj1.equals(obj2)true。可以推出obj1.hash- Code()obj2.hashCode(),但是hashCode相等不一定就满足equals。不过为了提高效率,应该尽量使上面两个条件接近等价。
7.wait方法
wait方法就是使当前线程等待该对象的锁,当前线程必须是该对象的拥有者,也就是具有该对象的锁。wait()方法一直等待,直到获得锁或者被中断。wait(long timeout)设定一个超时间隔,如果在规定时间内没有获得锁就返回。
调用该方法后当前线程进入睡眠状态,直到以下事件发生。
(1)其他线程调用了该对象的notify方法。
(2)其他线程调用了该对象的notifyAll方法。
(3)其他线程调用了interrupt中断该线程。
(4)时间间隔到了。
此时该线程就可以被调度了,如果是被中断的话就抛出一个InterruptedException异常。
8.notify方法
该方法唤醒在该对象上等待的某个线程。
9.notifyAll方法
该方法唤醒在该对象上等待的所有线程。
装饰者模式:Java IO
FileInputStream –> BufferedInputStream –> LineNumberInputStream;
FileInputStream:被装饰的组件,实现文本的读取,BufferedInputStream :一个具体的装饰者,加入了两种方法,利用缓冲来改进性能,用readLine()来增强接口;LineNumberInputStream:也是一个具体的装饰者,它加上了计算行数的功能。
一个方法的递归版本与对等的迭代版本相比,通常运行速度更慢一些,而且使用更多的存储空间,计算机必须做额外的工作,即使用栈来跟踪递归。
Loading Comments...