数据结构-前置概念
时间和空间复杂度
时间复杂度
概念
算法的时间复杂度是一个科学函数,定量描述了算法的运行时间。算法中基本操作的执行次数就是算法的时间复杂度
大O的渐进表示法
用常数1取代运行时间中所有的加法常数,仅保留最高阶项,且最高阶项的系数恒为1
有些算法的的时间复杂度存在最好,平均和最坏情况(默认关注情况)。
计算时间复杂度:
int binarySearch(int[] array, int value) {int begin = 0;int end = array.length - 1;while (begin <= end) {int mid = begin + ((end-begin) / 2);if (array[mid] < value)begin = mid + 1;else if (array[mid] > value)end = mid - 1;elsereturn mid;}return -1; }
递归的时间复杂度=递归的次数*每次递归的执行次数
long factorial(int N) {return N < 2 ? N : factorial(N-1) * N; }O(N)->忽略-1
int fibonacci(int N) {return N < 2 ? N : fibonacci(N-1)+fibonacci(N-2);
}
O(2^N)
总结:计算时间复杂度要结合思想,不能一味的观察代码
空间复杂度
对一个算法在运行过程中临时占用储存空间大小的量度,计算的是变量的个数(额外的内存),也是用大O渐进表示法
1.冒泡排序的空间复杂度是O(1)
2.
int[] fibonacci(int n) {long[] fibArray = new long[n + 1];fibArray[0] = 0;fibArray[1] = 1;for (int i = 2; i <= n ; i++) {fibArray[i] = fibArray[i - 1] + fibArray [i - 2];}return fibArray; }O(N)
3.
long factorial(int N) {return N < 2 ? N : factorial(N-1)*N; }每一次递归都会给函数开辟内存
O(N)
常遇到复杂度有O(1),O(logN),O(N),O(N*logN),O(N^2) 未特殊声明,log都是以2为底
包装类
在Java中由于基本类型不是继承于Object,为了在泛型代码中可以支持基本类型,Java给每个基本类型都对应了一个包装类型

