选择题
1. 类中的静态变量、实例变量、静态代码块、实例代码块的加载顺序。
回顾类加载过程:
重要
加载阶段完成的三件事:
- 通过类的全限定名获取类的二进制字节流
- 将字节流中代表静态存储结构转化成方法区的运行时数据结构
- 在堆中生成这个类的Class对象,作为方法区这个类的各种数据的访问入口
准备阶段需要注意的是:
- 在这个阶段首先给静态变量分配内存并且初始值是默认。(引用变量为null,int型为0等)
- 这些变量所使用的内存都应该在方法区进行分配。但方法区本身是个逻辑上的区域。所以在实现上JDK6,JDK7,JDK8有区别或者说是改进。
扩展:JDK6、7、8方法区的区别。
JDK6时代,方法区的实现是永久代。方法区就是一个逻辑概念,而永久代就是物理上堆内存的一块空间。
永久代存放的信息:
- 类信息
- 字段信息
- 方法信息
- 常量池
- 类变量(静态变量)
- 到ClassLoad的引用
- 到Class对象的引用
- 方法表
JDK7时代,永久代中的静态变量和字符串常量池移出去,即移动到了堆中。
JDK8时代,废弃的永久代的概念,而是将方法区的实现用本地内存中的元空间实现。把JDK7中永久代还剩余的内容(主要是类信息)全部移动到元空间中。
参考《深入理解Java虚拟机》46页 方法区的概念。
常量池分为以下几类:
1)静态常量池:即*.class文件中的常量池,class文件中的常量池不仅仅包含字符串/数字这些字面量,还包含类、方法的信息,占用class文件绝大部分空间。这种常量池主要用于存放两大类常量:字面量和符号引用量,字面量相当于Java语言层面常量的概念,如文本字符串,声明为final的常量值等;符号引用则属于编译原理 方面的概念,包括了如下三种类型的常量:类和接口的全限定名、字段名称描述符、方法名称描述符。
(2)运行时常量池:虚拟机会将各个class文件中的常量池载入到运行时常量池中,即编译期间生成的字面量、符号引用,总之就是装载class文件。
(3)字符串常量池 :字符串常量池可以理解为运行时常量池分出来的部分。加载时,对于class的静态常量池,如果字符串会被装到字符串常量池中。
(4)整型常量池:Integer,类似字符串常量池,可以理解为运行时常量池分出来的。加载时,对于class的静态常量池装的东西,如果是整型会被装到整型常量池中。
类似的还有Character、Long等等常量池(基本数据类型没有哦)。。。
扩展结束,回到初始化阶段,初始化阶段才涉及到静态代码块的加载顺序。
初始化阶段
<clinit>()方法作用:由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生,收集后的顺序就是编写代码的顺序。
注意点:
- 静态语句块中只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块可以赋值,但不能访问。
public class Test {
static {
i = 0; //给变量赋值,可以正常编译通过
System.out.print(i); //编译报错
}
static int i = 1;
}
- <clinit>()方法与类的构造函数不同(<init>()方法)不同。 虚拟机保证在子类的<clinit>()方法执行前,父类的<clinit>()方法已经执行完毕。所以父类中定义的静态语句块要优先于子类的变量赋值操作。
- 接口中不能使用静态代码块,但是会生成<clinit>()方法。接口与类不同的是,执行接口的<clinit>()方法不需要先执行父接口的<clinit>()方法。
总结:继承的类初始化
1、首先会加载父类,在准备阶段对类中所有的静态变量进行分配内存设置初始值(默认值);
2、然后执行<clinit>()方法,收集类中所有的静态变量和静态代码块,按顺序执行操作。
3、父类的<clinit>()方法执行完毕,开始加载子类,进入准备阶段对所有的静态变量分配内存并设置默认值。
4、进入初始化阶段,执行子类的<clinit>()方法,相同操作进行操作。
5、开始实例化,首先进入子类的构造函数,由于构造函数的第一行隐藏调用父类的构造函数,所有跳转到父类的实例操作。
6、进入父类的构造函数,先不执行父类构造函数,而是将类中的所有成员变量和实例代码块,从上到下顺序执行一遍,然后再执行构造函数内的代码。
7、然后再同6一样执行一遍子类。
2. 数据库中drop table、truncate table、delete的区别,考是否会释放空间
- drop
删除内容、表定义、释放空间。删的一干二净。 - truncate
删除内容、释放空间、但不删除定义 。表单结构还在,只是清空了数据而且也释放了空间。(注意:truncate不能删除行数据,要删就全删了) - delete
删除内容,不删除定义,不释放空间。(过程是一行一行的删,效率低)
释放空间和不释放空间的区别:
上图Id标识列中,因之前delete过行数据,所以会出现标识列不连续(体现了delete删除是不释放空间的)。
编程题
1、平行四边形构造
题目描述:
给出你n条长度不一的边,请你从中选择四条边,组成一个最大的平行四边形。请你输出最大的平行四边形的面积。
输入描述
输入第一行包含一个正整数n,表示边的数量。(4<=n<=50000)
输入第二行包含n个正整数,表示n条边的长度,边的长度不会超过10^9。
输出描述
输出仅包含一个正整数,即最大的平行四边形的面积,无解则输出-1。
5 3 3 4 4 5
样例输出
12
思路:
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
public class Test01 {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();//n : 输入的个数
long[] num = new long[n];
/**
* 循环遍历数组
* 创建一个Map容器,一边加入值,一边把出现的次数+1
* 在加入的过程中判断,
* 1、如果等于了2,就判断当前的len大,就向下递推一个数然后计算结果
* 2、如果出现边的次数为4,则直接算面积是否大于结果res,如果打,就替换,
* 3、最后判断返回结果
*/
long with = 0, len = 0;
long res = -1;
//创建容器
Map<Long, Integer> map = new HashMap<Long, Integer>();
for (int i = 0; i < n; i++) {
//输入
num[i] = in.nextLong();
//加到容器内,次数+1
int count = 1;
if (map.containsKey(num[i])) {
map.put(num[i],(count = map.get(num[i]) + 1));
} else {
map.put(num[i], 1);
}
if(count == 2 && num[i] > 0) {
//如果当前边长大于原来的长度,就递推
if(num[i] > len) {
with = len;
len = num[i];
res = with * len;
}
} else if(count == 4) { // 如果是四条边,就判断当前的面积和之前的面积的大小,每次取最大值
if (num[i] > 0 && res < num[i] * num[i]) {
res = num[i] * num[i];
}
}
}
if (res <= 0) {
System.out.println(-1);
} else {
System.out.println(res);
}
}
}
2、排序
题目描述:
有一种排序算法定义如下,该排序算法每次只能把一个元素提到序列的开头,例如2,1,3,4,只需要一次操作把1提到序列起始位置就可以使得原序列从小到大有序。现在给你个乱序的1-n的排列,请你计算最少需要多少次操作才可以使得原序列从小到大有序。
输入描述
输入第一行包含两个正整数n ,表示序列的长度。(1<=n<=100000)
接下来一行有n个正整数,表示序列中的n个元素,中间用空格隔开。(1<=a_i<=n)
输出描述
输出仅包含一个整数,表示最少的操作次数。
样例:
4
2 1 3 4
样例输出:
1
import java.util.Arrays;
import java.util.Scanner;
public class Solution {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
int[] nums = new int[n];
int[] sortData = new int[n];
for (int i = 0; i < nums.length; i++) {
nums[i] = in.nextInt();
sortData[i] = nums[i];
}
/**
* 思路:1、创建一个排好序的辅助数组, p 原数组指针,q:排好序的数组指针
* 2、从后向前挨个对比,刚开始p == q == n - 1; 判断当前是否相等。
* 3、相等说明不用移动,两个一起向前移动。
* 4、不相等,则把原始数组的指针p向后移动,一直找到相等的时候,两个指针一起向前移动。
* 5、如果p移到了头,说明q之前的数字都要移动。所以直接返回下标位置+1即可。
*/
Arrays.sort(sortData);
int p = n - 1;
int q = n - 1;
while(p >= 0 && q >= 0) {
if(nums[p] == sortData[q]) {
//相等两个一起向前移动
p--;
q--;
} else {
//不相等,原始数据指针向前移动,直到移动到头就结束
while(p >= 0 && nums[p] != sortData[q]) {
p--;
}
}
}
System.out.println(q + 1);
}
}
3、范围攻击
题目描述:
在一个2D横版游戏中,所有的怪物都是在X轴上的,每个怪物有两个属性X和HP,分别代表怪物的位置和生命值。
玩家控制的角色有一个AOE(范围攻击)技能,玩家每次释放技能可以选择一个位置x,技能会对[x-y,x+y]范围内的所有怪物造成1点伤害,请问,玩家最少需要使用多少次技能,才能杀死所有怪物,怪物血量清0即视为被杀死。
输入描述
输入第一行包含一个两个正整数n和y,分别表示怪物的数量和技能的范围。(1<=n<=100000)
接下来有n行,每行有两个正整数x和hp分别表示一只怪物的位置和血量。(1<=x,hp<=10^9)
输出描述
输出仅包含一个整数,表示最少使用的技能次数。
输入用例
3 5
1 10
10 5
22 3
输出样例
13
import java.util.Arrays;
import java.util.Comparator;
import java.util.Scanner;
public class Test03 {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();//怪物的数量
int y = in.nextInt();//技能范围
//所有怪物
Monster[] monsters = new Monster[n];
//x怪物的位置,hp怪物的血量
for(int i = 0; i < n; i++) {
Monster monster = new Monster();
monster.setX(in.nextInt());
monster.setHp(in.nextInt());
monsters[i] = monster;
}
//怪物输入完成
Arrays.sort(monsters, new Comparator<Monster>() {
@Override
public int compare(Monster o1, Monster o2) {
return o1.getX() - o2.getX();
}
});
int count = 0, left = 0;
while(left < n) {
int kill = monsters[left].getHp();
count += kill;//第一个怪的血量,就得释放多少次技能
//技能范围[left, left + 2 * y]
//处理技能范围内的所有怪
int j = left + 1;
while(j < n && monsters[j].getX() <= 2 * y + monsters[left].getX()) {
monsters[j].setHp(monsters[j].getHp() - kill);//怪掉血
j++;
}
//释放了技能
//移动位置,找还有血量的怪
int k = left + 1;
while(k < n && monsters[k].getHp() <= 0) {
k++;
}
left = k;
}
System.out.println(count);
}
}
//怪物对象
class Monster {
private int x;
private int hp;
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getHp() {
return hp;
}
public void setHp(int hp) {
this.hp = hp;
}
}