Java面试复习
Java面试大纲
立志于找到一份Java开发工作
Java开发环境
1、Java编译过程
- Java语言编译原理
- 编写Java源文件(.Java)首先要经过编译,生成字节码文件(.class);
- Java程序运行需要JVM的支持,JVM是安装在操作系统上的软件,为字节码文件提供运行环境;
Java源文件---------------->字节码文件------------------->JVM(Java虚拟机-----操作系统)
- 一次编程到处使用–跨平台上
- Java官方提供了针对不同平台的JVM软件,遵循着相同的规则,只要是.class文件,就可以在不同的JVM上运行
- Java运行时期
- 编译期:Java源程序------javac编译命令------>.class字节码文件
- 运行期:-----通过Java命令启动jvm---->{jvm:加载.class----->运行.class}
2、JVM,JRE,JDK之间的关系
- JDK:java developmenmt kit (java开发工具包)
- JRE:java runtime environment (Java运行环境)
- JVM: java virtual mechines (Java虚拟机)
- JRE:java runtime environment (Java运行环境)
基本数据类型
1、八种基本类型
-
byte :1字节,存储字节数据
-
short :2字节,兼容性考虑
-
int :4字节,存储普通整数
-
long :8字节,长整数
-
float :4字节,存储浮点数
-
double :8字节,双精度浮点数
-
boolean:1字节,true/false
-
char :2字节,存储一个字符
2、数据类型详解
- 公式:-2^(n-1) ~~~ 2^(n-1) -1
- int的取值范围: -2^31 ~ 2^31-1
- long的取值范围: -263~263-1
- short的取值范围:-215~215-1
- byte的取值范围: -27~27-1 (-128~127)
- 通过时间毫秒数来存储日期和时间
- JDK提供了一个方法,返回1970年1月1日0点0分0秒到此时此刻所经历的毫秒数,其数据类型为 long
long time = System.currentTimeMillis();
System.out.println(time);
- double运算时会出现舍入误差(面试)
2进制中无法精确地表示1/10,就和10进制无法精确表示1/3一样。
- 二进制表示10进制可能会有误差
double money = 3.0;
double price =2.9;
System.out.println(money- price);//输出结果为0.100000000009 舍入误差!
- char
- 本质是一个16位无符号整数,所在编码Unicode编码
- 整数变量:0~65535之间的整数数值
- 字符直接量:用单引号括起来的内容就为字符的实际内容
- Unicode形式:‘\uoo4e’…Unicode的16进制形式
//1.char型变量赋值
char c1 = 65;
char c2 = 'A';
char c3 = '\u0041';
System.out.println(c1);//A
System.out.println(c2);//A
System.out.println(c3);//A
- 转义字符
转义 | 含义 |
---|---|
‘\n’ | 回车符 |
‘\r’ | 换行符 |
‘’’ | 单引号 |
‘"’ | 双引号 |
‘’ | 单斜杠 |
3.基本类型间的转换
-
基本类型转换
基本类型转换(隐式类型转换):小类型–>大类型
byte -->short -->int -->long -->float -->doublechar ---------------^
-
强制类型转换:大类型–>小类型
- 需要转换符
- 造成精度丢失或者数据溢出
5.数据类型之间的转换
(1)、字符串如何转基本数据类型?
调用基本数据类型对应的包装类中的方法 parseXXX(String)或 valueOf(String)即可返回相应基本类型。
(2)、基本数据类型如何转字符串?
一种方法是将基本数据类型与空字符串(“”)连接(+)即可获得其所对应的字符串;另一种方法是调用 String
类中的 valueOf()方法返回相应字符串。
运算符
1.& 和 && 的区别
&运算符有两种用法:(1)按位与;(2)逻辑与。
&&运算符是短路与运算。逻辑与跟短路与的差别是非常巨大的,虽然二者都要求运算符左右两端的布尔值都是
true 整个表达式的值才是 true。
&&之所以称为短路运算是因为,如果&&左边的表达式的值是 false,右边的表达式会被直接短路掉,不会进行
运算。很多时候我们可能都需要用&&而不是&,例如在验证用户登录时判定用户名不是 null 而且不是空字符串,应
当写为 username != null &&!username.equals(“”),二者的顺序不能交换,更不能用&运算符,因为第一个条件如
果不成立,根本不能进行字符串的 equals 比较,否则会产生 NullPointerException 异常。
注意:逻辑或运算符(|)和短路或运算符(||)的差别也是如此。
常考关键字
1.关键字static
- static存在的主要意义
static的主要意义是在于创建独立于具体对象的域变量或者方法。以致于即使没有创建对象,也能使用属性和调用方法!
为什么说static块可以用来优化程序性能,是因为它的特性:只会在类加载的时候执行一次。因此,很多时候会将一些只需要进行一次的初始化操作都放在static代码块中进行。 - static修饰
- static修饰变量
将修饰的成员变量存储在静态存储区,不在为对象所有,而属于类属性 - static修饰方法
可以使用"类名.方法名"的方式操作方法,避免了先要new出对象的繁琐和资源消耗 - static修饰代码块
只会在类加载的时候执行一次,用来优化程序性能 - static静态导包
将类的方法直接导入到当前类中,从而直接使用“方法名”即可调用类方法,更加方便
- static修饰变量
2.关键字final
- final的几种用法
- 修饰变量
- 用final关键字修饰的变量,只能进行一次赋值操作,并且在生成周期内无法改变它的值。final修饰的变量可以先声明,后赋值。
- final修饰基本数据类型时,起到了常量作用;
- final修饰引用数据类型时,引用的是对象的地址,其值可以被修改;
- final在修饰成员变量时,必须在定义或者构造器中进行初始化赋值,而局部变量只需保证在使用之前被初始化赋值;
- 修饰方法参数
- 编写方法时,可以在参数前面加上final关键字
- 修饰方法
- final关键字修饰方法,表示该方法不能被覆盖(重写);另外,类中所有的private方法都隐式地指定为是final的。
- 修饰类
- 用final修饰的类是无法被继承。且final类中的所有成员方法都是被隐式的指定为final方法
- 修饰变量
- final变量和普通变量的区别
- 当final变量是基本数据类型以及String类型时,如果在编译期间知道它的确切值,则会将其当作编译期常量使用,而普通变量的访问则需要在运行时通过链接来进行;
- final、static、static final修饰的字段赋值的区别
- static关键字:static修饰的字段会在类加载过程中的准备阶段被初始化为0或者null,而后在初始化阶段(触发类构造器)才会被赋值,没有则默认
- final关键字:final修饰的字段在运行时被初始化(可以直接赋值或者在实例构造器中赋值),一旦赋值不可更改
- static final修饰的字段在javac时生成Constant Value属性,在类加载的准备阶段根据该属性的值为该字段赋值,没有默认值,必须显式的赋值。理解:在编译期间就把结果放入了常量池
- final关键字的好处
- final关键字提高了性能,JVM和Java应用都会缓存final变量
- final变量可以安全的在多线程环境下进行共享,而不需要额外的同步开销
- 使用final关键字,JVM会对方法,变量以及类进行优化
- 在匿名类中所有变量都必须时final变量
- final于abstract相反
- 使用final方法的原因
- 锁定方法,防止任何继承类修改它的含义
- 效率方面:已经不需要了
-
final、finally、finalize 有什么区别?
final可以修饰类,变量,方法,修饰的类不能被继承,修饰的变量不能重新赋值,修饰的方法不能被重写finally用于抛异常,finally代码块内语句无论是否发生异常,都会在执行finally,常用于一些流的关闭。
finalize方法用于垃圾回收。
一般情况下不需要我们实现finalize,当对象被回收的时候需要释放一些资源,比如socket链接,在对象初始化时创建,整个生命周期内有效,那么需要实现finalize方法,关闭这个链接。
但是当调用finalize方法后,并不意味着gc会立即回收该对象,所以有可能真正调用的时候,对象又不需要回收了,然后到了真正要回收的时候,因为之前调用过一次,这次又不会调用了,产生问题。所以,不推荐使用finalize方法。
数组
1.数组元素的反转
//方法一:
for(int i = 0;i < arr.length / 2;i++){
String temp = arr[i];
arr[i] = arr[arr.length - i -1];
arr[arr.length - i -1] = temp;
}
//方法二:
for(int i = 0,j = arr.length - 1;i < j;i++,j--){
String temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
2.数组正序和逆序输出
//1.定义数组,长度为10
int[] num = new int[10];
//2.通过元素下标赋值随机数
for (int i = 0; i < num.length; i++) {
Random ran = new Random();
num[i] = ran.nextInt(100);
}
//3.正序遍历
for (int i = 0; i < num.length; i++) {
System.out.print(num[i] + " ");
}
System.out.println();
//4.逆序遍历
for (int i = num.length-1; i >= 0; i--) {
System.out.print(num[i] + " ");
}
3.手写冒泡排序
int[] arr = new int[]{43,32,76,-98,0,64,33,-21,32,99};
//冒泡排序
for(int i = 0;i < arr.length - 1;i++){
for(int j = 0;j < arr.length - 1 - i;j++){
if(arr[j] > arr[j + 1]){
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
4.Arrays工具类
//1.boolean equals(int[] a,int[] b):判断两个数组是否相等。
int[] arr1 = new int[]{1,2,3,4};
int[] arr2 = new int[]{1,3,2,4};
boolean isEquals = Arrays.equals(arr1, arr2);
System.out.println(isEquals);
//2.String toString(int[] a):输出数组信息。
System.out.println(Arrays.toString(arr1));
//3.void fill(int[] a,int val):将指定值填充到数组之中。
Arrays.fill(arr1,10);
System.out.println(Arrays.toString(arr1));
//4.void sort(int[] a):对数组进行排序。
Arrays.sort(arr2);
System.out.println(Arrays.toString(arr2));
//5.int binarySearch(int[] a,int key)
int[] arr3 = new int[]{-98,-34,2,34,54,66,79,105,210,333};
int index = Arrays.binarySearch(arr3, 210);
if(index >= 0){
System.out.println(index);
}else{
System.out.println("未找到");
}
//6.copyof
int[] arr = {1,2,3,4,5,6};
arr = Arrays.copyOf(arr,7);
面向对象
1.封装、继承、多态
- 继承:继承是从已有类得到继承信息创建新类的过程。提供继承信息的类被称为父类(超类、基类);得到继
承信息的类被称为子类(派生类)。继承让变化中的软件系统有了一定的延续性,同时继承也是封装程序中可变因素的重要手段。 - 封装:通常认为封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口。面向对象
的本质就是将现实世界描绘成一系列完全自治、封闭的对象。我们在类中编写的方法就是对实现细节的一种封装;我们编写一个类就是对数据和数据操作的封装。可以说,封装就是隐藏一切可隐藏的东西,只向外界提供最简单的编程接口。 - 多态性:多态性是指允许不同子类型的对象对同一消息作出不同的响应。简单的说就是用同样的对象引用调
用同样的方法但是做了不同的事情。多态性分为编译时的多态性和运行时的多态性。 - 注意:默认情况下面向对象有 3 大特性,封装、继承、多态,如果面试官问让说出 4 大特性,那么我们就把抽象
加上去。
2.重载(overload)和重写(override)的区别?重载的方法能否根据返回类型进行区分
方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态
性。重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为重载;重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。重载对返回类型没有特殊的要求。
方法重载的规则:
1.方法名一致,参数列表中参数的顺序,类型,个数不同。
2.重载与方法的返回值无关,存在于父类和子类,同类中。
3.可以抛出不同的异常,可以有不同修饰符。
方法重写的规则:
1.参数列表必须完全与被重写方法的一致,返回类型必须完全与被重写方法的返回类型一致。
2.构造方法不能被重写,声明为 final 的方法不能被重写,声明为 static 的方法不能被重写,但是能够被再次
声明。
3.访问权限不能比父类中被重写的方法的访问权限更低。
4.重写的方法能够抛出任何非强制异常(UncheckedException,也叫非运行时异常),无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。
3.抽象类(abstract class)和接口(interface)有什么异同?
抽象类:
1.抽象类中可以定义构造器
2.可以有抽象方法和具体方法
3.接口中的成员全都是 public 的
4.抽象类中可以定义成员变量
5.有抽象方法的类必须被声明为抽象类,而抽象类未必要有抽象方法
6.抽象类中可以包含静态方法
7.一个类只能继承一个抽象类
接口:
1.接口中不能定义构造器
2.方法全部都是抽象方法
3.抽象类中的成员可以是 private、默认、protected、public
4.接口中定义的成员变量实际上都是常量
5.接口中不能有静态方法
6.一个类可以实现多个接口
相同:
1.不能够实例化
2.可以将抽象类和接口类型作为引用类型
3.一个类如果继承了某个抽象类或者实现了某个接口都需要对其中的抽象方法全部进行实现,否则该类仍然需要
被声明为抽象类
4.Java 中实现多态的机制是什么?
靠的是父类或接口定义的引用变量可以指向子类或具体实现类的实例对象,而程序调用的方法在运行期才动态绑定,就是引用变量所指向的具体实例对象的方法,也就是内存里正在运行的那个对象的方法,而不是引用变
量的类型中定义的方法。
内置类
1.String、StringBuffer、StringBuilder 的区别?
(1)、可变不可变
String:字符串常量,在修改时不会改变自身;若修改,等于重新生成新的字符串对象。
StringBuffer:在修改时会改变对象自身,每次操作都是对 StringBuffer 对象本身进行修改,不是生成新的对
象;使用场景:对字符串经常改变情况下,主要方法:append(),insert()等。
(2)、线程是否安全
String:对象定义后不可变,线程安全。
StringBuffer:是线程安全的(对调用方法加入同步锁),执行效率较慢,适用于多线程下操作字符串缓冲区
大量数据。
StringBuilder:是线程不安全的,适用于单线程下操作字符串缓冲区大量数据。
(3)、共同点
StringBuilder 与 StringBuffer 有公共父类 AbstractStringBuilder(抽象类)
StringBuilder、StringBuffer 的方法都会调用 AbstractStringBuilder 中的公共方法,如 super.append(…)。
只是 StringBuffer 会在方法上加 synchronized 关键字,进行同步。最后,如果程序不是多线程的,那么使用
StringBuilder 效率高于 StringBuffer。
System
Collection集合
|----Collection接口:单列集合,用来存储一个一个的对象
-
|----List接口:存储有序的、可重复的数据。 -->“动态”数组
-
|----ArrayList、LinkedList、Vector
-
|----Set接口:存储无序的、不可重复的数据 -->高中讲的“集合”
-
|----HashSet、LinkedHashSet、TreeSet
1.Collection接口中的方法
boolean add(Object element); 增添元素,返回true,false
boolean remove(Object element); 删除元素,成功删除返回true,没有成功返回false
void clear(); 清空集合中的元素
int size(); 集合的长度 ,返回int值
boolean isEmpty(); 判断集合是否为空,返回true,false
boolean contains(Object element); 判断集合中是否包含给定的元素 返回true,flase
boolean retainAll(Collection c); 求交集,集合数据发生变化返回true, 不变返回false
2.List接口
共有的特点:
1.有序(按照添加元素的顺序排序)
2.可以存储重复元素
2.2ArrayList(实现类)
-
ArrayList底层结构
底层是一个动态数组 -
ArrayList特点:
List共同特点:1.有序, 2.可以添加重复元素
优点:查询快(因为是数组)
缺点:对中间操作慢(后面元素的位置需要发生改变),空间利用率低(数组在底层存储需要连续空间) -
常用方法:
add(int index, E element)get(int index)
indexOf(Object o)
lastIndexOf(Object o)
remove(int index) 删除并返回指定位置元素
removeRange(int fromIndex, int toIndex) 删除指定区间的元素(子类继承使用)
set(int index, E)
-
源码分析
在jdk1.7之前底层默认创建一个长度为10的Object[],当数组装满时,添加元素会创建一个原来长度为1.5的新数组,将原数据复制过来;
在jdk1.7之后,底层先创建一个无长度的数组,在调用add方法后才会确认长度,扩容机制一致
2.2LinkedList(实现类)
- LinkedList的底层结构
底层时链表实现的 - LinkedList的特点
优点:中间操作快(只需改变后继节点的位置),空间利用率高(可以在任何位置添加元素,无需联系空间)
缺点:查询慢(从头/尾开始查询,直到找到位置) - 常用方法:
add(int index,Object element)
addFirist(Object element)
addLast(Object element)
get(int index)
removeFirst()
removeLast()
remove(int index)
getFirst()
getLast()
2.3Vector(实现类)
- Vector底层结构
底层也是通过数组实现 - Vector特点:
因为底层是一个数组,所以和ArrayList的特点一致,但是添加了同步锁的原因,变为线程安全的
3. Set(接口)
共有的特点:
1.无序(不按照添加元素的顺序排序)
2.不能存储重复元素
保证不能重复的原因:
当set方法调用add方法进行添加是,实际上调用了HashMap的put方法,调用hashcode()方法计算哈希值再通过equals哈希值的原因比较来确保唯一性
3.扩容:初始容量为16,负载因子0.75,扩容增量1被
3.1 hashSet(实现类)
- HashSet底层结构:
在jdk1.8之前,采用数组+链表结构实现
在jdk1.8中,采用数组+链表+红黑树实现,当链表长度超过阈值8时,将链表转换成为红黑树 - 特点:
- 存储唯一元素并允许为空值
- 由hashMap支持
- 不保证插入顺序
- 非线程安全 - LinkedHashSet:
作为HashSet的子类;遍历其内部数据时,可以按照添加的顺序遍历
3.2TreeSet
TreeSet底层结构:
TreeSet底层数据结构是二叉树(红黑树是一种自平衡的二叉树)
TreeSet的特点:
1.自然排序中,比较两个对象是否相同的标准为:compareTo()返回0.不再是equals().
2.定制排序中,比较两个对象是否相同的标准为:compare()返回0.不再是equals().
- TreeSet的使用
6.1 使用说明:
1.向TreeSet中添加的数据,要求是相同类的对象。
2.两种排序方式:自然排序(实现Comparable接口 和 定制排序(Comparator)
Map集合
|----Map:双列数据,存储key-value对的数据 —类似于高中的函数:y = f(x)
-
|----HashMap:作为Map的主要实现类;线程不安全的,效率高;存储null的key和value
-
|----LinkedHashMap:保证在遍历map元素时,可以照添加的顺序实现遍历。
-
原因:在原的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素。
-
对于频繁的遍历操作,此类执行效率高于HashMap。
-
|----TreeMap:保证照添加的key-value对进行排序,实现排序遍历。此时考虑key的自然排序或定制排序
-
底层使用红黑树
-
|----Hashtable:作为古老的实现类;线程安全的,效率低;不能存储null的key和value
-
|----Properties:常用来处理配置文件。key和value都是String类型
-
HashMap的底层:数组+链表 (jdk7及之前)
-
数组+链表+红黑树 (jdk 8)
1.存储结构的理解:
Map中的key:无序的、不可重复的,使用Set存储所的key —> key所在的类要重写equals()和hashCode() (以HashMap为例)
Map中的value:无序的、可重复的,使用Collection存储所的value —>value所在的类要重写equals()
一个键值对:key-value构成了一个Entry对象。
Map中的entry:无序的、不可重复的,使用Set存储所的entry
2.常用方法
- 添加:put(Object key,Object value)
- 删除:remove(Object key)
- 修改:put(Object key,Object value)
- 查询:get(Object key)
- 长度:size()
- 遍历:keySet() / values() / entrySet()
添加、删除、修改操作:
Object put(Object key,Object value):将指定key-value添加到(或修改)当前map对象中
void putAll(Map m):将m中的所key-value对存放到当前map中
Object remove(Object key):移除指定key的key-value对,并返回value
void clear():清空当前map中的所数据
元素查询的操作:
Object get(Object key):获取指定key对应的value
boolean containsKey(Object key):是否包含指定的key
boolean containsValue(Object value):是否包含指定的value
int size():返回map中key-value对的个数
boolean isEmpty():判断当前map是否为空
boolean equals(Object obj):判断当前map和参数对象obj是否相等
元视图操作的方法:
Set keySet():返回所key构成的Set集合
Collection values():返回所value构成的Collection集合
Set entrySet():返回所key-value对构成的Set集合
1.HashMap
-
HashMap的特点:
- hashMap底层是哈希表,查询速度非常快
- hashMap是无序集合,存储元素和取出元素的顺序可能不一样
- 集合是不同步的,线程不安全的,速度快
- 存储null的key和value
-
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。
- 在实例化以后,底层创建了长度是16的一维数组Entry[] table。
补充:关于情况2和情况3:此时key1-value1和原来的数据以链表的方式存储。
在不断的添加过程中,会涉及到扩容问题,当超出临界值(且要存放的位置非空)时,扩容。默认的扩容方式:扩容为原来容量的2倍,并将原的数据复制过来。
- HashMap在jdk8中相较于jdk7在底层实现方面的不同:
- new HashMap():底层没创建一个长度为16的数组
- jdk 8底层的数组是:Node[],而非Entry[]
- 首次调用put()方法时,底层创建长度为16的数组
- jdk7底层结构只:数组+链表。jdk8中底层结构:数组+链表+红黑树。
- 形成链表时,七上八下(jdk7:新的元素指向旧的元素。jdk8:旧的元素指向新的元素)
- 当数组的某一个索引位置上的元素以链表形式存在的数据个数 > 8 且当前数组的长度 > 64时,此时此索引位置上的所数据改为使用红黑树存储。
2.LinkedHashMap
LinkedHashMap底层使用的结构与HashMap相同,因为LinkedHashMap继承于HashMap.
区别就在于:LinkedHashMap内部提供了Entry,替换HashMap中的Node.
底层原理:哈希表+链表(记录元素顺序)
特点:
- LinkedHashMap底层是一个哈希表+链表(保证迭代顺序)
- LindedHashMap是一个有序的集合,存储元素和取出元素的顺序一致
总结:元素存储有序
原因:在原的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素。
对于频繁的遍历操作,此类执行效率高于HashMap。
3.HashTable
作为古老的实现类;线程安全的,效率低;不能存储null的key和value
4.TreeMap
保证照添加的key-value对进行排序,实现排序遍历。此时考虑key的自然排序或定制排序
底层使用红黑树
//向TreeMap中添加key-value,要求key必须是由同一个类创建的对象
//因为要照key进行排序:自然排序 、定制排序
5.使用Properties读取配置文件
//Properties:常用来处理配置文件。key和value都是String类型
public static void main(String[] args) {
FileInputStream fis = null;
try {
Properties pros = new Properties();
fis = new FileInputStream("jdbc.properties");
pros.load(fis);//加载流对应的文件
String name = pros.getProperty("name");
String password = pros.getProperty("password");
System.out.println("name = " + name + ", password = " + password);
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fis != null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
6.Collections工具类
-
常用方法
-
reverse(List):反转 List 中元素的顺序 shuffle(List):对 List 集合元素进行随机排序
-
sort(List):根据元素的自然顺序对指定 List 集合元素升序排序 sort(List,Comparator):根据指定的
-
Comparator 产生的顺序对 List 集合元素进行排序 swap(List,int, int):将指定 list 集合中的 i 处元素和 j 处元素进行交换 Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素 Object
-
max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素 Object
-
min(Collection) Object min(Collection,Comparator) int
-
frequency(Collection,Object):返回指定集合中指定元素的出现次数 void copy(List
-
dest,List src):将src中的内容复制到dest中 boolean replaceAll(List list,Object
-
oldVal,Object newVal):使用新值替换 List 对象的所旧值
IO流
1.流的分类
- 1.操作数据单位:字节流、字符流
- 2.数据的流向:输入流、输出流
- 3.流的角色:节点流、处理流
2.流的体系结构
3.重点说明的几个流结构
4.输入、输出的标准化过程
4.1 输入过程
① 创建File类的对象,指明读取的数据的来源。(要求此文件一定要存在)
② 创建相应的输入流,将File类的对象作为参数,传入流的构造器中
③ 具体的读入过程:
创建相应的byte[] 或 char[]。
④ 关闭流资源
说明:程序中出现的异常需要使用try-catch-finally处理。
4.2 输出过程
① 创建File类的对象,指明写出的数据的位置。(不要求此文件一定要存在)
② 创建相应的输出流,将File类的对象作为参数,传入流的构造器中
③ 具体的写出过程:
write(char[]/byte[] buffer,0,len)
异常
1.异常的分类
异常(Exception)的分类:
- 编译时期异常:Checked异常。在编译时期,就会检查,如果没有处理异常,则编译失败
- 运行时期异常:Runtime异常。在运行时期,检查异常,在编译时期不报错
2.异常处理语句
Java异常处理的五个关键字:try - catch -finally;throw , throws;
-
多个异常时的处理方式
多种异常处理方式,要求catch中的异常不能相同,并且若catch中的多个异常之间有子夫类异常关系,那么子类异常要求再上面的catch处理,父类异常再下面的catch处理 -
throw和throws的区别
(1)throw
作用在方法内,表示抛出具体异常,由方法体内的语句处理;一定抛出了异常;(2)throws
作用在方法的声明上,表示抛出异常,由调用者来进行异常处理;可能出现异常,不一定会发生异常; -
常见的异常
- ClassCastException:类型转换异常
- ArrayIndexOutOfBoundsException:数组下标越界异常
- NullPointerException:空指针异常。
- ArithmeticException:算法异常。
- NumberFormatException:数字格式化异常。
- FileNotFoundException:文件未找到异常。
- FileAlreadyExistsException:文件已经存在异常。
多线程
1.什么是多线程
多线程是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务。
好处:提高cpu的利用率;在多线程程序中,一个线程必须等待的时候,cpu可以运行其他的线程而不是等待。
2.多线程有哪几种实现方式?
- 继承Thread类,优点:可以直接调用start方法启动。缺点:单继承,需要重写run方法,无返回值
- 实现Runnable接口,优点:多实现或单继承。缺点:不可以直接启动,需要通过构造一个Thread把自己传进去,需要重写run方法,无返回值
- 实现Callable接口,优点:可以抛出异常,有返回值;缺点:jdk1.5之后才支持。需要重写call方法;结合FutureTask和Thread类一起使用,最后调用start启动
3.start()和run()的区别
- start()用于启动线程,当调用start方法时,线程不会马上执行,而会处于就绪状态,等待cpu分配的时间片后开始运行
- run方法用于子类重写父类来实现线程的功能;如果单独使用run方法,其实时再main线程情况下运行,相当于一个普通的run方法
4. Synchronized的原理
-
可重入性:
Synchronized的锁对象中有一个计数器(recursions变量)会记录线程获得几次锁: -
不可中断性:
一个线程获得锁后,另一个线程想要获得锁,必须处于阻塞或者等待状态,如果第一个线程不释放锁,第二个线程会一直被阻塞,不可被中断
5.Java如何实现多线程之间的通信
1.Object类的wait(),natify(),natifyAll()
2.Condition类的await().singal(),singalAll()
直接数据通信:
通过管道进行线程间的通信:IO流
6.线程池的理解
-
线程池的重要参数
- corePoolSize:核心线程数
- maximumPoolSize:最大线程数
- keepAliveTime:空闲存活时间
- unit:时间单位
- workQueue:任务队列
- threadFactory:线程工厂
- handler:拒绝策略
-
常用的几种线程池
● newSingleThreadExecutor:创建一个单线程的线程池,此线程池保证所有任务的执行顺序按照任务的提交顺序执行。● newFixedThreadPool:创建固定大小的线程池,每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。
● newCachedThreadPool:创建一个可缓存的线程池,此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
● newScheduledThreadPool:创建一个大小无限的线程池,此线程池支持定时以及周期性执行任务的需求。
● newSingleThreadExecutor:创建一个单线程的线程池。此线程池支持定时以及周期性执行任务的需求。
-
线程池的启动策略
1、线程池刚创建时,里面没有一个线程。任务队列是作为参数传进来的。不过,就算队列里面有任务,线程池也不会马上执行它们。2、当调用execute()方法添加一个任务时,线程池会做如下判断:
(1)如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务; (2)如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列; (3)如果这时候队列满了,而且正在运行的线程数量小于maximumPoolSize,那么还是要创建线程运行这个任务; (4)如果队列满了,而且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会抛出异常,告诉调用者“我不能再接受任务了”。 (5)当一个线程完成任务时,它会从队列中取下一个任务来执行。
(6)当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到corePoolSize的大小。
Socket、网络通信
1.Socket 类:
该类实现客户端套接字,套接字指的是两台设备之间通讯的端点。
- 构造方法
public Socket(String host, int port) :创建套接字对象并将其连接到指定主机上的指定端口号。如果指定的host是null ,则相当于指定地址为回送地址。
回送地址(127.x.x.x) 是本机回送地址(Loopback Address),主要用于网络软件测试以及本地机进程间通信,无论什么程序,一旦使用回送地址发送数据,立即返回,不进行任何网络传输。
构造举例,代码如下:
Socket client = new Socket(“127.0.0.1”, 6666);
- 成员方法
public InputStream getInputStream() : 返回此套接字的输入流。
如果此Scoket具有相关联的通道,则生成的InputStream 的所有操作也关联该通道。
关闭生成的InputStream也将关闭相关的Socket。
public OutputStream getOutputStream() : 返回此套接字的输出流。
如果此Scoket具有相关联的通道,则生成的OutputStream 的所有操作也关联该通道。
关闭生成的OutputStream也将关闭相关的Socket。
public void close() :关闭此套接字。
一旦一个socket被关闭,它不可再使用。
关闭此socket也将关闭相关的InputStream和OutputStream 。
public void shutdownOutput() : 禁用此套接字的输出流。
任何先前写出的数据将被发送,随后终止输出流。
2.网络编程
一、实现网络通信需要解决的两个问题
- 1.如何准确地定位网络上一台或多台主机;定位主机上的特定的应用
- 2.找到主机后如何可靠高效地进行数据传输
二、网络通信的两个要素:
- 1.对应问题一:IP和端口号
- 2.对应问题二:提供网络通信协议:TCP/IP参考模型(应用层、传输层、网络层、物理+数据链路层)
三、通信要素一:IP和端口号
1.IP的理解
-
- IP:唯一的标识 Internet 上的计算机(通信实体)
-
- 在Java中使用InetAddress类代表IP
-
- IP分类:IPv4 和 IPv6 ; 万维网 和 局域网
-
- 域名: www.baidu.com www.mi.com www.sina.com www.jd.com
- 域名解析:域名容易记忆,当在连接网络时输入一个主机的域名后,域名服务器(DNS)负责将域名转化成IP地址,这样才能和主机建立连接。 -------域名解析
-
- 本地回路地址:127.0.0.1 对应着:localhost
2.InetAddress类:此类的一个对象就代表着一个具体的IP地址
-
实例化
getByName(String host) 、 getLocalHost() -
常用方法
getHostName() / getHostAddress() -
端口号:正在计算机上运行的进程。
- 要求:不同的进程不同的端口号
- 范围:被规定为一个 16 位的整数 0~65535。
端口号与IP地址的组合得出一个网络套接字:Socket
四、通信要素二:网络通信协议
-
分型模型
-
TCP和UDP的区别
-
TCP三次握手和四次挥手
函数式接口
- 函数式接口的使用说明
如果一个接口中,只声明了一个抽象方法,则此接口就称为函数式接口。
我们可以在一个接口上使用 @FunctionalInterface 注解,这样做可以检查它是否是一个函数式接口。
Lambda表达式的本质:作为函数式接口的实例
- Java8中关于Lambda表达式提供的4个基本的函数式接口:
Stream流
- Stream API的理解:
1.1 Stream关注的是对数据的运算,与CPU打交道
集合关注的是数据的存储,与内存打交道
1.2 java8提供了一套api,使用这套api可以对内存中的数据进行过滤、排序、映射、归约等操作。类似于sql对数据库中表的相关操作。
- 注意点:
- ①Stream 自己不会存储元素。
- ②Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
- ③Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。
- Stream的使用流程:
- ① Stream的实例化
- ② 一系列的中间操作(过滤、映射、…)
- ③ 终止操作
- 使用流程的注意点:
- 4.1 一个中间操作链,对数据源的数据进行处理
- 4.2 一旦执行终止操作,就执行中间操作链,并产生结果。之后,不会再被使用
- 步骤一:Stream实例化
//创建 Stream方式一:通过集合
@Test
public void test1(){
List<Employee> employees = EmployeeData.getEmployees();
// default Stream<E> stream() : 返回一个顺序流
Stream<Employee> stream = employees.stream();
// default Stream<E> parallelStream() : 返回一个并行流
Stream<Employee> parallelStream = employees.parallelStream();
}
//创建 Stream方式二:通过数组
@Test
public void test2(){
int[] arr = new int[]{1,2,3,4,5,6};
//调用Arrays类的static <T> Stream<T> stream(T[] array): 返回一个流
IntStream stream = Arrays.stream(arr);
Employee e1 = new Employee(1001,"Tom");
Employee e2 = new Employee(1002,"Jerry");
Employee[] arr1 = new Employee[]{e1,e2};
Stream<Employee> stream1 = Arrays.stream(arr1);
}
//创建 Stream方式三:通过Stream的of()
@Test
public void test3(){
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6);
}
//创建 Stream方式四:创建无限流
@Test
public void test4(){
// 迭代
// public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
//遍历前10个偶数
Stream.iterate(0, t -> t + 2).limit(10).forEach(System.out::println);
// 生成
// public static<T> Stream<T> generate(Supplier<T> s)
Stream.generate(Math::random).limit(10).forEach(System.out::println);
}
- 步骤二:中间操作
7. 步骤三:终止操作
Mysql
SSM
Linux
SpringBoot
SpringCloud
RabbitMQ
Docker
JUC
JVM
本文章部分引用jing<>,哪吒等博主部分博客