2020/07/22 笔试错误题

选择题

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;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值