京东Java开发

九点做完了京东的Java开发工程师的笔试题。30道选择,两道编程。
选择题里有一半是看代码选结果,问的是初始化方面的知识。线程优先级对输出结果的影响。Linux chmod命令,Shell表达式echo `expr 3/4`结果为3/4。

Java类的初始化顺序

参见Java虚拟机(四)——JVM类加载机制中初始化阶段的内容。静态变量和静态初始化块是依照他们在类中的定义顺序进行初始化的。同样,变量和初始化块也遵循这个规律。遇到new进行初始化(在此之前的加载、验证、准备阶段自然也要完成)。
1)没有继承
(静态变量、静态初始化块)->(变量、变量初始化块)->构造方法
2)有继承的情况
(父类静态变量、父类静态初始化块)->(子类静态变量、子类静态变量初始化块)->(父类变量初始化、父类变量初始化块)->父类构造方法->(子类变量初始化、子类变量初始化块)->子类构造方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Solution2 {	
// 静态变量
public static String staticField1 = "静态变量前";
// 静态初始化块
static {
System.out.println("静态初始化块");
}
// 静态变量
public static String staticField2 = "静态变量后";
public static String staticField3 = "静态变量后2";
//构造器
Solution2() {
System.out.println("构造器初始化");
}
// Test
public static void main(String[] args) {
System.out.println(staticField1); //this调用,默认类名
//Solution2 so = new Solution2(); //如果实例化,一定是先初始化静态代码块
System.out.println(Solution2.staticField2); //类名显式调用
}
}

输出:

1
2
3
静态初始化块
静态变量前
静态变量后

可见,就算没有进行实例化,由于调用了静态变量,也初始化了类。静态块与静态成员的初始化工作与实例化过程无关,实例化必须先执行静态块和静态成员,但并不代表实例化一定会执行静态块和静态成员。只有当实例化的对应的类加载入虚拟机的时候,才会进行这种操作。有些时候执行静态块或者初始化静态成员不一定就是实例化该类对象才会进行的,例如调研该类的某静态成员或者静态方法,又例如该类的子类被实例化或者调用了静态成员或静态方法等。
静态变量和静态初始化块的初始化时根据它们的先后顺序进行的。上例中加载了main()方法对应的类,所以按顺序加载了静态变量、静态初始化块。由于静态初始化块中有输出,所以才先输出了静态初始化块的内容。为了验证这点,给出了下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class TestOrder { 
// 静态变量
public static TestA a = new TestA();
// 静态初始化块
static {
System.out.println("静态初始化块");
}
// 静态变量
public static TestB b = new TestB();
//Test
public static void main(String[] args) {
new TestOrder();
}
}
1
2
3
4
5
class TestA { 
public TestA() {
System.out.println("Test--A");
}
}
1
2
3
4
5
class TestB { 
public TestB() {
System.out.println("Test--B");
}
}

输出:

1
2
3
Test--A 
静态初始化块
Test--B

静态语句块只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块中可以赋值但不能访问。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class TestOrder { 
// 静态变量
public static TestA a = new TestA();
// 静态初始化块
static {
//c = 0;
//System.out.println(c); //可以赋值但不能访问,报错Unresolved compilation problem
System.out.println("静态初始化块");
}
// 静态变量
public static TestB b = new TestB();
public static int c = 1;
//Test
public static void main(String[] args) {
new TestOrder();
}
}

去掉静态初始化块中的注释会报错:

1
2
Exception in thread "main" java.lang.Error: Unresolved compilation problem: 
at TestOrder.main(TestOrder.java:16)

