JAVA基础,语法
1.JAVA跨平台原理(字节码,虚拟机)
java源程序(扩展名.java)---->java字节码文件(扩展名.class)---->java虚拟机(JVM)—>执行代码
字节码文件不面向任何具体平台,只面向虚拟机
java虚拟机是可运行java字节码文件的虚拟计算机,不同平台的虚拟机是不同的,但他们都提供了相同的接口
2、Java的安全性
Java取消了指针
垃圾回收机制:不需要程序员直接控制内存回收,有垃圾收集器在后台自动回收不在使用的内存。避免程序忘记及时回收,导致内存泄露。避免程序错误回收程序核心类库的内存导致系统崩溃。
异常处理机制:Java异常机制主要依赖于try,catch,finally,throws,trow
在四种安全性保障机制:
字节码校验器,类装载器,运行时内存布局,文件访问限制
3、Java三大版本
Java2平台包括、
标准版(J2SE)数据库连接、接口定义、输入/输出、网络编程
、企业版(J2EE)servlet、JSP、XML、事务控制
微缩版(J2ME)呼机、智能卡、手机、PDA、机顶盒
4、JRE,JDK,JVM
JVM:
JVM是Java Virtual Machine(Java虚拟机)的缩写,它是整个java实现跨平台的最核心的部分,所有的java程序会首先被编译为.class的类文件,这种类文件可以在虚拟机上执行,也就是说class并不直接与机器的操作系统相对应,而是经过虚拟机间接与操作系统交互,由虚拟机将程序解释给本地系统执行。
JRE:
JRE是java runtime environment(java运行环境)的缩写。光有JVM还不能让class文件执行,因为在解释class的时候JVM需要调用解释所需要的类库lib。
在JDK的安装目录里你可以找到jre目录,里面有两个文件夹bin和lib,在这里可以认为bin里的就是jvm,lib中则是jvm工作所需要的类库,而jvm和lib和起来就称为jre。
JDK:
JDK是java development kit(java开发工具包)的缩写。
JDK包含JRE,而JRE包含JVM。
bin: 最主要的是编译器(javac.exe)
include: java和JVM交互用的头文件
**lib:**类库
jre: java运行环境
JDK,JRE,JVM三者关系概括如下:
jdk是JAVA程序开发时用的开发工具包,其内部也有JRE运行环境JRE。JRE是JAVA程序运行时需要的运行环境,就是说如果你光是运行JAVA程序而不是去搞开发的话,只安装JRE就能运行已经存在的JAVA程序了。JDk、JRE内部都包含JAVA虚拟机JVM,JAVA虚拟机内部包含许多应用程序的类的解释器和类加载器等等
5、8种基本数据类型及其字节数
小数默认是双精度浮点型即double类型的。
数据类型 | 关键字 | 字节数 | 取值范围 | |
---|---|---|---|---|
数值型 | 整数型 | byte | 1 | -2^7 ~ 2^7-1,即-128 ~ 127 |
short | 2 | -2^15 ~ 2^15-1,即-32768 ~ 32767 | ||
int | 4 | -2^31 ~ 2^31-1,即-2147483648 ~ 2147483647 | ||
long | 8 | -2^63 ~ 2^63-1 | ||
浮点型 | float | 4 | ||
double | 8 | |||
布尔型 | boolean | 1 | ||
字符型 | char | 2 |
6、&与&&的区别和联系,位运算符
&逻辑运算符称为逻辑与运算符,&&逻辑运算符称为短路与运算符,也可叫逻辑与运算符。
对于&:无论任何情况,&两边的操作数或表达式都会参与计算。
对于&&:当&&左边的操作数为false或左边表达式结果为false时,&&右边的操作数或表达式将不参与计算,此时最终结果都为false。
&还可以用作位运算符。当&两边操作数或两边表达式的结果不是boolean类型时,&用于按位与运算符的操作。
|和||的区别和联系与&和&&的区别和联系类似
位运算符
2乘以8—>2<<3
<<是将一个数左移n位,相当于乘以2的n次方
7、基本数据类型的类型转换规则
基本数据类型有自动类型转换和强制类型转换
自动类型转换
容量小的数据类型可以自动转换成容量大的数据类型,也可以说是低级自动向高级转换
强制类型转换
高级变成低级
(1)、赋值运算符“=”右边的转换,先自动转换成表达式中级别最高的数据类型,再进行运算。
(2)、赋值运算符“=”两侧的转换,若左边级别>右边级别,会自动转换;若左边级别 == 右边级别,不用转换;若左边级别 < 右边级别,需强制转换。
(3)、可以将整型常量直接赋值给byte, short, char等类型变量,而不需要进行强制类型转换,前提是不超出其表述范围,否则必须进行强制转换
8、循环判断
switch为等值判断(不允许比如>= <=),而if为等值和区间都可以,if的使用范围大
while先判断后执行,第一次判断为false,循环体一次都不执行
do while先执行 后判断,最少执行1次。
如果while循环第一次判断为true, 则两种循环没有区别
break: 结束当前循环并退出当前循环体。
break还可以退出switch语句
continue: 循环体中后续的语句不执行,但是循环没有结束,继续进行循环条件的判断(for循环还会i++)。continue只是结束本次循环
9.数组的特征
数组是(相同类型数据)的(有序)(集合)
数组会在内存中开辟一块连续的空间,每个空间相当于之前的一个变量,称为数组的元素element
索引从0开始
数组的长度是固定的,一经定义,不能再发生变化(数组的扩容)
冒泡排序
相邻元素两两比较,大的往后放,第一次遍历比较完毕,最大值出现在了最大索引处
public class Main {
public static void main(String[] args) {
int[] a = {1,5,4,6,3};
System.out.println(Arrays.toString(a));
sort(a);
}
public static void sort(int[] a){
int temp = 0;
for(int i = 0; i < a.length-1; ++i){
//通过符号为可以减少无畏的比较,如果已经有序,就跳出循环
int flag = 0;
int b = 1;
for(int j = 0; j < a.length-1-i; ++j){
if(a[j+1] < a[j]){
System.out.println(a[j] + "移动了:" + b++);
temp = a[j];
a[j] = a[j + 1];
a[j + 1] = temp;
flag = 1;
}
}
if(flag == 0){
break;
}
System.out.println(Arrays.toString(a));
}
}
}
选择排序
从0索引开始,依次和后面元素比较,大的往前放,第一次遍历比较完毕,最大值出现了最小索引处
public static void main(String[] args) {
int[] a = {1,5,4,6,3};
System.out.println(Arrays.toString(a));
sort1(a);
}
//选择排序
public static void sort1(int[] arr) {
int temp = 0;
for (int i = 0; i < arr.length - 1; i++) {
// 认为目前的数就是最小的, 记录最小数的下标
int minIndex = i;
for (int j = i + 1; j < arr.length; j++) {
if (arr[minIndex] > arr[j]) {
// 修改最小值的下标
minIndex = j;
}
}
// 当退出for就找到这次的最小值
if (i != minIndex) {
temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
System.out.println(Arrays.toString(arr));
}
}
插入排序
public static void main(String[] args) {
int[] a = {1,5,4,6,3};
System.out.println(Arrays.toString(a));
sort1(a);
}
//插入排序
public static void sort1(int arr[]) {
int i, j;
for (i = 1; i < arr.length; i++) {
int temp = arr[i];
for (j = i; j > 0 && temp < arr[j - 1]; j--) {
arr[j] = arr[j - 1];
}
arr[j] = temp;
System.out.println(Arrays.toString(arr));
}
}
10、类和对象的关系
类是对象的抽象,而对象是类的具体实例。类是抽象的,不占用内存的,而对象是具体的,占用存储空间。类是用于创建对象的蓝图,它是一个定义包括在特定类型的对象中的方法和变量的软件模板。
11、面向过程和面向对象,方法重载和方法重写
编程思路不同: 面向过程以实现功能的函数开发为主,而面向对象要首先抽象出类、属性及其方法,然后通过实例化类、执行方法来完成功能。
**封装性:**都具有封装性,但是面向过程是封装的是功能,而面向对象封装的是数据和功能。
面向对象具有继承性和多态性,而面向过程没有继承性和多态性,所以面向对象优势是明显。
方法重载和方法重写
英文 | 位置不同 | 作用不同 | |
---|---|---|---|
重载 | overload | 同一个类中 | 在一个类里面为一种行为提供多种实现方式,并提高可读性 |
重写 | override | 子类和父类间 | 父类方法无法满足子类的要求,子类通过方法重写满足要求 |
修饰符 | 返回值 | 方法名 | 参数 | 抛出异常 | |
---|---|---|---|---|---|
重载 | 无关 | 无关 | 相同 | 不同 | 无关 |
重写 | 大于等于 | 小于等于 | 相同 | 相同 | 小于等于 |
12、this和super关键字的作用–static关键字的作用
this是对象内部指代自身的引用,同时也是解决成员变量和局部变量同名问题;this可以调用成员变量,不能调用局部变量;this也可以调用成员方法,但是在普通方法中可以省略this,在构造方法中不允许省略,必须是构造方法的第一条语句。,而且在静态方法当中不允许出现this关键字。
super代表对当前对象的直接父类对象的引用,super可以调用直接父类的成员方法(注意权限修饰符的影响,比如不能访问private成员);super可以调用直接父类的构造方法,只限构造方法中使用,且必须是第一条语句。
static可以修饰变量、方法、代码块和内部类
static属性属于这个类所有,即由该类创建的所有对象共享同一个static属性。可以对象创建后通过对象名.属性名和类名.属性名两种方式来访问。也可以在没有创建任何对象之前通过类名.属性名的方式来访问。
.static变量和非static变量的区别(都是成员变量,不是局部变量)
内存中份数不同
static变量只有1份,对于每个对象,实例变量都会有单独的一份
static变量是属于整个类的,也称为类变量,而非静态变量是属于对象的,也称为实例变量
内存中存放位置不同
静态变量存在方法去中,实例变量存在堆内存中
访问方式不同
实例变量:对象名.变量名
静态变量:对象名.变量名,类名.变量名
在内存中分配空间的时间不同
实例变量:创建对象的时候才分配空间,静态变量:第一次使用类的时候
static方法也可以通过对象名.方法名和类名.方法名两种方式来访问
static代码块。当类被第一次使用时(可能是调用static属性和方法,或者创建其对象)执行静态代码块,且只被执行一次,主要作用是实现static属性的初始化。
**static内部类:**属于整个外部类,而不是属于外部类的每个对象。不能访问外部类的非静态成员(变量或者方法),.可以访问外部类的静态成员
13、final和abstract关键字的作用
abstract:可以用来修饰类和方法,不能用来修饰属性和构造方法,使用abstract修饰的类是抽象类,需要被继承,使用abstract修饰的方法是抽象方法,需要子类被重写
final可以用来修饰类,方法和属性,不能修饰构造方法。使用final修饰的类不能被继承,使用final修饰的方法不能被重写,使用final修饰的变量的值不能被修改,所以就成了常量。
特别注意:final修饰基本类型变量,其值不能改变,由原来的变量变为常量;但是final修饰引用类型变量,栈内存中的引用不能改变,但是所指向的堆内存中的对象的属性值仍旧可以改变
14、Object类的六个常用方法
equals,hashCode,toString,getClass,finalize,clone,wait,notify,notifyAll
15、访问权限修饰符
类的访问权限修饰符只有两种:public,default
成员访问权限共四种:public,protected,default,private
16、==和equals的区别和联系
==是关系运算符,equals()是方法,同时他们的结果都返回布尔值
==使用情况如下:
基本类型:比较的是值
引用类型:比较的是地址
不能比较没有父子关系的两个对象
equals方法使用如下:
系统类一般已经覆盖了equals(),比较的是内容
用户自定义类如果没有覆盖equals(),将调用父类的equals(比如是Object),而 Object的equals的比较是地址(return (this == obj);)
用户自定义类需要覆盖父类的equals()
Object的和equals比较的都是地址,作用相同
两个对象相等,hashCode一定相等,两个对象有相同的hashCode,则不一定相等
17、JAVA多态
实现多态的三个条件:前提条件,向上转型,向下转型
- 继承的存在—继承是多态的基础,没有继承就没有多态
- 子类重写父类的方法—多态下会掉用子类重写后的方法
- 父类引用变量指向子类对象
向上转型
向上转型 父类引用指向子类对象
如果调用的是静态方法,静态变量,成员变量,调用的是父类里面的。如果调用的是成员方法,调用的是子类自己的
子类对象转为父类,父类可以是接口,公式:Father f = new Son();,Father是父类或接口,son是子类。
**向下转型 **
向下转型 父类引用转为子类对象,前提是父类对象指向的是子类对象,它已经向上转型
在继承关系下,创建子类对象,先执行父类的构造方法,在执行子类的构造方法
18、基本数据类型和包装类
Byte,Boolean,Short,Character,Integer,Long,Float,Double
int的默认值为0,Integer的默认值为null
Java程序经编译后会产生byte code,编译出来是字节码
19、Java接口的修饰符
接口中的访问权限修饰符只可以是public或default
接口中的所有的方法必须要实现类实现,所以不能使用final
接口中所有方法默认都是abstract的,所以接口可以使用abstract修饰,但通常abstract可以省略不写
20、什么是Java的序列化,如何实现Java的序列化?列举在哪些程序中见过Java序列化?
Java中的序列化机制能够将一个实例对象(只序列化对象的属性值,而不会去序列化什么所谓的方法。)的状态信息写入到一个字节流中使其可以通过socket进行传输、或者持久化到存储数据库或文件系统中;然后在需要的时候通过字节流中的信息来重构一个相同的对象。一般而言,要使得一个类可以序列化,只需简单实现java.io.Serializable接口即可。
对象的序列化主要有两种用途:
1) 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;
2) 在网络上传送对象的字节序列。
发送方需要把这个Java对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为Java对象。
21、不通过构造函数也能创建对象
用new语句创建对象,这是最常见的创建对象的方法
运用反射手段,调用java.lang.Class或者java.lang.reflect.Constructor类的newInstance实例方法
调用对象的clone()方法
运用反序列化手段,调用java.io.ObjectInputStream对象的 readObject()方法
22、匿名内部类
他是没有名字的内部类,不能继承其他类,但一个内部类可以作为一个接口,有另一个内部类实现
- 由于匿名内部类没有名字,所以它没有构造函数。因为没有构造函数,所以它必须完全借用父类的构造函数来实例化
- 因为匿名内部类没有名字,所以无法进行向下的强制类型转换,持有对一个匿名内部类对象引用的变量类型一定是它的直接或间接父类类型
杂七杂八
-
标识符的命名规范,可以包含字母,数字,下划线,$,不能以数字开头,不能是Java关键字
-
内存溢出:out of memory 是指程序在申请内存时,没有足够的内存空间供其使用
-
内存泄漏:memory leak,是指程序在申请内存后,无法释放已申请的内存空间
Java中怎么实现多态
实现多态有三个前提条件:
继承的存在
子类重写父类的方法
父类引用变量指向子类对象
最后使用父类的引用变量调用子类重写的方法即可实现多态。
面向对象的三大特征
1、抽象:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。抽象只关注对象有哪些属性和行为,并不关注这些行为的细节是什么
2、继承:继承是从已有类得到继承信息创建新类的过程。提供继承信息的类被称为父类(超类、基类);得到继承信息的类被称为子类(派生类)。继承让变化中的软件系统有了一定的延续性,同时继承也是封装程序中可变因素的重要手段
3、封装:通常认为封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口。面向对象的本质就是将现实世界描绘成一系列完全自治、封闭的对象。我们在类中编写的方法就是对实现细节的一种封装;我们编写一个类就是对数据和数据操作的封装。
4、多态性:多态性是指允许不同子类型的对象对同一消息作出不同的响应。简单的说就是用同样的对象引用调用同样的方法但是做了不同的事情。多态性分为编译时的多态性和运行时的多态性。
一个Java源文件中可以包含多个类,但最多只能有一个公开类,而且文件名必须和公开类的类名完全保持一致
创建对象时构造器的调用顺序是:先初始化静态成员,然后调用父类构造器,再初始化非静态成员,最后调用自身构造器
23、HashMap的键值只能是引用类型,要用包装类
map.put(1, “Java”);”,但实际上是将其中的key值1进行了自动装箱操作,变为了Integer类型
24、接口和抽象类的区别
能修饰接口的只有public,abstract,default
抽象类和接口均包含抽象方法,类必须实现所有的抽象方法,否则是抽象类
抽象类和接口都不能实例化,它们位于继承树的顶端,用来被其他类继承和实现
区别
接口中只能定义全局静态常量,不能定义变量,抽象类中可以定义常量和变量
接口中所有方法都是全局抽象方法,抽象类则不固定
抽象类可以有构造方法,但不能用来实例化,而子类实例化是执行,完成属于抽象类的初始化操作,接口中不能定义构造方法
一个类只能有一个直接父类(可以是抽象类)但是可以实现多个接口,一个类使用extends来继承抽象类,implements来实现接口
25、同步代码块和同步方法有什么区别
同步方法就是在方法前加关键字synchronized,然后被同步的方法一次只能有一个线程进去,其他线程等待,而同步代码块则是在方法内部使用大括号使得一个代码块得到同步。同步代码块会有一个同步的“目标”,使得同步块更加灵活一些(同步代码块可以通过“目标”决定需要锁定的对象)
同步方法直接在方法上加synchronized实现加锁,同步代码块则在方法内部加锁。很明显,同步方法锁的范围比较大,而同步代码块范围要小点。一般同步的范围越大,性能就越差。所以一般需要加锁进行同步的时候,范围越小越好,这样性能更好
26、静态内部类和内部类有什么区别
静态内部类不需要有指向外部类的引用,但非静态内部类需要持有对外部类的引用
静态内部类可以有静态成员(方法,属性),而非静态内部类则不能有静态成员(方法,属性)。
非静态内部类能够访问外部类的静态和非静态成员。静态内部类不能访问外部类的非静态成员,只能访问外部类的静态成员。
反射
加载完类之后, 在堆内存中会产生一个Class类型的对象(一个类只有一个Class对象), 这个对象包含了完整的类的结构信息,而且这个Class对象就像一面镜子,透过这个镜子看到类的结构,所以被称之为:反射
通过反射可以使程序代码访问装载到JVM 中的类的内部信息
1) 获取已装载类的属性信息
2) 获取已装载类的方法
3) 获取已装载类的构造方法信息
getDeclaredFields(): 可以获取所有本类自己声明的方法, 不能获取继承的方法
getFields(): 只能获取所有public声明的方法, 包括继承的方法
1)Class类:代表一个类
2)Field 类:代表类的成员变量(属性)
3)Method类:代表类的成员方法
4)Constructor 类:代表类的构造方法
5)Array类:提供了动态创建数组,以及访问数组的元素的静态方法
27、JAVA参数传递
按值传递:call by value:传递的是具体的值,如基础数据类型,不能改变实参的数值
按引用传递:call by reference:传递的是对象的引用,即对象的存储地址,虽然不能改变实参的参考地址,但是可以通过该地址访问地址中的内容从而实现内容的改变
28、正则表达式含义
\d: 匹配一个数字字符。等价于[0-9]
\D: 匹配一个非数字字符。等价于[^0-9]
\s: 匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]
. :匹配除换行符 \n 之外的任何单字符。要匹配 . ,请使用 \. 。
*:匹配前面的子表达式零次或多次。要匹配 * 字符,请使用 \*。
+:匹配前面的子表达式一次或多次。要匹配 + 字符,请使用 \+。
|:将两个匹配条件进行逻辑“或”(Or)运算
[0-9]{6}:匹配连续6个0-9之间的数字
\d+:匹配至少一个0-9之间的数字
29、静态变量,成员变量
成员变量 | 静态变量 | |
---|---|---|
生命周期 | 随着对象的创建而存在,随着对象的回收而释放 | 随着类的加载而存在,对这类的消失而消失 |
调用方式 | 成员变量只能被对象调用 | 静态变量可以被对象调用,还可以被类名调用 |
别名不同 | 实例变量 | 类变量 |
数据存储位置不同 | 在堆内存的对象中 | 存储在方法区的静态区 |
String相关知识
String str1=``"hello"``;
String str2=``new` `String(``"hello"``);
System.out.println(str1==str2);---------->结果为false
str1没有使用new关键字,在堆中没有开辟空间,其值”hello”在常量池中,str2使用new关键字创建了一个对象,在堆中开辟了空间,”==”比较的是对象的引用,即内存地址,所以str1与str2两个对象的内存地址是不相同的
String是不可以被继承的
因为String类有final修饰符,而final修饰的类是不能被继承的,实现细节不允许改变。
创建几个String对象
String s=new String(“abc”)
两个或一个,”abc”对应一个对象,这个对象放在字符串常量缓冲区,常量”abc”不管出现多少遍,都是缓冲区中的那一个。New String每写一遍,就创建一个新的对象,它一句那个常量”abc”对象的内容来创建出一个新String对象。如果以前就用过’abc’,这句代表就不会创建”abc”自己了,直接从缓冲区拿
String,StringBuilder,StringBuffer
Java提供了两种类型的字符串:String,StringBuilder/StringBuffer
- 相同点
它们都可以存储和操作字符串,同时三者都使用final修饰,都属于终结类不能派生子类
- 不同点
其中String是只读字符串,也就意味着String引用的字符串的内容是不能被改变的,而StringBuffer和StringBuilder类表示的字符串对象可以直接进行修改,在修改的同时地址值不会发生改变。StringBuilder是在单线程环境下使用的,因为他的所有方面都没有被synchronized修饰,因此它的效率也比StringBuffer略高
String、StringBuffer、StringBuilder三者类型不一样,无法使用equals()方法比较其字符串内容是否一样!
集合
1、JAVA集合体系结构
- Collection接口存储一组不唯一,无序的对象
- List接口存储一组不唯一,有序(插入顺序)的对象
- Set接口存储一组唯一,无序的对象
- Map接口存储一组键值对象,提供Key到value的映射,Key无序,唯一。value不要求有序,允许重复如果只使用key存储,而不使用value,那就是Set
2、Vector和ArrayList的区别和联系
相同点
- 实现原理相同—底层都使用数组
- 功能相同—实现增删改查等操作
- 都是长度可变的数组结构
不同点
- Vector是早期JDK版本提供,ArrayList代替了Vector
- Vector线程安全,ArrayList重速度轻安全,线程非安全,长度需增长时,Vector默认增长一倍,ArrayList增长50%
ArrayList 底层原理
ArrayList 底层采用数组实现,访问特别快,它可以根据索引下标快速找到元素。但添加插入删除等写操作效率低,因为涉及到内存数据复制转移。
ArrayList 对象初始化时,无参数构造器默认容量为 10,当空间不足时会扩容,扩容后的容量是老容量的 1.5 倍
3、ArrayList和ListedList的区别和联系
两者都实现了List接口,都具有List中元素有序、不唯一的特点。
不同点:
ArrayList实现了长度可变的数组,在内存中分配连续空间。遍历元素和随机访问元素的效率比较高
LinkedList采用链表存储方式, 是采用双向链表结构。插入、删除元素时效率比较高
4、HashMap和Hashtable
相同点
- 实现原理相同,功能相同,底层都是哈希表结构,查询速度快
不同点
- Hashtable是早期提供的接口,HashMap是新版JDK提供的接口
- Hashtable继承Dictionary类,HashMap实现Map接口
- Hashtable线程安全,HashMap线程不安全
- Hashtable不允许有null值,HashMap允许null值
HashMap底层原理
HashMap底层主要针对key的算法,取key的哈希值模于存储空间大小,该模数就是HashMap空间的顺序位置,里面存放了Key值和Vaule的地址。如果两个不同的 Key,通过哈希函数得出的实际存储地址相同,那就冲突了。为了存储冲突的多个地址,Java 使用了链表,但链表遍历效率较低,Java8 改进为红黑树算法。当冲突越严重,性能则越低,空间扩容后可以提高性能。HashMap 对象初始化时,默认的空间是 16 个,并且设了一个扩充因子,默认值是0.75,也就是当哈希表中的条目超过了容量和加载因子的乘积的时候(默认16*0.75=12时),就会扩容,并重新进行哈希操作。虽然扩容后性能可以提升,但扩充过程中会重新哈希,因此扩充过程会消耗时间
5、HashSet使用原理
- 哈希表的查询速度特别快,时间复杂度为O(1)
- HashMap、Hashtable、HashSet这些集合采用的是哈希表结构,需要用到hashCode哈希码,hashCode是一个整数值
- 向哈希表中添加数据的原理:当向集合Set中增加对象时,首先集合计算要增加对象的hashCode码,根据该值来得到一个位置用来存放当前对象,如在该位置没有一个对象存在的话,那么集合Set认为该对象在集合中不存在,直接增加进去。如果在该位置有一个对象存在的话,接着将准备增加到集合中的对象与该位置上的对象进行equals方法比较,如果该equals方法返回false,那么集合认为集合中不存在该对象,在进行一次散列,将该对象放到散列后计算出的新地址里。如果equals方法返回true,那么集合认为集合中已经存在该对象了,不会再将该对象增加到集合中了。
- 在哈希表中判断两个元素是否重复要使用到hashCode()和equals()。hashCode决定数据在表中的存储位置,而equals判断是否存在相同数据
6、TreeSet的原理和而使用
- TreeSet集合,元素不允许重复且有序(自然顺序)
- TreeSet采用树结构存储数据,存入元素时需要和树中元素进行对比,需要指定比较策略。
- 可以通过Comparable(外部比较器)和Comparator(内部比较器)来指定比较策略,实现了Comparable的系统类可以顺利存入TreeSet。自定义类可以实现Comparable接口来指定比较策略。
- 可创建Comparator接口实现类来指定比较策略,并通过TreeSet构造方法参数传入。这种方式尤其对系统类非常适用
7、JAVA中集合类层次结构
Java中集合主要分为两种:Collection和Map
Collection是List和Set接口的父接口;ArrayList和LinkedList是List的实现类
HashSet和TreeSet是Set的实现类
LinkedHashSet是HashSet的子类
HashMap和ThreeMap是Map的实现类
LinkedHashMap是HashMao的子类
8、Map实现类中:有序与无序
-
Map的实现类有HashMap,LinkedHashMap,TreeMap
-
HashMap是有无序的,LinkedHashMap和TreeMap都是有序的(LinkedHashMap记录了添加数据的顺序;TreeMap默认是自然升序)
-
LinkedHashMap底层存储结构是哈希表+链表,链表记录了添加数据的顺序
-
TreeMap底层存储结构是二叉树,二叉树的中序遍历保证了数据的有序性
-
LinkedHashMap有序性能比较高,因为底层数据存储结构采用的哈希表
多线程
1、run与start—乐观锁,悲观锁
启动线程需要调用start方法,如果在方法中调用run就相当于一个方法
继承Thread和实现Runable接口
- 悲观锁:传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁
- 乐观锁:乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁
1) start方法:用start方法来启动线程,真正实现了多线程运行,这时无需等待run方法体代码执行完毕而直接继续执行下面的代码。通过调用Thread类的start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到cpu时间片,就开始执行run()方法,这里方法run()称为线程体,它包含了要执行的这个线程的内容,Run方法运行结束,此线程随即终止。
2) run():run()方法只是类的一个普通方法而已,如果直接调用run方法,程序中依然只有主线程这一个线程,其程序执行路径还是只有一条,还是要顺序执行,还是要等待run方法体执行完毕后才可继续执行下面的代码,这样就没有达到写线程的目的
调用start方法方可启动线程,而run方法只是thread的一个普通方法调用,还是在主线程里执行。这两个方法应该都比较熟悉,把需要并行处理的代码放在run()方法中,start()方法启动线程将自动调用 run()方法,这是由jvm的内存机制规定的。并且run()方法必须是public访问权限,返回值类型为void。
实际中往往采用实现Runable接口,一方面因为java只支持单继承,继承了Thread类就无法再继续继承其它类,而且Runable接口只有一个run方法;另一方面通过结果可以看出实现Runable接口才是真正的多线程。
2、Java中怎么实现多线程
当多个线程访问同一个数据时,容易出现线程安全问题,需要某种方式来确保资源在某一时刻只被一个线程使用。需要让线程同步,保证数据安全线程同步的实现方案:同步代码块和同步方法,均需要使用synchronized关键字
**同步代码块:**一般用这个
public void makeWithdrawal(int amt) {
synchronized (acct) { }
}
同步方法:
public synchronized void makeWithdrawal(int amt) { }
3、使用Thread和Runable
-
继承Thread类
-
实现Runnable接口
-
方式一:继承Java.lang.Thread类,并覆盖run方法
public class ThreadDemo1 {
public static void main(String args[]) {
MyThread1 t = new MyThread1();
t.start();
while (true) {
System.out.println("兔子领先了,别骄傲");
}
}
}
class MyThread1 extends Thread {
public void run() {
while (true) {
System.out.println("乌龟领先了,加油");
}
}
}
- 方式二:实现Java.lang.Runnable接口,并实现run()方法。优势:可继承其它类,多线程可共享同一个Thread对象;劣势:编程方式稍微复杂,如需访问当前线程,需调用Thread.currentThread()方法
public class ThreadDemo2 {
public static void main(String args[]) {
MyThread2 mt = new MyThread2();
Thread t = new Thread(mt);
t.start();
while (true) {
System.out.println("兔子领先了,加油");
}
}
}
class MyThread2 implements Runnable {
public void run() {
while (true) {
System.out.println("乌龟超过了,再接再厉");
}
}
}
4、在多线程编程里,wait方法的调用方式是怎样的
wait方法是线程通信的方式之一,必须用在synchronized方法或者synchronized代码块中,否则会抛出异常,而wait方法必须使用上锁的对象来调用,从而持有该对象的锁进入线程等待状态,直到使用该上锁对象调用notify或者nootifyAll方法唤醒之前进入等待的线程,以释放持有的锁。
5、Java线程的几种状态
新建 | 当创建Thread类的一个实例时,此线程进入新建状态 |
---|---|
就绪 | 线程已经被启动,正等待被分配给CPU时间片,此时线程正在就绪队列中排队等候得到CPU资源 |
运行 | 线程获得CPU资源正在执行任务,此时除非此线程自动放弃CPU资源或者有优先级更高的线程进入,线程将一直运行到结束 |
死亡 | 当线程执行完毕后或被其他线程杀死,线程就进入死亡状态,这是线程不可能再进入就绪状态等待执行,自然终止:正常运行run方法后终止,异常终止:调用stop方法让一个线程终止运行 |
堵塞 | 由于某种原因导致正在运行的线程让出CPU并暂停自己的执行,即进入堵塞状态,正在休眠:用sleep方法可是线程进入睡眠方式,一个睡眠着的线程在指定的时间过去可进入就绪状态。正在等待:调用wait方法,被另一个线程所阻塞:Suspend |
yield:会让线程进入就绪状态
6、volatile关键字
一旦一个共享变量(类成员变量,类静态成员变量)被volatile修饰后,那么就具有两层含义:
- 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的
- 禁止进行指令重排序(编译原理)
他是不能保证线程安全的
7、sleep和wait以及sleep与yield区别
sleep | wait |
---|---|
线程类Thread的方法 | Object类的方法 |
不释放对象锁 | 放弃对象锁 |
暂停线程,但监控状态仍然保持,结束后自动恢复 | 进入等待锁定池,只有针对此对象发出notify方法(或notifyAll)后本线程才进入对象锁定池,准备获得对象锁进行运行状态 |
sleep | yield |
---|---|
给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行机会 | 只会给相同优先级或更高优先级的线程以运行机会 |
执行sleep方法后转入阻塞(blocked)状态 | 执行yield方法后转入就绪状态 |
声明抛出InterruptedException | yield()方法没有声明任何异常 |
比yield具有更好的移植性 |
8、进程与线程
进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。
线程是进程的一个实体,是CPU调度和分派的基本单位,他是比进程更小的能独立里运行的基本单位,线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源
区别 | 进程 | 线程 |
---|---|---|
根本区别 | 作为资源分配的单位 | 调度和执行单位 |
开销 | 每个进程都有独立的代码和数据空间,进程间切换会有较大的开销 | 线程可以看成轻量级的进程,同一类进程共享代码和数据空间,每个线程有独立的运行栈和程序计数器,线程切换的开销小 |
所处环境 | 在操作系统中能同时运行多个任务 | 在同一应用程序中有多个顺序流同时执行 |
分配内存 | 系统在运行的时候会为每个进程分配不同的内存区域 | 除了CPU之外,不会为线程分配内存,线程组只能共享资源 |
包含关系 | 没有线程的进程是可以被看作单线程的,如果一个进程内拥有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的。 | 线程是进程的一部分,所以线程有的时候被称为是轻权进程或者轻量级进程。 |
- wait():是一个线程处于等待状态,并释放所持有的对象的锁
- sleep:使一个正常运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉异常
- notify:唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且与优先级无关
- notityAll():唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,而是让它们竞争
9、线程池
线程池接口是ExecutorService
建议使用newFixedThreadPool
- newFixedThreadPool:创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
池化技术:事先准备好一些资源,有人要用就来我这里拿,用完之后还给我
线程池的好处:1.降低资源消耗2.提高响应速度3.方便管理
线程复用,控制线程并发数,管理线程
程序的运行,本质:占用系统的资源,优化资源的使用
线程池:三大方法,七大参数,四种拒绝策略
//Executors 工具类 3大方法
//使用了线程池之后用线程池来创建线程
public class Demo01 {
public static void main(String[] args) {
//ExecutorService threadPool = Executors.newSingleThreadExecutor();//单个线程
//ExecutorService threadPool = Executors.newFixedThreadPool(5);//创建一个固定的线程池的大小
ExecutorService threadPool = Executors.newCachedThreadPool();//可以伸缩的
try {
for (int i = 0; i < 10; i++) {
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+"ok");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//程序结束,关闭线程池
threadPool.shutdown();
}
}
}
IO流
以InputStream(输入流)/OutputStream(输出流)为后缀的是字节流;以Reader(输入流)/Writer(输出流)为后缀的是字符流
Java两种类型的流
字节流继承于InputStream,OutputStream,字符流继承于Reader,Writer
输入流是得到数据,输出流是输出数据,而节点流,处理流是流的另一种划分,按照功能不同进行的划分。节点流,可以从或向一个特定的地方(节点)读写数据。处理流是对一个已存在的流的连接和封装,通过所封装的流的功能调用实现数据读写。如BufferedReader。处理流的构造方法总是要带一个其他的流对象做参数。一个流对象经过其他流的多次包装,称为流的链接。
常用字节输入流和输出流
- FileInputStream:从文件系统中的某个文件中获取输入字节
- FileOutputStream:从程序中的数据,写入到指定文件
- ObjectInputStream: 对以前使用 ObjectOutputStream 写入的基本数据和对象进行反序列化。
- ByteArrayInputStream 包含一个内部缓冲区,该缓冲区包含从流中读取的字节。内部计数器跟踪 read 方法要提供的下一个字节。
- StringBufferInputStream此类允许应用程序创建输入流,在该流中读取的字节由字符串内容提供
- ByteArrayOutputStream此类实现了一个输出流,其中的数据被写入一个 byte 数组。缓冲区会随着数据的不断写入而自动增长。可使用 toByteArray() 和 toString() 获取数据。
网络编程
TCP协议和UDP协议的比较
TCP和UDP是TCP/IP协议栈中传输层的两个协议,他们使用IP路由功能把数据包发送到目的地,从而为应用程序及应用层协议提供网络服务
TCP的server和client之间通信就好比两个人打电话,需要互相知道对方的电话号码,然后开始对话。所以在两者的连接过程中间需要指定端口和地址
UDP的server和client之间的通信就像两个人互相发信。我只需要知道对方的地址,然后就发信过去。对方是否收到我不知道,也不需要专门对口令似的来建立连接
1)TCP是面向连接的传输。UDP是无连接的传输
2)TCP有流量控制、拥塞控制,检验数据数据按序到达,而UDP则相反。
3)TCP的路由选择只发生在建立连接的时候,而UDP的每个报文都要进行路由选择
4)TCP是可靠性传输,他的可靠性是由超时重发机制实现的,而UDP则是不可靠传输
5)UDP因为少了很多控制信息,所以传输速度比TCP速度快
6)TCP适合用于传输大量数据,UDP适合用于传输小量数据
Socket编程
套接字:socket,用于描述IP地址和端口
在应用层和传输层之间,则使用套接字来进行分离,它实际上就是传输层供给应用层的编程接口,传输层则在网路层的基础上提供进程到进程间的逻辑通道
基于TCP协议的Socket编程的主要步骤
服务器端(server):
构建一个ServerSocket实例,指定本地的端口。这个socket就是用来监听指定端口的连接请求的。
2.重复如下几个步骤:
- a. 调用socket的accept()方法来获得下面客户端的连接请求。通过accept()方法返回的socket实例,建立了一个和客户端的新连接。
- b.通过这个返回的socket实例获取InputStream和OutputStream,可以通过这两个stream来分别读和写数据。
- c.结束的时候调用socket实例的close()方法关闭socket连接。
客户端(client):
1.构建Socket实例,通过指定的远程服务器地址和端口来建立连接。
2.通过Socket实例包含的InputStream和OutputStream来进行数据的读写。
3.操作结束后调用socket实例的close方法,关闭。
UDP
服务器端(server):
1. 构造DatagramSocket实例,指定本地端口。
2. 通过DatagramSocket实例的receive方法接收DatagramPacket.DatagramPacket中间就包含了通信的内容。
3. 通过DatagramSocket的send和receive方法来收和发DatagramPacket.
客户端(client):
1. 构造DatagramSocket实例。
2.通过DatagramSocket实例的send和receive方法发送DatagramPacket报文。
3.结束后,调用DatagramSocket的close方法关闭。
JVM
Java类加载器都有哪些,每个类加载器都有加载哪些类,什么是双亲委派模型,是做什么的?
类加载器按照层次,从顶层到底层,分为三种:
-
启动类加载器(Bootstrap ClassLoader)
这个类加载器负责将存放在JAVA_HOME/lib下的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的类库加载到虚拟机内存中。启动类加载器无法被Java程序直接引用。
-
扩展类加载器(Extension ClassLoader)
这个加载器负责加载JAVA_HOME/lib/ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器
-
应用程序类加载器(Application ClassLoader)
这个加载器是ClassLoader中getSystemClassLoader()方法的返回值,所以一般也称它为系统类加载器。它负责加载用户类路径(Classpath)上所指定的类库,可直接使用这个加载器,如果应用程序没有自定义自己的类加载器,一般情况下这个就是程序中默认的类加载器
双亲委派模型
类加载器收到类加载的请求,将这个请求向上委托给父类加载器去完成,一直向上委托,直到启动类加载器,启动类加载器检查是否能加在当前这个类,能加载就结束,就是用当前的加载器,否则抛出异常,通知子加载器进行加载—>重复上一步
工作过程:
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传递到顶层的启动类加载器中,只有当父类加载器反馈自己无法完成这个请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载
垃圾回收器GC基本原理,它可以马上回收内存吗?如何通知虚拟机进行垃圾回收?
1、对于GC来说,当程序员创建对象时,GC就开始监控这个对象的地址、大小以及使用情况。通常,GC采用有向图的方式记录和管理堆(heap)中的所有对象。通过这种方式确定哪些对象是"可达的",哪些对象是"不可达的"。当GC确定一些对象为"不可达”时,GC就有责任回收这些内存空间。
2、可以。程序员可以手动执行System.gc(),通知GC运行,但是Java语言规范并不保证GC一定会执行。
3、System.gc();或者Runtime.getRuntime().gc();
GC
GC是垃圾收集的意思,内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java提供的GC功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的,Java语言没有提供释放已分配内存的显示操作方法。Java程序员不用担心内存管理,因为垃圾收集器会自动进行管理。要请求垃圾收集,可以调用下面的方法之一:System.gc() 或Runtime.getRuntime().gc() ,但JVM可以屏蔽掉显示的垃圾回收调用
垃圾回收可以有效的防止内存泄露,有效的使用可以使用的内存。垃圾回收器通常是作为一个单独的低优先级的线程运行,不可预知的情况下对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收,程序员不能实时的调用垃圾回收器对某个对象或所有对象进行垃圾回收
与垃圾回收相关的JVM参数:
· -Xms / -Xmx — 堆的初始大小 / 堆的最大大小
· -Xmn — 堆中年轻代的大小
· -XX:-DisableExplicitGC — 让System.gc()不产生任何作用
· -XX:+PrintGCDetail — 打印GC的细节
-XX:+PrintGCDateStamps — 打印GC操作的时间戳
1、JVM的体系结构![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/18544d6c7e1da08f86a3e8c1b0608e1b.png)
JVM调优是调优堆和方法区
2、堆
堆:Heap,一个JVM只有一个堆内存,堆内存的大小是可以调节的,类加载器读取了类文件后,一般会把什么东西放入堆中?类,方法,常量,变量,保存所有引用类型的真实对象;
堆内存中还要区分三个区域:新生区(伊甸园去),养老区,永久区
GC垃圾回收,主要是在新生区和养老区
新生区
类:诞生,成长的地方,甚至死亡;
伊甸园区:所有的对象都在此new出来的
幸存者区(0区,1区)
经过研究,99%的对象都是临时对象,
永久区:(这个区域常驻内存在,用于存放JDK自身携带的Class对象,Interface元数据,存储的是java运行时的一些环境或类信息,这个区域不存在垃圾回收,关闭虚拟机就会释放这个区域的内存
一个启动类,加载了大量的第三方jar包,Tomcat部署了太多的应用,大量动态生成的反射类,这些东西不断的被加载,直到内存满,就会出现OOM)
jdk1.8:无永久代,常量池在元空间、
1.JVM在进行GC时,并不是对这三个区域统一回收,大部分的时候,回收都是新生代
新生代,幸存区(from,to),老年区
2.GC两种类:轻GC(普通GC),重GC(全局GC)
堆里面的分区有哪些?Eden,from,to,老年区
3.GC常用算法:标记清除法,标记整理,复制算法,引用计数器
普通GC和重GC分别在什么时候发生?
4.引用计数法:并不高效
复制算法:当一个对象经历了15次GC,都没有死,就会去老年区
标记清除算法:
缺点:两次扫描严重浪费时间,会产生内存碎片
优点:不需要额外的空间
标记压缩算法:再优化,防止内存碎片产生,再次扫描向一端移动存活的对象,多了一个移动成本
总结:
内存效率:复制算法>标记清除算法>标记压缩算法(时间复杂度)
内存整齐度:复制算法=标记压缩算法>标记清除算法
内存利用率:标记压缩算法=标记清除算法>复制算法
GC:分代收集算法
年轻代:存活率低-复制算法
老年代:存活率高,区域大-标记清除(内存碎片不是太多)+标记压缩混合算法实现
Linux
linux指令
arch 显示机器的处理器架构(1)
uname -m 显示机器的处理器架构(2)
shutdown -h now 关闭系统(1)
shutdown -r now 重启(1)
cd /home 进入 ‘/ home’ 目录’
cd … 返回上一级目录
cd …/… 返回上两级目录
mkdir dir1 创建一个叫做 ‘dir1’ 的目录’
mkdir dir1 dir2 同时创建两个目录
find / -name file1 从 ‘/’ 开始进入根文件系统搜索文件和目录
find / -user user1 搜索属于用户 ‘user1’ 的文件和目录
linuxtomcat启动
进入tomcat下的bin目录执行 ./catalina.sh start直接启动即可,然后使用tail -f /usr/local/tomcat6/logs/catalina.out查看tomcat启动日志