除了Integer和Character,其余基本类型的包装类都是首字母大写
装箱/装包:把基本数据类型变为包装类型
int a=10;Integer a1=Integer.valueOf(a);//显式装箱Integer a2=new Integer(a); Integer a3=a;//隐式装箱Integer a4=(Integer)a;
拆箱/拆包:包装类型变为基本数据类型
int a=10;Integer a1=Integer.valueOf(a);int a2= a1.intValue();//显式拆箱int a3=a1;//自动拆箱int a4=(int)a1;
面试题:
Integer a=100;Integer b=100;System.out.println(a==b);Integer c=200;Integer d=200;System.out.println(c==d);
原因:
Integer装箱的源码:
public static Integer valueOf(int i) {if (i >= IntegerCache.low && i <= IntegerCache.high)return IntegerCache.cache[i + (-IntegerCache.low)];return new Integer(i);}其中IntegerCache.low =-128 IntegerCache.higt =127
可以发现,凡是符合[-128,127]的256个数据都储存在了数组里,那么a和b的地址就是一样的,c和d的地址就不一样
想要比较值可以用.equals()
泛型
泛型是JDK1.5引入的新的语法,通俗讲就是:适用于许多许多类型。从代码上讲,就是对类型实现了参数化
引出泛型
泛型的主要目的是:指定当前容器,要持有什么类型的对象。让编译器去做检查
语法
声明:
class 泛型类名称<类型形参列表> {// 这里可以使用类型参数
}class 泛型类名称<类型形参列表> extends 继承类/* 这里可以使用类型参数 */ {// 这里可以使用类型参数
}
使用:
泛型类<类型实参> 变量名; // 定义一个泛型类引用
new 泛型类<类型实参>(构造方法实参); // 实例化一个泛型类对象
MyArray<Integer> list = new MyArray<Integer>();
裸类型:
表示一个泛型类但是没有带着类型实参
MyArray list = new MyArray();这是为了兼容老版本的API所保留的机制,我们不要去主动的使用
class Myarray<T>{public Object[] array=new Object[10];public void setArray(int pos,T value){array[pos]=value;}public T getArray(int pos){return (T)array[pos];}
}public static void main(String[] args){Myarray<Integer> myarray1 =new Myarray<Integer>();myarray1.setArray(0,22);Myarray<String> myarray2 =new Myarray<String>();myarray2.setArray(0,"Hello");String str=(String)myarray2.getArray(0);}
注意:
1.类名后<T>表示占位符,表示当前类是一个泛型类,类型必须是包装类
2.泛型是将数据类型参数化,进行传递
3.new 对象时后面<>可以不写类型
4.泛型目前为止的优点是:数据类型参数化,编译时自动进行类型检查和转换
了解:类型的形参一般使用一个大写字母表示
E:Element
K:Key
V:Value
N:Number
T:Type
泛型是如何编译的
擦除机制
在编译过程中将所有的T替换为Object这种机制称为擦除机制,以下是介绍https://zhuanlan.zhihu.com/p/51452375
https://zhuanlan.zhihu.com/p/51452375
此时尖括号当中的内容不参与类型的组成
Myarray<Integer> myarray1 =new Myarray<Integer>();
Myarray<String> myarray2 =new Myarray<String>();
myarray1和myarray2的类型就是Myarray类型
问题解答:
问:T[] ts =new T[5];编译T替换为Object,不是相当于:Object[] ts = new Object[5]吗?
答:1.Java在运行时会严格检查元素特性,而泛型编译后会发生“类型擦除”,泛型T的具体类型信息会被丢弃(如果没有指定边界,那被擦除为Object),这意味着创建数组时无法知道T是什么类型
问:类型擦除,一定是把T变成Object吗?
答:不一定。无界类型擦除为Object,有界类型擦除为对应的上界,多边界泛型擦除为第一个上界类型
泛型的上界
class 泛型类名称<类型形参 extends 类型边界> {...
}
//
public class MyArray<E extends Number> {...
}
此时只接受Number或者Number的子类作为E的类型实参
MyArray<Integer> l1; // 正常,因为 Integer 是 Number 的子类型
MyArray<String> l2; // 编译错误,因为 String 不是 Number 的子类型
没有指定E的边界那么可以视为 E extends Object
复杂示例:
public class Person implements Comparable<Person>{public String name;public int age;public int compareTo(Person o){return this.name.compareTo(o.name);}public Person(String name, int age) {this.name = name;this.age = age;}
}
class Max<T extends Comparable<T>>{public T toMax(T[] arrays){T tmp=arrays[0];for(int i=1;i<arrays.length;i++){if(tmp.compareTo(arrays[i])<0){tmp=arrays[i];}}return tmp;}
}public class Test {public static void main(String[] args){Person p1=new Person("zhangsan",1);Person p2 =new Person("lisi",2);Person[] ps={p1,p2};Integer[] array={0,1,2,3,4,5,6};Max<Integer> max=new Max<>();Integer i=max.toMax(array);Max<Person> max2=new Max<>();System.out.println(max2.toMax(ps));System.out.println(i);}
}
泛型方法
方法限定符 <类型形参列表> 返回值类型 方法名称(形参列表) { ... }
//静态的泛型方法 需要在static后用<>声明泛型类型参数
class Max{public static<T extends Comparable<T>> T toMax(T[] arrays){//静态方法T tmp=arrays[0];for(int i=1;i<arrays.length;i++){if(tmp.compareTo(arrays[i])<0){tmp=arrays[i];}}return tmp;}
}public class Test {public static void main(String[] args){Person p1=new Person("zhangsan",1);Person p2 =new Person("lisi",2);Person[] ps={p1,p2};Integer[] array={0,1,2,3,4,5,6};Integer i=Max.toMax(array);//类型推导System.out.println(Max.<Person>toMax(ps));//不使用类型推导}
}
JDK17新增的特性
var关键字
从Java10开始,var被引用,用于局部变量
var num =10;var str="Hello";
自动识别后面的类型,更加简洁
注意:var不能声明字段(属于类的变量),方法参数,返回值且必须初始化(不能初始化为null)
密封类
sealed修饰也表示密封类
sealed class Animal permits Dog, Dog1 {
}
//Dog1也是密封类
final class Dog1 extends Animal{
}
//Dog无限制,任何类都可继承
non-sealed class Dog extends Animal{
}
sealed修饰的类必须有子类,而且子类必须是final,sealed,non-sealed
没有写permits表示都允许
接口的私有方法
Java8,接口可以有默认方法,Java9之后,接口内可以实现私有方法,仅为接口提供服务
instanceof
允许在判断类型的同时,声明一个变量
Object obj="Hello";if(obj instanceof String str){str="Hello";}