下面加上实例,比较构造器的位置对输出顺序有没有影响:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Solution2 {
// 静态变量
public static String staticField1 = "静态变量前";
// 静态初始化块
static {
System.out.println("静态初始化块");
}
// 静态变量
public static String staticField2 = "静态变量后";
public static String staticField3 = "静态变量后2";
//构造器
Solution2() {
System.out.println("构造器初始化");
}
// Test
public static void main(String[] args) {
System.out.println(staticField1);
Solution2 so = new Solution2(); //如果实例化,一定是先初始化静态代码块
System.out.println(Solution2.staticField2);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Solution2 {
//构造器
Solution2() {
System.out.println("构造器初始化");
}
// 静态变量
public static String staticField1 = "静态变量前";
// 静态初始化块
static {
System.out.println("静态初始化块");
}
// 静态变量
public static String staticField2 = "静态变量后";
public static String staticField3 = "静态变量后2";
// Test
public static void main(String[] args) {
System.out.println(staticField1);
Solution2 so = new Solution2(); //如果实例化,一定是先初始化静态代码块
System.out.println(Solution2.staticField2);
}
}

输出都是相同的:

1
2
3
4
静态初始化块
静态变量前
构造器初始化
静态变量后

可见,构造器在任何地方,都不会改变它的输出顺序。注意遇到new才初始化了构造器。

B/S(brower/server)模式的优缺点

(1) 优点:

  • 具有分布性特点,可以随时随地进行查询、浏览等业务处理。
  • 业务扩展简单方便,通过增加网页即可增加服务器功能。
  • 维护简单方便,只需要改变网页,即可实现所有用户的同步更新。
  • 开发简单,共享性强

(2) B/S 模式的缺点

  • 个性化特点明显降低,无法实现具有个性化的功能要求。
  • 操作是以鼠标为最基本的操作方式,无法满足快速操作的要求。
  • 页面动态刷新,响应速度明显降低。
  • 无法实现分页显示,给数据库访问造成较大的压力。
  • 功能弱化,难以实现传统模式下的特殊功能要求。

2)C/S (client/server)模式的优点和缺点
(1) 优点

  • 由于客户端实现与服务器的直接相连,没有中间环节,因此响应速度快。
  • 操作界面漂亮、形式多样,可以充分满足客户自身的个性化要求。
  • 结构的管理信息系统具有较强的事务处理能力,能实现复杂的业务流程。

(2) 缺点

  • 需要专门的客户端安装程序,分布功能弱,针对点多面广且不具备网络条件的用户群体,不能够实现快速部署安装和配置。
  • 兼容性差,对于不同的开发工具,具有较大的局限性。若采用不同工具,需要重新改写程序。
  • 开发成本较高,需要具有一定专业水准的技术人员才能完成。

值类型和引用类型的区别

在Java中类型可分为两大类:值类型与引用类型。值类型就是基本数据类型(如int ,double 等),而引用类型,是指除了基本的变量类型之外的所有类型(如通过 class 定义的类型)。所有的类型在内存中都会分配一定的存储空间(形参在使用的时候也会分配存储空间,方法调用完成之后,这块存储空间自动消失), 基本的变量类型只有一块存储空间(分配在stack中), 而引用类型有两块存储空间(一块在stack中,一块在heap中),在函数调用时Java是传值还是传引用,下面用图形与代码来解释:
值类型和引用类型

设计模式

适配器(Adapter)模式:可以让接口不兼容而不能工作的那些类可以一起工作。
备忘录(Mementto)模式:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。

DML、DDL和DCL

1)DML(data manipulation language,数据操纵语言)
就是我们最经常用到的 SELECT、UPDATE、INSERT、DELETE 主要用来对数据库的数据进行一些操作。
2)DDL(data definition language,数据库定义语言)
其实就是我们在创建表的时候用到的一些sql,比如CREATE、ALTER、DROP等。DDL主要是用在定义或改变表的结构、数据类型、表之间的链接和约束等初始化工作上。
3)DCL(data control language,数据库控制语言)
是用来设置或更改数据库用户或角色权限的语句,包括grant、deny、revoke等语句。这个比较少用到。

drop、truncate和delete的区别

1.delete

1) delete是DML,执行delete操作时,每次从表中删除一行,并且同时将该行的的删除操作记录在redo和undo表空间中以便进行回滚(rollback)和重做操作,但要注意表空间要足够大,需要手动提交(commit)操作才能生效,可以通过rollback撤消操作。
2) delete可根据条件删除表中满足条件的数据,如果不指定where子句,那么删除表中所有记录。
3) delete语句不影响表所占用的extent,高水线(high watermark)保持原位置不变。
2.truncate

