1.java的跨平台性(java的字节码是跨平台的):java的跨平台不是java源程序的跨平台,java源程序先经过javac编译器编译成二进制的.class字节码文件(java的跨平台指的就是.class字节码文件的跨平台,.class字节码文件是与平台无关的),.class文件再运行在jvm上,java解释器(jvm的一部分)会将其解释成对应平台的机器码执行,所以java所谓的跨平台就是在不同平台上安装了不同的jvm,而在不同平台上生成的.class文件都是一样的,而.class文件再由对应平台的jvm解释成对应平台的机器码执行。
java源文件(*.java)经过java编译器编译成java字节码(*.class),执行java字节码,java字节码经过java解释器(jvm的一部分)解释为具体平台的具体指令,并执行。不同平台有不同的JVM,java字节码可以在主流平台上解释执行。
2.java虚拟机JVM:Java虚拟机是一个可以执行Java字节码的虚拟机进程。Java源文件被编译成能被Java虚拟机执行的字节码文件。
3.java中内存泄露的情况:
① 在内存对象已经不需要的时候,还仍然保留着这块内存和他的访问方式(引用)。
② 在堆中分配的内存,在没有将其释放掉的时候,就将所有能访问这块内存的凡是都删掉。垃圾回收机制已经解决。
第一种情况:
List list = new ArrayList(10);
for(int i=1;i<100;i++){
Object o = new Object();
list.add(o);
o=null;//释放了引用本身,但list仍然引用该对象,所以这个对象对于GC来说不可回收,所以,这个对象没有被释放。
}
4.计算二进制中1的个数:
/**
* 解法一 通过相除和判断余数的值的方法计算
*
* @param num
* 传入的数字
* @return 二进制数1的个数
*/
public static int CountOne(int num) {
int counter = 0;
while (num > 0) {
if (num % 2 == 1) {
counter++;
}
num = num / 2;
}
return counter;
}
/**
* 解法二 位操作
*
* @param num
* @return
*/
public static int Counttow(int num) {
int counter = 0;
while (num > 0) {
counter += num & 0X01;
num >>= 1;
}
return counter;
}
/**
* 解法三
*
* @param num
* @return
*/
public static int CountThree(int num) {
int counter = 0;
while (num > 0) {
num = num & (num - 1);
counter++;
}
return counter;
}
/**
* 解法二 位操作
*
* @param num
* @return
*/
public static int Counttow(int num) {
int counter = 0;
while (num > 0) {
counter += num & 0X01;
num >>= 1;
}
return counter;
}
/**
* 解法三
*
* @param num
* @return
*/
public static int CountThree(int num) {
int counter = 0;
while (num > 0) {
num = num & (num - 1);
counter++;
}
return counter;
}
5. 排序算法:插入排序---》直接插入排序;希尔排序
交换排序 ----》冒泡,快速排序
选择排序 ----》直接选择排序,堆排序
归并排序
6.Arrays.sort()方法用的什么排序:如果数组长度大于等于286且连续性好的话,就用归并排序,如果大于等于286且连续性不好的话就用双轴快速排序。如果长度小于286且大于等于47的话就用双轴快速排序,如果长度小于47的话就用插入排序。
7.Collections.sort()方法用的什么排序:① 如果
LegacyMergeSort.userRequested
为true的话就会使用归并排序,可以通过下面代码设置为true。
System.setProperty("java.util.Arrays.useLegacyMergeSort", "true");
② 如果不为true的话就会用一个叫TimSort
的排序算法---》Timsort是结合了归并排序和插入排序而得出的排序算法。
8.杨辉三角:
public class YangHuiSanJiao {
/**
* @param args
*/
public static void main(String[] args) {
int n = 6;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n-i; j++) {
System.out.print(" ");
}
for (int k = 1; k <=i; k++) {
System.out.print(num(i,k)+" ");
}
System.out.println();
}
}
private static int num(int x, int y) {
if (y==1||y==x) {
return 1;
}else {
return num(x-1, y-1)+num(x-1, y);
}
}
}
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1
9.快速排序:背吧,没别的办法
public class QuickSort {
/**
* @param args
*/
public static void main(String[] args) {
int[] num = {34,1,23,235,12,456,131,54,78};
sort(num);
for (int i = 0; i < num.length; i++) {
System.out.println(num[i]);
}
}
private static void sort(int[] num) {
quickSort(num,0,num.length-1);
}
private static void quickSort(int[] num, int left, int right) {
if (left<right) {
int s = num[left];
int i = left;
int j=right+1;
while (true) {
while (i+1<num.length && num[++i]<s) {
}
while (j-1>-1&&num[--j]>s) {
}
if(i>=j)
break;
else {
swap(num,i,j);
}
}
num[left] = num[j];
num[j] = s;
quickSort(num, left, j-1);
quickSort(num, j+1, right);
}
}
private static void swap(int[] num, int i, int j) {
int t;
t = num[i];
num[i]=num[j];
num[j]=t;
}
}
10.约瑟夫环问题:
/**
* @param args
* 约瑟夫环问题,每次出去什么数字,剩余什么数字
*/
public static void main(String[] args) {
int res = LastRemaining_Solution(10, 7);
System.out.println("剩余:"+res);
}
public static int LastRemaining_Solution(int n, int m) {
if(m==0||n==0)
return -1;
ArrayList<Integer> list = new ArrayList<Integer>();
for (int i = 1; i <= n; i++)
list.add(i);
int pos = 0;
while (list.size() > 1) {
pos = (pos + m-1) % list.size();
System.out.println(list.get(pos));
list.remove(pos);
}
return list.get(0);
}
11.a,b整形变量,不引入第3个变量的前提下,实现a=b,b=a
int a = 3,b = 4;
a = a + b;
b = a - b;
a = a - b;
System.out.println("a = " + a);
System.out.println("b = " + b);
12.查找方法:顺序查找,二分查找,分块查找,二叉排序树查找
13.在改写equals方法的时候总是要改写hashcode方法。
14.继承和重载的区别:
java的继承是子类对象继承父类对象的成员属性和成员方法。只允许单继承。在继承的过程中可以实现方法的重写(覆盖),即子类定义一个方法,覆盖从父类那里继承来的同名方法。每当子类对象调用该方法时都是子类自己定义的方法,只有使用super或者父类名为前缀时,才会调用父类原来的方法。方法覆盖时,子类应与父类有完全相同的方法名,返回值类型和参数列表,子类中的覆盖方法不能使用比父类中被覆盖的方法更严格的访问权限。
方法重载要求是有两个或多个方法名相同,只是方法参数列表不同的方法,他对返回值类型没有限定。
15.抽象类和接口的区别:
① 抽象类方法中可以有自己的数据成员,也可以有非抽象的成员方法,并赋予方法的默认行为,而在接口方式中,一般不定义成员数据变量,所有的方法都是抽象的,方法不能拥有默认的行为。
② 抽象类在java语言中表示的是一种继承关系,一个类只能使用一次继承关系,但是一个类却可以实现多个接口。
③ 抽象类在java语言中体现了一种继承关系,要想使得继承关系合理,父类和派生类之间必须存在“is a”关系,即父类和派生类在概念本质上应该是相同的。对于接口来说则不然,并不要求接口的实现者和接口的定义者在概念本质上是一样的,仅仅是实现了接口定义的契约而已。
16. java中的类分为:① 类: 使用class定义且不含有抽象方法的类。② 抽象类:使用abstract class定义的类,它可以含有,也可以不含有抽象方法。③ 接口:使用interface定义的类。
17. 多态:面向对象的三大基本特征之一(封装,继承,多态),多态的特性:重载和重写。
//汽车接口
interface Car {
// 汽车名称
String getName();
// 获得汽车售价
int getPrice();
}
// 宝马
class BMW implements Car {
public String getName() {
return "BMW";
}
public int getPrice() {
return 300000;
}
}
// 奇瑞QQ
class CheryQQ implements Car {
public String getName() {
return "CheryQQ";
}
public int getPrice() {
return 20000;
}
}
// 汽车出售店
public class CarShop {
// 售车收入
private int money = 0;
// 卖出一部车
public void sellCar(Car car) {
System.out.println("车型:" + car.getName() + " 单价:" + car.getPrice());
// 增加卖出车售价的收入
money += car.getPrice();
}
// 售车总收入
public int getMoney() {
return money;
}
public static void main(String[] args) {
CarShop aShop = new CarShop();
// 卖出一辆宝马
aShop.sellCar(new BMW());
// 卖出一辆奇瑞QQ
aShop.sellCar(new CheryQQ());
System.out.println("总收入:" + aShop.getMoney());
}
}
运行结果:
车型:BMW 单价:300000车型:CheryQQ 单价:20000总收入:320000
继承是多态得以实现的基础。从字面上理解,多态就是一种类型(都是Car类型)表现出多种状态(宝马汽车的名称是BMW,售价是300000;奇瑞汽车的名称是CheryQQ,售价是2000)。多态给我们带来的好处是消除了类之间的耦合关系,使程序更容易扩展。比如在上例中,新增加一种类型汽车的销售,只需要让新定义的类继承Car类并实现它的所有方法,而无需对原有代码做任何修改,CarShop类的sellCar(Car car)方法就可以处理新的车型了。
18. 重载:在同一个类中 方法具有相同的名字,相同或不同的返回值,但参数不同的多个方法(参数个数或参数类型)
19. 重写:子类重写父类的方法,在不同的类中 重写方法必须和被重写方法具有相同方法名称、参数列表和返回类型 重写方法不能使用比被重写方法更严格的访问权限。(public > protected >default > private)
20.static ① 修饰成员变量:无论该类创建了多少对象,一个类的static成员变量只有一份。
21.静态变量和实例变量区别:静态变量归全类共有,不依赖于某个对象,可通过类名直接访问。实例变量必须依存于某一实例,只能通过对象才能访问到它。
22. 堆 栈 方法区:
栈: 在函数中定义的一些基本类型的变量数据和对象的引用变量都在函数的栈内存中分配。当在一段代码块定义一个变量时,Java就在栈中为这个变量分配内存空间,当该变量退出该作用域后,Java会自动释放掉为该变量所分配的内存空间,该内存空间可以立即被另作他用。每个线程包含一个栈区,每个栈中的数据(原始类型和对象引用)都是私有的,其他栈不能访问。栈分为3个部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令)。
堆:堆内存用来存放new出来的对象和数组。 在堆中分配的内存,由Java虚拟机的自动垃圾回收器来管理。在堆中产生了一个数组或对象后,在栈中定义一个特殊的变量,让栈中这个变量的取值等于数组或对象在堆内存中的首地址,栈中的这个变量就成了数组或对象的引用变量。 引用变量就相当于是为数组或对象起的一个名称,以后就可以在程序中使用栈中的引用变量来访问堆中的数组或对象。引用变量就相当于是为数组或者对象起的一个名称。引用变量是普通的变量,定义时在栈中分配,引用变量在程序运行到其作用域之外后被释放。而数组和对象本身在堆中分配,即使程序运行到使用 new 产生数组或者对象的语句所在的代码块之外,数组和对象本身占据的内存不会被释放,数组和对象在没有引用变量指向它的时候,才变为垃圾,不能在被使用,但仍然占据内存空间不放,在随后的一个不确定的时间被垃圾回收器收走(释放掉)。这也是java比较占内存的原因。实际上,栈中的变量指向堆内存中的变量,这就是java中的指针!
Java的堆是一个运行时数据区,类的对象从中分配空间。这些对象通过new、newarray、anewarray和multianewarray等指令建立,它们不需要程序代码来显式的释放。堆是由垃圾回收来负责的,堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,因为它是在运行时动态分配内存的,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢。 jvm只有一个堆区(heap)被所有线程共享。
栈是一种线形集合,其添加和删除元素的操作应在同一段完成,栈按照后进先出的方式进行处理;堆地址是不连续的,可随机访问。栈的优势是,存取速度比堆要快,仅次于寄存器,栈数据可以共享。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。栈中主要存放一些基本类型的变量数据(int, short, long, byte, float, double, boolean, char)和对象引用。
方法区:方法区跟堆一样,被所有的线程共享。用于存储虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
在类加载的过程中,所有的非静态成员和静态成员会分别加载到JVM的方法区中,静态变量的赋值以及静态构造代码块会先执行,也就是说静态代码块是存储在方法区中的。在创建对象的时候,非静态成员会被加载堆内存中,并完成成员变量的赋值初始化。也就是说所有的非静态成员(包括成员变量、成员方法、构造方法、构造代码块、普通代码块)是保存在堆内存中的,但是方法调用的时候,调用的方法会在栈内存中执行,构造代码块也会在栈内存中执行。
23. String 类型转化成int类型:① 当我们需要的是一个基本类型* int *的时候我们需要使用Integer.parseInt()
函数
② 当我们需要的是一个Integer对象类的时候我们就是用Integer.valueOf()
函数
24. java中的异常:
Error类以及他的子类的实例,代表了JVM本身的错误,比如内存耗尽等。错误不能被程序员通过代码处理,Error很少出现。因此,程序员应该关注Exception为父类的分支下的各种异常类。
Exception以及他的子类,代表程序运行时发送的各种不期望发生的事件。可以被Java异常处理机制使用,是异常处理的核心。
① Error 和 RuntimeException及其子类,javac在编译时不会提示和发现这样的异常,这样的异常发生的原因多半是代码写的有问题。如除0错误ArithmeticException,错误的强制类型转换错误ClassCastException,数组索引越界ArrayIndexOutOfBoundsException,使用了空对象NullPointerException等等。Error 和 RuntimeException及其子类称为非检查异常。发生在运行阶段,编译器不会检查运行时异常。
② 除了Error和RuntimeException的其他异常,都称之为检查异常。javac强制要求程序员为这样的异常做预备处理工作(使用try…catch…finally或者throws)。在方法中要么用try-catch语句捕获它并处理,要么用throws子句声明抛出它,否则编译不会通过。如:SQLException , IOException,ClassNotFoundException等。检查异常发生在编译阶段,编译器会强制程序去捕获此类异常。
先捕获子类异常,再捕获父类异常。
25. 遍历map:
//遍历key
for (String key:map.keySet()) {
System.out.println(key);
}
//遍历value
for(Integer value:map.values()){
System.out.println(value);
}
//遍历key-value
for(String key:map.keySet()){
System.out.println("key:"+key+"---value:"+map.get(key));
}
//遍历key-value 性能优于上一种
for(Map.Entry<String, Integer> entry : map.entrySet()){
System.out.println(entry.getKey()+"----"+entry.getValue());
}
//遍历key-value Iterator
Iterator<Map.Entry<String, Integer>> it = map.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, Integer> entry = it.next();
System.out.println(entry.getKey()+"---"+entry.getValue());
it.remove()//删除元素
}
26. Set里的元素不能重复,用重写equals方法和hashcode()方法来判断两个对象是否是同一个对象,而“==”判断地址是否相等,用来决定引用值是否指向同一对象。
27.数组有length属性,String有length()方法。
28. java 里面任何class都要装载在虚拟机上才能运行。
29. 转换自己想要的日期:
Date date = new Date();
DateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String str = fmt.format(date);
System.out.println(str);
30. 端口号的范围0~65535 一些常用的服务和应用都有默认的端口号,一般不能轻易更改,比如web服务器的80端口,远程连接SSH服务的22端口,数据库MySql的3306端口等等。因为80端口是web服务器的默认端口,所以在写HTTP请求的URL时,80端口一般是省略的。
31. 普通索引:普通索引(由关键字KEY或INDEX定义的索引)的唯一任务是加快对数据的访问速度。因此,应该只为那些最经常出现在查询条件(WHEREcolumn=)或排序条件(ORDERBYcolumn)中的数据列创建索引。只要有可能,就应该选择一个数据最整齐、最紧凑的数据列(如一个整数类型的数据列)来创建索引。
32. 唯一索引:普通索引允许被索引的数据列包含重复的值。比如说,因为人有可能同名,所以同一个姓名在同一个“员工个人资料”数据表里可能出现两次或更多次。
如果能确定某个数据列将只包含彼此各不相同的值,在为这个数据列创建索引的时候就应该用关键字UNIQUE把它定义为一个唯一索引。这么做的好处:一是简化了MySQL对这个索引的管理工作,这个索引也因此而变得更有效率;二是MySQL会在有新记录插入数据表时,自动检查新记录的这个字段的值是否已经在某个记录的这个字段里出现过了;如果是,MySQL将拒绝插入那条新记录。也就是说,唯一索引可以保证数据记录的唯一性。事实上,在许多场合,人们创建唯一索引的目的往往不是为了提高访问速度,而只是为了避免数据出现重复。
33. ACID: 原子性 automicity 一个事务或者完全发生,或者完全不发生
一致性 consistency 事务把数据库从一个一致状态转变到另一个状态,转账前和转账后的总金额不变。
隔离性 isolation 在事务提交之前,其他事务察觉不到事务的影响,多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离。
持久性 durability 一旦事务提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。
34. 关于类加载机制的一道面试题:
能不能自己写个类叫java.lang.System
?
答案:通常不可以,但可以采取另类方法达到这个需求。
解释:为了不让我们写System类,类加载采用委托机制,这样可以保证爸爸们优先,爸爸们能找到的类,儿子就没有机会加载。而System类是Bootstrap加载器加载的,就算自己重写,也总是使用Java系统提供的System,自己写的System类根本没有机会得到加载。
但是,我们可以自己定义一个类加载器来达到这个目的,为了避免双亲委托机制,这个类加载器也必须是特殊的。由于系统自带的三个类加载器都加载特定目录下的类,如果我们自己的类加载器放在一个特殊的目录,那么系统的加载器就无法加载,也就是最终还是由我们自己的加载器加载。
35. 类的加载概述:
当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,连接,初始化三步来实现对这个类进行初始化。
加载:就是指将class文件读入内存,通过javac创建一个.class文件,这个.class文件会生成在硬盘上,加载到内存的意思就是把硬盘上的class文件加载到内存,变成一个Class对象。并为之创建一个Class对象。任何类被使用时系统都会建立一个Class对象。
连接:
验证 是否有正确的内部结构,并和其他类协调一致
准备 负责为类的静态成员分配内存,并设置默认初始化值
解析 将类的二进制数据中的符号引用替换为直接引用
初始化: 就是我们以前讲过的初始化步骤
36. 类加载器的概述:负责将.class文件加载到内存中,并为之生成对应的Class对象。
37. Collection和Collections区别:
Collection 是集合类的上级接口,子接口有Set List。
Collections 是针对集合类的一个帮助类,提供了操作集合的工具方法:一系列静态方法实现对各种集合的搜索,排序,线程安全化等操作。
38. Integer和Int类型数值进行比较的时候,Integer会自动拆箱为int类型再比较
39. 静态代码块在类加载的初始化阶段就会被调用。
40. JAVA初始化可以在不同块中完成,执行顺序: 父类静态变量 -- 父类静态代码块(谁在前,谁先初始化)-- 子类静态变量 -- 子类静态代码块(谁在前,谁先初始化) --父类非静态变量 -- 父类非静态代码块(谁在前,谁先初始化) -- 父类构造函数 -- 子类非静态变量 -- 子类非静态代码块(谁在前,谁先初始化)--子类构造函数。
JAVA程序的初始化一般3原则:1. 静态对象(变量)优先非静态对象(变量)初始化,其中,静态变量只初始化一次,非静态变量可以初始化多次。2:父类优先于子类初始化。3. 成员变量按定义顺序进行初始化,即使散布于方法定义内,他们依然在任何方法(包括构造函数)被调用前初始化。
41. finalize() 当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。
42. static有四种使用方式:修饰类(静态内部类),修饰成员变量(静态成员变量),修饰成员方法(静态成员方法),静态代码块。
43.类加载:
在第一次创建一个类的对象或者第一次调用一个类的静态属性和方法的时候,会发生类加载。类加载期间,如果发现有静态属性,就给对应的静态属性分配内存空间,并赋值这个过程完成之后,今后在调用该类的静态属性,虚拟机会直接寻找改属性先前已经分配的内存空间地址,然后调用其值。
同样,修改这个类的静态属性也一样,说白了,静态属性将永远占用某一块固定的内存空间,直到程序终止但是这里有点说不通,假如一个静态的字符串,运行过程中,不断修改这个字符串的值,那么其内存空间就不可能固定,所以可以认为这个静态字符串的引用是固定的。
44. java 和C/C++区别:
① 运行过程不同,java源程序经过编译器编译成字节码文件,然后由jvm解释执行。而C/C++经过编译、链接后生成可执行的二进制代码。因此C/C++执行速度比java快。
② 跨平台性。java可以跨平台,C/C++不可以跨平台。
③ java没有指针,C/C++有指针。
④ java不支持多重继承,但可以同时实现多个接口来达到类似的目的。C++支持多重继承。
⑤ java不需要对内存进行管理,有垃圾回收机制。C/C++需要对内存进行显示的管理。
⑥ java不支持运算符重载。C/C++支持运算符重载
⑦ java中每个数据类型在不同平台上所占字节数固定,而C/C++则不然。
45.Integer.valueOf(String)是将String转化为Integer对象;Integer.parseInt(String)是将String转化为int
1.System.out.println(127==127); //true , 值比较
2.System.out.println(128==128); //true , 值比较
3.System.out.println(new Integer(127) == new Integer(127)); //false, 对象比较
4.System.out.println(Integer.parseInt("128")==Integer.parseInt("128")); //true, 值比较
5.System.out.println(Integer.valueOf("127")==Integer.valueOf("127")); //true ,对象比较 默认在-128~127之间时返回缓存中的已有对象
6.System.out.println(Integer.valueOf("128")==Integer.valueOf("128")); //false ,对象比较, 不在缓存中
7.System.out.println(Integer.parseInt("128")==Integer.valueOf("128")); //true , 有一个是值,就是值比较
总结:
对于整型的比较,首先判断是值比较还是对象比较,值比较肯定返回true,有一个是值就是值比较。对象比较,则看对象是怎么构造出来的,如果是采用new Integer方式,则每次产生新对象,两个new出来的Integer比较肯定返回false,如果是Integer.valueOf方式的话,注意值的区间是否在-128~127之间,如果在,则构造的相同值的对象是同一个对象,==比较后返回true,否则返回false。
所以,对于值比较==放心没问题,对于Integer的比较最好用equals方法比较对象内容,当然注意先判断Integer是否不为null。
46. 线程的写法:
/**
* 奇数线程
* @author HP
*/
class SubThread1 extends Thread{
public void run(){
for (int i = 1; i <= 100; i+=2) {
System.out.println(Thread.currentThread().getName()+": 奇数:"+i);
}
}
}
/**
* 偶数线程
* @author HP
*/
class SubThread2 extends Thread{
public void run(){
for (int i = 0; i <= 100; i+=2) {
System.out.println(Thread.currentThread().getName()+": 偶数:"+i);
}
}
}
//匿名内部类:
new Thread(){
public void run(){
for (int i = 1; i <= 100; i+=2) {
System.out.println(Thread.currentThread().getName()+": 奇数:"+i);
}
}
}.start();
new Thread(){
public void run(){
for (int i = 0; i <= 100; i+=2) {
System.out.println(Thread.currentThread().getName()+": 偶数:"+i);
}
}
}.start();
//另一种线程实现接口的方式:
class SubThread1 implements Runnable{
public void run() {
for (int i = 1; i <= 100; i+=2) {
System.out.println(Thread.currentThread().getName()+": 奇数:"+i);
}
}
}
//主函数中要这样写:
public static void main(String[] args) {
SubThread1 st1 = new SubThread1();
SubThread2 st2 = new SubThread2();
Thread t1 = new Thread(st1);
Thread t2 = new Thread(st2);
//启动线程,执行Thread对象生成时构造器形参的对象的run()方法
t1.start();
t2.start();
}
显然实现接口的方式要由于继承的方式
47.线程安全问题:三个窗口同时卖票,共享100张票
//实现接口的方式的同步代码块的写法
主函数main方法:
//继承的方式实现线程安全问题
class Window2 extends Thread{
static int ticket = 100;//共享数据
static int ticket = 100;//共享数据
//充当锁对象 任何类的对象都可以充当同步锁,但必须保证这个锁是唯一的,这几个线程共用的是同一把锁,不能每个Window2对象都有一把锁
static Object obj = new Object();
public void run() {while (true) {synchronized (obj) {//this表示当前对象 此处就是w-->Window对象if (ticket >0) {System.out.println(Thread.currentThread().getName()+":"+ticket--);}else {break;}}}}}
public static void main(String[] args) {
Window2 w1 = new Window2();
Window2 w2 = new Window2();
Window2 w3 = new Window2();
w1.start();
w2.start();
w3.start();
}
//同步方法加锁:
class WindowTest implements Runnable {
int ticket = 100;// 共享数据
public void run() {
while (true) {
showTicket();
}
}
public synchronized void showTicket() {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + ":"
+ ticket--);
}
}
}
public static void main(String[] args) {
WindowTest w = new WindowTest();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.start();
t2.start();
t3.start();
}
48. 懒汉式单例模式是有安全问题的。
饿汉式没有安全问题
49. 线程之间的通信方式:
① Object类中的wait() \ notify() \ notifyAll()方法。② 用Condition接口。③ 管道实现线程间的通信。④ 使用volatile关键字
50. 线程池: 一个线程池中会缓存一定数量的线程,通过线程池就可以避免因为频繁创建和销毁线程所带来的系统开销。
51. 只有public,final,abstract可以修饰类,private,protected不能用来修饰类。
52.java创建对象的4种方式:① 通过new语句实例化一个对象;② 通过反射机制创建对象(反射机制的三种方法获取Class类);③ 通过clone()方法创建一个对象;④ 通过反序列化的方式创建对象。
53. 多态的表现形式:方法的重载和方法的覆盖 重载是编译时多态,覆盖是运行时多态 注意:只有类中的方法有多态的概念,类中的成员没有多态的概念。
54. java类加载机制详解:点击打开链接
55. 斐波那契递归循环问题,请看兼职offer的 斐波那契数列,跳台阶,变态跳台阶,矩形覆盖这四道题。
56.JVM的垃圾回收方式?
1.Serial New/Serial Old(新生代/老生代串行收集器)
Serial/Serial Old收集器是最基本最古老的收集器,它是一个单线程收集器,并且在它进行垃圾收集时,必须暂停所有用户线程。
Serial New收集器是针对新生代的收集器,采用的是Copying算法,Serial Old收集器是针对老年代的收集器,采用的是Mark-Compact算法。
它的优点是实现简单高效,但是缺点是会给用户带来停顿。
2.Parallel New(新生代并行收集器)
Parallel New收集器是Serial收集器的多线程版本(参照Serial New),使用多个线程进行垃圾收集,采用copying算法。
3.Parallel Scavenge(并行收集器)
Parallel Scavenge收集器是一个新生代的多线程收集器(并行收集器),它在回收期间不需要暂停其他用户线程,其采用的是Copying算法,
该收集器与前两个收集器有所不同,它主要是为了达到一个可控的吞吐量。
4.Parallel Old(老生代并行收集器)
Copying算法,Serial Old收集器是针对老年代的收集器,采用的是Mark-Compact算法。
它的优点是实现简单高效,但是缺点是会给用户带来停顿。
2.Parallel New(新生代并行收集器)
Parallel New收集器是Serial收集器的多线程版本(参照Serial New),使用多个线程进行垃圾收集,采用copying算法。
3.Parallel Scavenge(并行收集器)
Parallel Scavenge收集器是一个新生代的多线程收集器(并行收集器),它在回收期间不需要暂停其他用户线程,其采用的是Copying算法,
该收集器与前两个收集器有所不同,它主要是为了达到一个可控的吞吐量。
4.Parallel Old(老生代并行收集器)
Parallel Old是Parallel Scavenge收集器的老年代版本(并行收集器),使用多线程和Mark-Compact算法。
5.CMS收集器
CMS(Current Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,它是一种并发收集器,采用的是Mark-Sweep算法。
6.G1
G1收集器是当今收集器技术发展最前沿的成果,它是一款面向服务端应用的收集器,它能充分利用多CPU、多核环境。
因此它是一款并行与并发收集器,并且它能建立可预测的停顿时间模型。
57.单链表的原地逆置:
递归的思想:将链表分为当前表头节点和其余节点,递归的思想就是,先将当前的表头节点从链表中拆出来,然后对剩余的节点进行逆序,最后将当前的表头节点连接到新链表的尾部。 这里边的关键点是头节点head的下一个节点head->next将是逆序后的新链表的尾节点,也就是说,被摘除的头接点head需要被连接到head->next才能完成整个链表的逆序head.next.next=head。
import java.util.*;
public class Solution {
public ListNode ReverseList(ListNode head) {
ListNode newHead = null;
if(head==null ||head.next==null)
return head;
newHead = ReverseList(head.next);
head.next.next = head;
head.next = null;
return newHead;
}
}
import java.util.*;
public class Solution {
public ListNode ReverseList(ListNode head) {
if(head==null)
return null;
ListNode cur=null,tmp=null,pre=null;
cur = head;
while(cur!=null){
tmp = cur.next;
cur.next = pre;
pre = cur;
cur = tmp;
}
return pre;
}
}
58.给一个有序数组,数组中每个数字都出现两次,只有一个数字只出现了一次,请找出这个数字。
时间复杂度O(n),空间复杂度O(n)
public static void findNumsAppearOnce1(int[] arr) {
//Map对应的键值key就是数组中的元素,value就是这个元素出现的次数
Map<Integer, Integer> hashMap = new HashMap<Integer, Integer>();
for (int i : arr) {
if (hashMap.containsKey(i)) {
hashMap.put(i, hashMap.get(i) + 1); //如果存在,value值加1
} else {
hashMap.put(i, 1); //如果不存在,存入hashMap,value值等于1
}
}
//遍历一遍map,返回value为1对应的key就是我们需要求得元素
Set<Integer> set = hashMap.keySet();
for (int i : set) {
if (hashMap.get(i) == 1) {
System.out.print(i+" ");
break;
}
}
}
异或运算-----时间复杂度O(n)
public static void findNumAppearOnce(int[] arr) {
int result = 0;
for (int i = 0; i < arr.length; i++) {
result ^= arr[i];
}
System.out.println(result);
}
二分查找---时间复杂度O(nlogn)
public static int method(int[] arr) {
int start = 0; //定义变量,记录最小的索引
int end = arr.length - 1; //定义变量,记录最大的索引
int mid = (start + end) / 2; //定义变量,记录中间的索引
while ((arr[mid] == arr[mid - 1]) || (arr[mid] == arr[mid + 1])) {
if (arr[mid] == arr[mid + 1]) { //中间值与右边的值相等
if ((arr.length - mid) % 2 == 0) { //加上mid对应的数后,右边子数组的数为偶数
mid = 0 + (mid - 1) / 2;
} else {
mid = (mid + (arr.length - 1)) / 2;
}
} else { //中间值与左边的值相等
if ((mid + 1) % 2 == 0) { //加上mid对应的数后,左边子数组的数为偶数
mid = ((mid + 1) + (arr.length - 1)) / 2;
} else {
mid = (0 + mid) / 2;
}
}
//判断,到数组的两端了,也break
if (mid == 0 || mid == arr.length - 1) {
break;
}
}
return mid; //如果数组中有这个元素,则返回
}
//中间值与右边的值相等
if ((arr.length - mid) % 2 == 0) { //加上mid对应的数后,右边子数组的数为偶数
mid = 0 + (mid - 1) / 2;
} else {
mid = (mid + (arr.length - 1)) / 2;
}
} else { //中间值与左边的值相等
if ((mid + 1) % 2 == 0) { //加上mid对应的数后,左边子数组的数为偶数
mid = ((mid + 1) + (arr.length - 1)) / 2;
} else {
mid = (0 + mid) / 2;
}
}
//判断,到数组的两端了,也break
if (mid == 0 || mid == arr.length - 1) {
break;
}
}
return mid; //如果数组中有这个元素,则返回
}
59.单向链表只知道其中一个节点,现在要删除这个节点。ListNode p;
ListNode q = p.next;
if(q!=null){
//说明p不是尾结点--将p后面的结点q复制到p中
p.data = q.data;
p.next = q.next;
}else{
p=null;
}
60. get post区别:
①get方式提交数据会带来安全问题,比如一个登录界面,通过get方式提交数据时,请求数据会出现在url上。②get方式传输的数据量非常小,一般是2kb左右,但是执行效率比post方法好。post方式传递数据量较大。
61.将数组中元素排列为奇数在前偶数在后:主要思想为:处理策略是定义两个指针pHead,pTail并令其初始指向数组头节点和尾节点。pHead从前往后找应该放在尾部的偶数节点,pTail从后往前找应该放在头部的奇数节点,若pHead位于pTail之前则交换二者内容,否则结束处理过程。
这种写法会改变之前的顺序。
private void jiOu(int a[]) //将数组a中奇数放在前面,偶数放在后面
{
int len = a.length;
if(len <= 0) //数组长度为0则返回
return ;
int front = 0, end = len-1;//设置两个指针,一个指向头部,一个指向尾部
while(front<end)
{
while(front<len && (a[front]&1)==1) //从前往后找偶数
front++;
while(end>=0 && (a[end]&1)==0) //从后往前找奇数
end--;
if(front<end)
{
int swap = a[front]; //将奇数往前挪,偶数往后挪
a[front] = a[end];
a[end] = swap;
}
}
}
62.环形链表入口点:
AC段距离是x, a为环入口到相遇点的路程(蓝色路程,假设顺时针走),c为环的长度(蓝色+橙色路程)。两个指针,慢指针每次走一步,快指针每次走两步。
当快慢指针相遇的时候:慢指针走的距离是slow=x+m*c+a; 快指针走的距离是:fast=x+n*c+a;
2slow = fast;==> x = (n - 2 * m )*c - a = (n - 2 *m -1 )*c + c - a;
即环前面的路程 = 数个环的长度(为可能为0) + c - a;
所以我们可以让一个指针从A起点开始走,另一个指针从相遇点B开始走,两指针的速度一样,这两个指针相遇的时候正好是环入口点。
时间复杂度:O(n)
空间复杂度:O(1)
public class Solution {
public ListNode EntryNodeOfLoop(ListNode pHead){
if(pHead==null|| pHead.next==null|| pHead.next.next==null)
return null;
ListNode fast=pHead.next.next;
ListNode slow=pHead.next;
//先判断有没有环
while(fast!=slow){
if(fast.next!=null&& fast.next.next!=null){
fast=fast.next.next;
slow=slow.next;
}else{
//没有环,返回
return null;
}
}
//循环出来的话就是有环,且此时fast==slow.
fast=pHead;
while(fast!=slow){
fast=fast.next;
slow=slow.next;
}
return slow;
}
}
另一种方法:
时间复杂度为O(n),两个指针,一个在前面,另一个紧邻着这个指针,在后面。
两个指针同时向前移动,每移动一次,前面的指针的next指向NULL。
也就是说:访问过的节点都断开,最后到达的那个节点一定是尾节点的下一个,也就是循环的第一个。
这时候已经是第二次访问循环的第一节点了,第一次访问的时候我们已经让它指向了NULL,所以到这结束。
public class Solution {
public ListNode EntryNodeOfLoop(ListNode pHead)
{
if(pHead==null||pHead.next==null)
return null;
else{
ListNode pre = pHead;
ListNode front = pHead.next;
while(front!=null){
pre.next= null;
pre = front;
front = front.next;
}
return pre;
}
}
}
63.合并有序数组
以2个排序好的数组M和数组N为例,i=0和j=0分别为M和N的起始下标。
1.M第一个元素与N第一个元素比较,如果M的第一个元素小于N的第一个元素值,此时新数组MN的第一个元素的值为M[i],下标为[i+j]。
2.此时M的下标i+1,与N的第一个元素继续比较,如果M的第二元素大于N的第一元素值,此时新数组MN的第二个元素的值为N[j],下标为[i+j](实际上是i=1,j=0)。
3.此时N的下标j+1,重复比较的过程。
public int[] getArraySort(int[] m,int[] n){
int im = 0; //数组m的起始位置,用于数组m下标计数
int jn = 0;
int[] mn = new int[m.length + n.length]; //排序后新数组大小
while(im < m.length && jn <n.length){
if(m[im]<=n[jn]){
mn[im + jn] = m[im];
im++;
}else{
mn[im + jn] = n[jn];
jn++;
}
}
//其中一个数组最后的元素值小于第二个数组的某个元素值,遍历第二个数组剩余元素值加入新数组
while(im < m.length){
mn[im + jn] = m[im];
im++;
}
while(jn < n.length){
mn[im + jn] = n[jn];
jn++;
}
return mn;
}
64.给出两个日期,判断两个日期之间相差的天数
/**
* 通过时间秒毫秒数判断两个时间的间隔 当两个日期相差不到24小时的话,不算一天
* @param date1
* @param date2
* @return
*/
public static int differentDaysByMillisecond(Date date1,Date date2)
{
int days = (int) ((date2.getTime() - date1.getTime()) / (1000*3600*24));
return days;
}
第二种方法:
/**
* date2比date1多的天数 这种方法当日期相差不到24小时的话,算一天
* @param date1
* @param date2
* @return
*/
public static int differentDays(Date date1,Date date2)
{
Calendar cal1 = Calendar.getInstance();
cal1.setTime(date1);
Calendar cal2 = Calendar.getInstance();
cal2.setTime(date2);
int day1= cal1.get(Calendar.DAY_OF_YEAR);
int day2 = cal2.get(Calendar.DAY_OF_YEAR);
int year1 = cal1.get(Calendar.YEAR);
int year2 = cal2.get(Calendar.YEAR);
if(year1 != year2) //不是同一年
{
int timeDistance = 0 ;
for(int i = year1 ; i < year2 ; i ++)
{
if(i%4==0 && i%100!=0 || i%400==0) //闰年
{
timeDistance += 366;
}
else //不是闰年
{
timeDistance += 365;
}
}
return timeDistance + (day2-day1) ;
}
else //同年
{
System.out.println("判断day2 - day1 : " + (day2-day1));
return day2-day1;
}
}