1、垃圾回收器的基本原理是什么?垃圾回收器可以马上回收内存吗?有什么办法主动通知虚拟机进行垃圾回
对于GC来说,当程序员创建对象时,GC就开始监控这个对象的地址、大小以及使用情况。通常,GC采用有向图的方式记录和管理堆(heap)中的所有对象。通过这种方式确定哪些对象是"可达的",哪些对象是"不可达的"。当GC确定一些对象为"不可达"时,GC就有责任回收这些内存空间。
(1. 不可用不可达------>这种情况GC会帮我们回收掉,而C++不会
2. 不可用可达 ------>这种情况会存在内存泄露
3. 可用可达 ------>正常使用
不可用不可达就是我们的变量作用域结束了,不可用不可达)
可以。
程序员可以手动执行System.gc(),通知GC运行,但是Java语言规范并不保证GC一定会执行。
2、数据库查询两张表,一张学生表,一张班级表,查询每个班级男女性别:
(一张学生表和一张班级表查询这个某个班的男生数sql该怎么写?)
select count(*) from 学生表 where class_id in (select class_id from 班级表 where grade=3 and class_no=5) and sex='男'
3、重载和重写区别
方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。
重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为重载;
重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的参数列表,有兼容的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。
重载对返回类型没有特殊的要求,不能根据返回类型进行区分。
4、八大排序算法
(1)快速排序
package demo2; import java.util.Arrays; public class QuickSort { public static void main(String[] args) { int[] arr=new int[] {9,4,6,7,2,7,2,8,0,10}; quickSort(arr, 0, arr.length-1); System.out.println(Arrays.toString(arr)); } public static void quickSort(int[] arr,int start,int end){ if(start //把数组中第0个数字作为标准数 int stard=arr[start]; //记录需要排序的下标 int low=start; int high=end; //循环找比标准数大的数和比标准数小的数 while(low //右边数字比标准数大 while(low high--; } //使用右边的数字替换左边的数字 arr[low]=arr[high]; // 如果左边的数字比标准数小 while(low low++; } arr[high]=arr[low]; } //把标准数据赋值给所在的位置的元素 arr[low]=stard; //处理所有小的数字 quickSort(arr, start, low); //处理所有大的数字 quickSort(arr, low+1, end); } } } |
(2)冒泡排序
package demo2; import java.util.Arrays; public class BubbleSort { public static void main(String[] args) { int[] arr=new int[] {5,7,2,9,4,1,0,5,7}; System.out.println("排序前的数组为:"+Arrays.toString(arr)); bubbleSort(arr); System.out.println("排序后的数组为:"+Arrays.toString(arr)); } public static void bubbleSort(int[] arr){ //控制共比较多少轮 for(int i=0;i //控制比较的次数 for(int j=0;j if(arr[j]>arr[j+1]){ int temp=arr[j]; arr[j]=arr[j+1]; arr[j+1]=temp; } } } } } |
(3)选择排序
package demo2;
import java.util.Arrays;
public class SelectSort {
public static void main(String[] args) {
int[] arr=new int[] {2,5,7,4,9,1,5,0}; selectSort(arr); System.out.println(Arrays.toString(arr)); } /** * 选择排序 * @param arr */ public static void selectSort(int[] arr){ //遍历所有数 for(int i=0;i int minIndex=i; //把当前遍历的数和后面所有的数依次进行比较,并记录最小的数 for(int j=i+1;j //如果后面的数比最小的数小 if(arr[minIndex]>arr[j]){ minIndex=j; } } //如果最小的数和当前遍历的数下标不一致,说明下标为minIndex的数比当前遍历的数更小 if(i!=minIndex){ int temp=arr[i]; arr[i]=arr[minIndex]; arr[minIndex]=temp; } } } } |
(4)希尔排序
package demo2; import java.util.Arrays; public class ShellSort { public static void main(String[] args) { int[] arr=new int[]{3,5,8,3,0,2,4,6,7}; System.out.println(Arrays.toString(arr)); shellSort(arr); System.out.println(Arrays.toString(arr)); } /** * 希尔排序 * @param arr */ public static void shellSort(int[] arr){ int k=1; //遍历所有步长 for(int d=arr.length/2;d>0;d/=2){ //遍历所有元素 for(int i=d;i //遍历本组中的所有元素 for(int j=i-d;j>=0;j-=d){ //如果当前元素大于加上步长后的那个元素 if(arr[j]>arr[j+d]){ int temp=arr[j]; arr[j]=arr[j+1]; arr[j+1]=temp; } } } System.out.println("第"+k+"次排序结果:"+Arrays.toString(arr)); k++; } } } |
(5)堆排序
package demo2; import java.util.Arrays; /** * Created by chengxiao on 2016/12/17. * 堆排序demo */ public class HeapSort { public static void main(String []args){ int []arr = {9,8,7,6,5,4,3,2,1}; sort(arr); System.out.println(Arrays.toString(arr)); } public static void sort(int []arr){ //1.构建大顶堆 for(int i=arr.length/2-1;i>=0;i--){ //从第一个非叶子结点从下至上,从右至左调整结构 adjustHeap(arr,i,arr.length); } //2.调整堆结构+交换堆顶元素与末尾元素 for(int j=arr.length-1;j>0;j--){ swap(arr,0,j);//将堆顶元素与末尾元素进行交换 adjustHeap(arr,0,j);//重新对堆进行调整 }
} /** * 调整大顶堆(仅是调整过程,建立在大顶堆已构建的基础上) * @param arr * @param i * @param length */ public static void adjustHeap(int []arr,int i,int length){ int temp = arr[i];//先取出当前元素i for(int k=i*2+1;k if(k+1 k++; } if(arr[k] >temp){//如果子节点大于父节点,将子节点值赋给父节点(不用进行交换) arr[i] = arr[k]; i = k; }else{ break; } } arr[i] = temp;//将temp值放到最终的位置 } /** * 交换元素 * @param arr * @param a * @param b */ public static void swap(int []arr,int a ,int b){ int temp=arr[a]; arr[a] = arr[b]; arr[b] = temp; } } |
(6)插入排序
package demo2; import java.util.Arrays; public class InserSort { public static void main(String[] args) { int[] arr=new int[] {3,5,7,1,9,0,2,4}; insertSort(arr); System.out.println(Arrays.toString(arr)); } public static void insertSort(int[] arr){ //遍历所有的数字 for(int i=1;i //如果当前数字比前一个数字小 if(arr[i] //把当前遍历的数字存起来 int temp=arr[i]; int j; //遍历当前数字前面所有的数字 for(j=i-1;j>=0&&temp //把前一个数字赋给后一个数字 arr[j+1]=arr[j]; } //把临时变量赋给(外层for循环的当前元素)不满足条件的最后一个元素 arr[j+1]=temp; } } } } |
(7)归并排序
package demo2; import java.util.Arrays; public class MergeSort { public static void main(String[] args) { int [] arr=new int[]{2,4,6,3,8,1,9,0,3}; System.out.println(Arrays.toString(arr)); mergeSort(arr, 0, arr.length-1); System.out.println(Arrays.toString(arr)); } //归并排序 public static void mergeSort(int[]arr,int low,int high){ int middle=(high+low)/2; if(low //处理左边数据 mergeSort(arr, low, middle); //处理右边数据 mergeSort(arr, middle+1, high); //归并 merge(arr, low, middle, high); } } public static void merge(int[]arr,int low,int middle,int high){ //用于存储归并后的临时数组 int[] temp=new int[high-low+1]; //记录第一个数组中需要遍历的下标 int i=low; //记录第二个数组中需要遍历的下标 int j=middle+1; //用于存储临时数组中存放的下标 int index=0; //遍历两个数组取出小的数字,放入临时数组中 while(i<=middle&&j<=high){ if(arr[i]<=arr[j]){ //把小的数放入临时数组 temp[index]=arr[i]; //让下标向下移一位 i++; }else{ temp[index]=arr[j]; j++; } index++; } //处理多余数据 while(j<=high){ temp[index]=arr[j]; j++; index++; } while(i<=middle){ temp[index]=arr[i]; i++; index++; } //把临时数组中的数据重新存入原数组 for(int k=0;k arr[k+low]=temp[k]; } } } |
(8)基数排序
package demo2; import java.util.Arrays; public class RadixSort { public static void main(String[] args) { int[] arr=new int[]{23,6,189,45,9,287,56,7,1,798,54,67,345}; radixSort(arr); System.out.println(Arrays.toString(arr)); } public static void radixSort(int[] arr){ //存数组中最大的数字 int max=Integer.MAX_VALUE; for(int i=0;i if(arr[i]>max){ max=arr[i]; } } //计算最大数组是几位数 int maxLength=(max+"").length(); //用于临时存储数据的数组 int[][] temp=new int[10][arr.length]; //用于记录在temp中相应的数组中存放的数字的数量 int [] counts=new int[10]; //根据最大长度的数决定比较的次数 for(int i=0,n=1;i //把每一个数字分别计算余数 for(int j=0;j //计算余数 int ys=arr[j]/n%10; //把当前遍历的数据放入指定数组中 temp[ys][counts[ys]]=arr[j]; //记录数量 counts[ys]++; } //记录取的元素需要放的位置 int index=0; //把数字取出来 for(int k=0;k //记录数量的数组中当前余数记录的数量不为0 if(counts[k]!=0){ for(int l=0;l //取出元素 arr[index]=temp[k][l]; //记录下一个位置 index++; } //把数量置位0 counts[k]=0; } } } } } |
5、单例模式
(1)饿汉模式
package com.study.DesignPattern01;/** * 创建一个饿汉模式的单例 * @author ZLHome *有些对象,我们只需要一个,如果多了,那么就可能导致数据不一致, 占用资源过多等等,比如: 配置文件、工具类、线程池、缓存、日志对象 */public class Singleton { //1、构造方法私有化(这样类就不能被实例化了) private Singleton(){ System.out.println("实例化Singleton类"); } //2、实例化单例对象,对象为静态的(这样就只会实例化一次),私有的(安全,外部不能直接Singleton.instance调用) private static Singleton instance = new Singleton(); //3、提供一个静态方法(静态方法,类可以直接调用),用于获取单例 public static Singleton getInstance(){ return instance; }} |
(2)懒汉模式
package com.study.DesignPattern01;
public class Singleton1 { //1、将构造方法设置为私有(这样类就不能被外部实例化了) private Singleton1(){ System.out.println("实例化Singleton1"); }
//2、申明单例对象 private static Singleton1 instance;
//3、提供一个静态方法(静态方法属于类),用于外部调用 public static Singleton1 getInstance(){ if(instance==null){ System.out.println("第一次实例化Singleton1对象"); instance=new Singleton1(); } return instance; } } |
加锁
public class Singleton{ private static Singleton singleton; private Singleton(){ } public static Singleton getInstance(){ if(singleton == null){ synchronized(Singleton.class){ if(singleton == null){ singleton = new Singleton(); } } }
return singleton; } } |
6、String、StringBuffer和StringBuilder区别
(1)长度是否可变
String 是被 final 修饰的,他的长度是不可变的,就算调用 String 的concat 方法,那也是把字符串拼接起来并重新创建一个对象,把拼接后的 String 的值赋给新创建的对象
StringBuffer 和 StringBuilder 类的对象能够被多次的修改,并且不产生新的未使用对象,StringBuffer 与 StringBuilder 中的方法和功能完全是等价的。调用StringBuffer 的 append 方法,来改变 StringBuffer 的长度,并且,相比较于 StringBuffer,String 一旦发生长度变化,是非常耗费内存的!
(2)执行效率
(3)应用场景
如果要操作少量的数据用 = String
单线程操作字符串缓冲区 下操作大量数据 = StringBuilder
多线程操作字符串缓冲区 下操作大量数据 = StringBuffer
7、StringBuffer和StringBuilder区别
(1)是否线程安全
StringBuilder 类在 Java 5 中被提出,它和 StringBuffer 之间的最大不同在于 StringBuilder 的方法不是线程安全的(不能同步访问),StringBuffer是线程安全的。只是StringBuffer 中的方法大都采用了 synchronized 关键字进行修饰,因此是线程安全的,而 StringBuilder 没有这个修饰,可以被认为是线程不安全的。
(2)应用场景
由于 StringBuilder 相较于 StringBuffer 有速度优势,所以多数情况下建议使用 StringBuilder 类。
然而在应用程序要求线程安全的情况下,则必须使用 StringBuffer 类。 append方法与直接使用+串联相比,减少常量池的浪费。
8、数据库事务特性
· 原子性(Atomicity)
事务的原子性是指事务是一个不可分割的工作单位,这组操作要么全部发生,否则全部不发生。
· 一致性(Consistency)
在事务开始以前,被操作的数据的完整性处于一致性的状态,事务结束后,被操作的数据的完整性也必须处于一致性状态。拿银行转账来说,一致性要求事务的执行不应改变A、B 两个账户的金额总和。如果没有这种一致性要求,转账过程中就会发生钱无中生有,或者不翼而飞的现象。事务应该把数据库从一个一致性状态转换到另外一个一致性状态。
· 隔离性(Isolation)
事务隔离性要求系统必须保证事务不受其他并发执行的事务的影响,也即要达到这样一种效果:对于任何一对事务T1 和 T2,在事务 T1 看来,T2 要么在 T1 开始之前已经结束,要么在 T1 完成之后才开始执行。这样,每个事务都感觉不到系统中有其他事务在并发地执行。
· 持久性(Durability)
一个事务一旦成功提交,它对数据库的改变必须是永久的,即便是数据库发生故障也应该不回对其产生任何影响。
9、数据库三大范式
第一范式(1NF)
定义:如果关系模式R的每个关系r的属性都是不可分的数据项,那么就称R是第一范式的模式。
简单的说,每一个属性都是原子项,不可分割。
1NF是关系模式应具备的最起码的条件,如果数据库设计不能满足第一范式,就不称为关系型数据库。关系数据库设计研究的关系规范化是在1NF之上进行的。
例如(学生信息表):
学生编号 姓名 性别 联系方式
20080901 张三 男 email:[email protected],phone:88886666
20080902 李四 女 email:[email protected],phone:66668888
以上的表就不符合,第一范式:联系方式字段可以再分,所以变更为正确的是:
学生编号 姓名 性别 电子邮件 电话
20080901 张三 男 [email protected] 88886666
20080902 李四 女 [email protected] 66668888
第二范式(2NF)
定义:如果关系模式R是1NF,且每个非主属性完全函数依赖于候选键,那么就称R是第二范式。
简单的说,第二范式要满足以下的条件:首先要满足第一范式,其次每个非主属性要完全函数依赖与候选键,或者是主键。也就是说,每个非主属性是由整个主键函数决定的,而不能由主键的一部分来决定。
例如(学生选课表):
学生 课程 教师 教师职称 教材 教室 上课时间
李四 Spring 张老师 java讲师 《Spring深入浅出》 301 08:00
张三 Struts 杨老师 java讲师 《Struts in Action》 302 13:30
这里通过(学生,课程)可以确定教师、教师职称,教材,教室和上课时间,所以可以把(学生,课程)作为主键。但是,教材并不完全依赖于(学生,课程),只拿出课程就可以确定教材,因为一个课程,一定指定了某个教材。这就叫不完全依赖,或者部分依赖。出现这种情况,就不满足第二范式。
修改后,选课表:
学生 课程 教师 教师职称 教室 上课时间
李四 Spring 张老师 java讲师 301 08:00
张三 Struts 杨老师 java讲师 302 13:30
课程表:
课程 教材
Spring 《Spring深入浅出》
Struts 《Struts in Action》
所以,第二范式可以说是消除部分依赖。第二范式可以减少插入异常,删除异常和修改异常。
第三范式(3NF)
定义:如果关系模式R是2NF,且关系模式R(U,F)中的所有非主属性对任何候选关键字都不存在传递依赖,则称关系R是属于第三范式。
简单的说,第三范式要满足以下的条件:首先要满足第二范式,其次非主属性之间不存在函数依赖。由于满足了第二范式,表示每个非主属性都函数依赖于主键。如果非主属性之间存在了函数依赖,就会存在传递依赖,这样就不满足第三范式。
上例中修改后的选课表中,一个教师能确定一个教师职称。这样,教师依赖于(学生,课程),而教师职称又依赖于教师,这叫传递依赖。第三范式就是要消除传递依赖。
修改后,选课表:
学生 课程 教师 教室 上课时间
李四 Spring 张老师 301 08:00
张三 Struts 杨老师 302 13:30
教师表:
教师 教师职称
张老师 java讲师
杨老师 java讲师
这样,新教师的职称在没被选课的时候也有地方存了,没人选这个教师的课的时候教师的职称也不至于被删除,修改教师职称时只修改教师表就可以了。
简单的说,
第一范式就 是原子性,字段不可再分割;
第二范式就是完全依赖,没有部分依赖;
第三范式就是没有传递依赖。
10、redis介绍
(1)怎么理解Redis事务
事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。
11、实现多线程
1、继承Thread类创建线程
Thread类本质上是实现了Runnable接口的一个实例,代表一个线程的实例。启动线程的唯一方法就是通过Thread类的start()实例方法。start()方法是一个native方法,它将启动一个新线程,并执行run()方法。这种方式实现多线程很简单,通过自己的类直接extend Thread,并复写run()方法,就可以启动新线程并执行自己定义的run()方法。
2、实现Runnable接口创建线程
如果自己的类已经extends另一个类,就无法直接extends Thread,此时,可以实现一个Runnable接口
3、实现Callable接口通过FutureTask包装器来创建Thread线程
4、使用ExecutorService、Callable、Future实现有返回结果的线程
ExecutorService、Callable、Future三个接口实际上都是属于Executor框架。返回结果的线程是在JDK1.5中引入的新特征,有了这种特征就不需要再为了得到返回值而大费周折了。而且自己实现了也可能漏洞百出。
可返回值的任务必须实现Callable接口。类似的,无返回值的任务必须实现Runnable接口。
执行Callable任务后,可以获取一个Future的对象,在该对象上调用get就可以获取到Callable任务返回的Object了。
注意:get方法是阻塞的,即:线程无返回结果,get方法会一直等待。
再结合线程池接口ExecutorService就可以实现传说中有返回结果的多线程了
12、JAVA多线程中start方法与run方法区别
1.start()方法来启动线程,真正实现了多线程运行。这时无需等待run方法体代码执行完毕,可以直接继续执行下面的代码;通过调用Thread类的start()方法来启动一个线程, 这时此线程是处于就绪状态, 并没有运行。 然后通过此Thread类调用方法run()来完成其运行操作的, 这里方法run()称为线程体,它包含了要执行的这个线程的内容, Run方法运行结束, 此线程终止。然后CPU再调度其它线程。
2.run()方法当作普通方法的方式调用。程序还是要顺序执行,要等待run方法体执行完毕后,才可继续执行下面的代码; 程序中只有主线程——这一个线程, 其程序执行路径还是只有一条, 这样就没有达到写线程的目的。
13、swtich是否能作用在byte上,是否能作用在long上,是否能作用在String上?
switch(expr1)中,expr1是一个整数表达式。因此传递给 switch 和 case 语句的参数应该是 int、 short、 char 或者 byte。long,string 都不能作用于swtich。