1) truncate是DDL,会隐式提交,所以,不能回滚,不会触发触发器。
2) truncate会删除表中所有记录,并且将重新设置高水线和所有的索引,缺省情况下将空间释放到minextents个extent,除非使用reuse storage,。不会记录日志,所以执行速度很快,但不能通过rollback撤消操作(如果一不小心把一个表truncate掉,也是可以恢复的,只是不能通过rollback来恢复)。
3) 对于外键(foreignkey)约束引用的表,也会照删不误,因此不能使用 truncate table,而应使用不带 where 子句的 delete 语句。
4) truncatetable不能用于参与了索引视图的表。
3.drop:

1) drop是DDL,会隐式提交,所以,不能回滚,不会触发触发器。
2) drop语句删除表结构及所有数据,并将表所占用的空间全部释放。
3) drop语句将删除表的结构所依赖的约束,触发器,索引,依赖于该表的存储过程/函数将保留,但是变为invalid状态。

总结

对于表来说,不再需要该表用drop;需要保留表,但要删除所有记录用truncate;删除部分记录用delete
1)在速度上,一般来说,drop> truncate > delete
2)Truncate删除所有行,如果遇到任何一行违反违约(外键约束),照删不误,但delete则会返回错误。
3)DELETE语句执行删除的过程是每次从表中删除一行,并且同时将该行的删除操作作为事务记录在日志中保存以便进行进行回滚操作。TRUNCATE TABLE 则一次性地从表中删除所有的数据并不把单独的删除操作记录记入日志保存,删除行是不能恢复的。并且在删除的过程中不会激活与表有关的删除触发器。执行速度快。

List集合扩容

List集合的底层实现是数组结构,而数组的大小是不可改变的,因此当其容器内存不足时,需要进行扩容,扩容的方法就是重新分配一个新数组,然后复制元素到新数组中,再将元素添加到数组末位。
以ArrayList集合为例,通过分析ArrayList的源码,发现构造器有三个,分别是:无参、一个int型参数、一个集合泛型。默认数组大小的常量为10(注意是默认,如果指定数组大小的创建,则没有扩充)。其中的grow()方法判断加一个元素之后,数组大小是否超出当前数组的大小,若超出则扩容:int newCapacity = oldCapacity + (oldCapacity >> 1); 表示新数组的长度=原数组的长度+原数组长度的一半。

总结

1.List 元素是有序的、可重复
1)ArrayList 默认初始容量为10
线程不安全,查询速度快
底层数据结构是数组结构
加载因子为1:即当元素个数超过容量长度时,进行扩容
扩容增量:原容量的 0.5倍
如 ArrayList的容量为10,一次扩容后是容量为15
2) Vector:线程安全,但速度慢
底层数据结构是数组结构
加载因子为1:即当元素个数超过容量长度时,进行扩容
扩容增量:原容量的1倍
如 Vector的容量为10,一次扩容后是容量为20
2.Set(集) 元素无序的、不可重复。
1)HashSet:线程不安全,存取速度快
底层实现是一个HashMap(保存数据),实现Set接口
默认初始容量为16(为何是16,见下方对HashMap的描述)
加载因子为0.75:即当元素个数超过容量长度的0.75倍时,进行扩容
扩容增量:原容量的 1 倍
如 HashSet的容量为16,一次扩容后是容量为32
其实HashSet 的创建其实就是一个HashMap,Map是一个双列集合
2)HashMap:默认初始容量为16
为何是16:16是2^4,可以提高查询效率
加载因子为0.75:即当元素个数超过容量长度的0.75倍时,进行扩容
扩容增量:原容量的 1 倍
如 HashSet的容量为16,一次扩容后是容量为32

1
2
3
4
5
6
例:
ArrayList list = new ArrayList(20);中的list扩充几次?
答:0次。
解析:
ArrayList list=new ArrayList(); //这种是默认创建大小为10的数组,每次扩容大小为1.5倍。
ArrayList list=new ArrayList(20); //这种是指定数组大小的创建,没有扩充。

GPRS是通用分组无线业务(General Packet Radio Service)的英文简称,是一种新的分组数据承载业务。GPRS与现有的GSM语音系统最根本的区别是,GSM是一种电路交换系统,而GPRS是一种分组交换系统。因此,GPRS特别适用于间断的、突发性的或频繁的、少量的数据传输,也适用于偶尔的大数据量传输。这一特点正适合大多数移动互联的应用

编程题

第一题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package Jdong;
/**
* 输入N个顶点,M条边的图,输入两顶点定义的边,
* 输出是否可以构成多部图
*/
import java.util.Scanner;

public class Main {
public static String isCover(int[][] edge, int N) {
String s = "Yes";
int[] sum = new int[N + 1];
//边的下标都从1开始
for (int i = 1; i < N + 1; i++) {
for (int j = 1; j < N + 1; j++) {
sum[i] += edge[i][j]; //将每行的边加起来,表示为出度或入度
}
}
for (int i = 1; i < N + 1; i++) {
for (int j = 1; j < N + 1; j++) {
if (edge[i][j] == 0) { //如果顶点i、j之间没有边
if (sum[j] != sum[i]) //如果i的出度与j的出度不等,则不能构成
s = "No";
}
}
}
return s;
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int T = in.nextInt(); //需要进行判断的图的个数
while (T > 0) {
int N = in.nextInt();
int M = in.nextInt();
int X, Y;
////构建邻接矩阵edge,由于边的输入是从1开始的,因此邻接矩阵使用的下标是1~N
int[][] edge = new int[N + 1][N + 1];
for (int i = 0; i < M; i++) {
X = in.nextInt();
Y = in.nextInt();
edge[X][Y] = 1; //无向图,对称的
edge[Y][X] = 1;
}

String s = isCover(edge, N);
System.out.println(s);
T--;
}
}
}

第二题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package Jdong;
/*
* 京东Java研发编程第二题
* 判断有多少个合格产品:如果某个产品的三个指标分别为ai、bi、ci,
* 若存在另一个产品使得aj>ai、bj>bi、cj>ci则说明这个产品不合格,
* 输入产品个数n,和产品的三个指标;输出不合格产品数。
*/
import java.io.FileNotFoundException;
import java.util.Scanner;

public class Main2 {
public static void main(String[] args) throws FileNotFoundException {
Scanner in = new Scanner(System.in);
int n = in.nextInt(); //行数
int m = in.nextInt(); //列数
int count = 0;
//定义每一列
int[] a = new int[n];
int[] b = new int[n];
int[] c = new int[n];
for (int j = 0; j < m; j++) { //对每行赋值
a[j] = in.nextInt();
b[j] = in.nextInt();
c[j] = in.nextInt();
}
// 一层一层地开始判断
for (int i = 0; i < n; i++) {
int ai = a[i];
int bi = b[i];
int ci = c[i];
//将第j行与第i行进行比较,这里i和j都是从0开始的,会有自己跟自己比较的情况,但对count的值没有影响。
for (int j = 0; j < n; j++) {
int aj = a[j];
int bj = b[j];
int cj = c[j];
if(aj>ai && bj>bi && cj>ci){
count++;
break; //只要第i行不合格,就不必再比较下去了,则跳出内层for循环
}
}
}
System.out.println(count);
}
}

参考

《深入理解Java虚拟机》
Java类的初始化顺序 (静态变量、静态初始化块、变量、初始…
Java静态成员初始化问题
什么是B/S模式?C/S模式?及它们的优缺点
深入理解Java引用类型
Java设计模式——接口型模式:适配器模式
java 设计模式 行为模式 -Memento(备忘录模式)
drop、truncate和delete的区别
浅谈DML、DDL、DCL的区别
集合扩容问题(ArrList为例,常用集合扩容机制)–JAVA 基础
常用集合的默认初始容量和扩容的原因