JAVA面试题连载2017

2017年初,我踏上了面试的路程,这一路非常坎坷,但是充满了收获。让我重新认识了自己,认识到自己的不足之处。以下是我面试以及笔试过程中收集到的各类问题。


1、String 与StringBuffer


JAVA平台提供了两个类:String和StringBuffer,它们可以储存和操作字符串,即包含多个字符的字符数据。这个String类提供了数值不可改变的字符串。而这个StringBuffer类提供的字符串进行修改。当你知道字符数据要改变的时候你就可以使用StringBuffer。典型地,你可以使用StringBuffers来动态构造字符数据。另外,String实现了equals方法,new String(“abc”).equals(newString(“abc”)的结果为true,而StringBuffer没有实现equals方法,所以,new StringBuffer(“abc”).equals(new StringBuffer(“abc”)的结果为false。接着要举一个具体的例子来说明,我们要把1到100的所有数字拼起来,组成一个串。
StringBuffer sbf =new StringBuffer(); 
for(inti=0;i<100;i++){       
sbf.append(i);
}
上面的代码效率很高,因为只创建了一个StringBuffer对象,而下面的代码效率很低,因为创建了101个对象。
String str = newString();  
for(inti=0;i<100;i++){      
 str = str + i;
}
在讲两者区别时,应把循环的次数搞成10000,然后用endTime-beginTime来比较两者执行的时间差异,最后还要讲讲StringBuilder与StringBuffer的区别。
String覆盖了equals方法和hashCode方法,而StringBuffer没有覆盖equals方法和hashCode方法,所以,将StringBuffer对象存储进Java集合类中时会出现问题。
补充:StringBuffer是线程安全的,如果是单线程的话使用StringBuilder,这样效率会更高。Stirng是线程安全的,因为String是不可变的。


2、String为什么是不可变得?


这要从java的源码说起,查看java的源码你就会发现,String类中value,offset和count这三个变量都是private的并且都是final类型的,String类中比没有提供修改这三个私有变量的方法,所以String类型变量一旦初始化就不能修改。


3、hashCode作用


hashcode是用来查找的,如果你学过数据结构就应该知道,在查找和排序这一章有  例如内存中有这样的位置  0  1  2  3  4  5  6  7    而我有个类,这个类有个字段叫ID,我要把这个类存放在以上8个位置之一,如果不用hashcode而任意存放,那么当查找时就需要到这八个位置里挨个去找,或者用二分法一类的算法。 但如果用hashcode那就会使效率提高很多。我们这个类中有个字段叫ID,那么我们就定义我们的hashcode为ID%8,然后把我们的类存放在取得得余数那个位置。比如我们的ID为9,9除8的余数为1,那么我们就把该类存在1这个位置,如果ID是13,求得的余数是5,那么我们就把该类放在5这个位置。这样,以后在查找该类时就可以通过ID除8求余数直接找到存放的位置了。


4、线程的实现方式,有什么不同


线程有两种实现方式:分别是继承Thread类与实现Runnable接口
参考链接:http://www.cnblogs.com/victory8023/p/5549284.html
实现Runnable接口相对于继承Thread类来说,有如下显著的好处:
(1)适合多个相同程序代码的线程去处理同一资源的情况,把虚拟CPU(线程)同程序的代码,数据有效的分离,较好地体现了面向对象的设计思想。
(2)可以避免由于Java的单继承特性带来的局限。我们经常碰到这样一种情况,即当我们要将已经继承了某一个类的子类放入多线程中,由于一个类不能同时有两个父类,所以不能用继承Thread类的方式,那么,这个类就只能采用实现Runnable接口的方式了。
(3)有利于程序的健壮性,代码能够被多个线程共享,代码与数据是独立的。当多个线程的执行代码来自同一个类的实例时,即称它们共享相同的代码。 多个线程操作相同的数据,与它们的代码无关。当共享访问相同的对象是,即它们共享相同的数据。当线程被构造时,需要的代码和数据通过一个对象作为构造函数 实参传递进去,这个对象就是一个实现了Runnable接口的类的实例。


5、Spring AOP的实现方式


参考链接:http://blog.csdn.net/moreevan/article/details/11977115/
类别分为静态AOP(包括静态织入)和动态AOP(包括动态代理、动态字节码生成、自定义类加载器、字节码转换)。
静态织入:
a、原理:在编译期,切面直接以字节码形式编译到目标字节码文件中 ;
b、优点:对系统性能无影响; 
c、缺点:不够灵活;
动态代理 :
a、原理:在运行期,目标类加载后,为接口动态生成代理类。将切面织入到代理类中;
b、优点:更灵活;
c、缺点:切入的关注点要实现接口;
动态字节码生成:
a、原理:在运行期,目标类加载后,动态构建字节码文件生成目标类的子类,将切面逻辑加入到子类中;
b、优点:没有接口也可以织入;
c、缺点:扩展类的实例方法为final时,无法进行织入;
自定义类加载器
a、原理:在运行期,目标加载前,将切面逻辑加到目标字节码里;
b、优点:可以对绝大部分类进行织入;
c、缺点:代码中若使用了其它类加载器,则这些类将不会被织入;
字节码转换
a、原理:在运行期,所有类加载器加载字节码前进行拦截;
b、优点:可以对所有类进行织入;
c、缺点:


6、http是udp协议还是tcp


这个问题还是上大学时学过的,好久没有进行网络编程,都给忘记了,http使用的是tcp传输协议,这种传输协议比较稳定和安全,不容易出错,丢包。UDP协议传输效率高,但是容易出错。
参考路径:http://www.cnblogs.com/Jessy/p/3536163.html


7、xml有哪些解析技术?区别是什么?
DOM生成和解析XML文档
为 XML 文档的已解析版本定义了一组接口。解析器读入整个文档,然后构建一个驻留内存的树结构,然后代码就可以使用 DOM 接口来操作这个树结构。
优点:整个文档树在内存中,便于操作;支持删除、修改、重新排列等多种功能;
缺点:将整个文档调入内存(包括无用的节点),浪费时间和空间;
使用场合:一旦解析了文档还需多次访问这些数据;硬件资源充足(内存、CPU)。
SAX生成和解析XML文档
为解决DOM的问题,出现了SAX。SAX事件驱动,当解析器发现元素开始、元素结束、文本、文档的开始或结束等时,发送事件,程序员编写响应这些事件的代码,保存数 据。
优点:不用事先调入整个文档,占用资源少;SAX解析器代码比DOM解析器代码小,适于Applet,下载。
缺点:不是持久的;事件过后,若没保存数据,那么数据就丢了;无状态性;从事件中只能得到文本,但不知该文本属于哪个元素;
使用场合:Applet;只需XML文档的少量内容,很少回头访问;机器内存少;
DOM4J生成和解析XML文档
DOM4J 是一个非常非常优秀的Java XML API,具有性能优异、功能强大和极端易用使用的特点,同时它也是一个开放源代码的软件。如今你可以看到越来越多的 Java 软件都在使用 DOM4J 来读写 XML,特别值得一提的是连 Sun 的 JAXM 也在用 DOM4J。

8、常用的设计模式,以及应用场景
单例模式:采用单例模式设计的类只有一个实例化对象。
实现步骤:构造函数私有化,不允许外部类实例化;类的实例化在类的内部进行;定义一个静态方法返回该类的实例;
方式一:饿汉式
public class Singleton {
    private static Singleton instance = new Singleton(); //加载类时进行实例化
    private Singleton(){} 
  public static Singleton getInstance() {//返回实例方法
       return instance; 
   } 
 } 
在类加载时实例化,所以类加载慢,但是调用返回实例方法较快。
方式二:懒汉式(以下代码可以改进,把synchronized块加到方法内部)
 public class Singleton { 
  private static Singleton instance = null;//类加载时没有进行实例化
    private Singleton(){}
  public static synchronized Singleton getInstance() {
      if (instance == null){
       instance = new Singleton();


        }
        return instance; 
     }
}
在类加载时没有进行实例化,所以类加载快,调用返回实例采用同步方法在运行中效率低,但可以在多线程下运行。
应用场景:单例模式能够保证一个类仅有唯一的实例,并提供一个全局访问点。比如全局计数器,记录网站当前登录人数等。
工厂模式:通过类工厂,动态的返回一个类的实例。不需要自己去创建实例,工厂代理创建。
实现步骤:设计一个接口,几个子类实现了接口,设计一个工厂类,调用的时候调用工厂类来具体创建哪个实现类对象。
interface Fruits { // 定义一个水果的接口    
// 属性 
public void attribute(); 

class Apple implements Fruits {//苹果类
public String attribute(){
    System.out.println("我是苹果:我甜甜的");
}
}
class Hawthorn implements Fruits {//山楂类
public String attribute(){
    System.out.println("我是山楂:我酸酸的");
}
}
class Factory {
public static Fruits  getInstance(String className){
if("Apple".equals(className)){
return new Apple();
}else if("Hawthorn ".equals(className)){
return new Hawthorn ();
}
}
}
//调用
Fruits  f = Factory. getInstance("Apple");
f.attribute();
输出:我是苹果:我甜甜的
应用场景:可以使代码结构清晰,有效地封装变化。在编程中,产品类的实例化有时候是比较复杂和多变的,通过工厂模式,将产品的实例化封装起来,使得调用者根本无需关心产品的实例化过程,只需依赖工厂即可得到自己想要的产品。对调用者屏蔽具体的产品类。如果使用工厂模式,调用者只关心产品的接口就可以了,至于具体的实现,调用者根本无需关心。即使变更了具体的实现,对调用者来说没有任何影响。降低耦合度。产品类的实例化通常来说是很复杂的,它需要依赖很多的类,而这些类对于调用者来说根本无需知道,如果使用了工厂方法,我们需要做的仅仅是实例化好产品类,然后交给调用者使用。对调用者来说,产品所依赖的类都是透明的。
当然工厂模式还分为好多种:比如抽象工厂模式、静态工厂模式等。
其他设计模式请参考:http://www.cnblogs.com/maowang1991/archive/2013/04/15/3023236.html


9、构造函数中的多态体现


public class A {
A(){
printS();
}
public void printS(){
System.out.println("A执行");
}
}
public class B extends A{
B(){
printS();
}
public void printS(){
System.out.println("B执行");
}
}
public class C extends B {
C(){
printS();
}
public void printS(){
System.out.println("C执行");
}
}
public class Test {
public static void main(String[] args){
C c =new C();
c.printS();
}
}
输出结果:
C执行
C执行
C执行
C执行


解析:在子类实例化的时候调用父类的构造函数,子类重写了父类的方法,一层一层向上调用,所以出现了上面的结果。


10.浮点数预算


//1.0逐次减去0.1,0.2,0.3,0.4 最后结果是否等于0,为什么?
public void getDouble(double d){
int count=0;
for(double i=.1;d>=i;i+=.1){
d=d-i;
count++;
}
System.out.println(d);
System.out.println("循环次数:"+count);
}
public static void main(String[] args){
new D().getDouble(1.0);
}
输出结果:
0.3999999999999999
循环次数:3


public static void main(String[] args){
double d=0.2;
System.out.println(d+0.1);
}
输出结果:
0.30000000000000004
解析:计算机二进制数不能表示想0.3这样的小数,只是一个近视值。
参考资料:http://ask.csdn.net/questions/356045


11.生成1~100的随机数


最常用的生成随机数有三种方式:
第一种:Math.random() 生成0到1之间的小数,想要生成1~100可以这样:int s=(int) (Math.random()*100)+1;
第二种:Random 生成的是一个伪随机数,如果两个Random的种子相等,那么生成的随机数相同,这样可以设置一个System.nanoTime()类型的种子,
System.nanoTime()是java1.5增加的相对精确的计时,是获取纳秒级的方法。相比System.currentTimeMillis要精确的多。
值得一提的是该方法的作用是生成一个随机的int值,该值介于[0,n)的区间,也就是0到n之间的随机int值,包含0而不包含n。
Random r = new Random(System.nanoTime());
int rs=r.nextInt(100)+1;
System.out.println(rs);
第三种:Random 一种多线程实现ThreadLocalRandom可以实现多线程随机数。线程安全的。
public class ThreadRandomController implements Runnable {
@Override
public void run() {
String name = Thread.currentThread().getName();
for (int i = 0; i < 10; i++) {
System.out.printf("%s : %s\n", name, ThreadLocalRandom.current()
.nextInt(100)+1);
}
}
public static void main(String[] args) {
Thread[] threads = new Thread[3];
for (int i = 0; i < 3; i++) {
ThreadRandomController taskLocalRandom = new ThreadRandomController();
threads[i] = new Thread(taskLocalRandom);
threads[i].start();
}
}
}

12.Oracle数据库index原理

索引是数据库管理系统提供的一种用于快速访问表中数据的机制。对于oracle数据来说,用户在创建表时就自动创建了一个唯一索引(表的主键)。

select * from all_indexes t where t.TABLE_NAME='tablename' and t.OWNER='user';可以查询出表对于的索引。在oracle数据表中存在一个rowid的伪列,这个rowid是用来标识一条记录所在的物理位置的一个ID号,每一行对应的rowid值是固定不变的,一旦数据存入到数据库中就会确定下来,不会随着对数据库表的操作改变。但是如果改变了数据库文件物理位置,那么rowid也会随之改变。我们对表中某一列建立一个索引的时候(如name列),数据库管理系统会进行一次全表扫描,获取每一条记录的name列数据,进行排列,同时获取每一条记录的rowid,将name值和rowid存入索引段(name,rowid),这样的最后我们称之为索引条目。当我们进行数据查询时,orcale先对索引中的列进行快速搜索,因为之前已经对索引列进行了排序,所以搜索速度很快,这样就可以很快的找到对应的rowid,然后通过rowid在数据表读取对应的数据。

索引按照组织形式和存储方式可以分为:

单列索引,复合索引,B树索引,位图索引,函数索引等(oracle默认创建B树索引)

注意,在创建复合索引时,将不常用的列放在后面。


13.在oracle中varchar和varchar2有什么区别

VARCHAR2把所有字符都占两字节处理(一般情况下),varchar只对汉字和全角等字符占两字节,数字,英文字符等都是一个字节,
VARCHAR2把空串等同于null处理,而varchar仍按照空串处理;
VARCHAR2字符要用几个字节存储,要看数据库使用的字符集,
大部分情况下建议使用VARCHAR2类型,可以保证更好的兼容性。

14.求一个数组中最大值的子集
输入一个整形数组,数组里有正数也有负数。数组中连续的一个或多个整数组成一个子数组,每个子数组都有一个和。求所有子数组的和的最大值。要求时间复杂度为O(n)。
例如输入的数组为1, -2, 3, 10, -4, 7, 2, -5,和最大的子数组为3, 10, -4, 7, 2,因此输出为该子数组的和18。
首先要找到使和增加的正的元素。然后有了当前最大的和后,纪录下来;继续累加求和,若新增的元素使和变为负数,那么重新置0,按这个逻辑找出剩余元素的一个最大子组和,若超过前纪录,覆盖,直到子组遍历结束。

public Integer getSubarraySum(int[] arr) {
if (arr == null || arr.length == 0) {
return null;
}
int curSum = 0;//求和指针
int maxSum = 0;//最大值记录
int start = 0;//记录子数组开始位置
int end = 0;//记录子数组结束位置
int len = arr.length;
for (int i = 0; i < len; i++) {
curSum += arr[i];
if (curSum < 0) {
curSum = 0;
start=i+1;
}
if (curSum > maxSum) {
maxSum = curSum;
end=i;
}
}
// 所有数据均为阴性
if (maxSum == 0) {
for (int i = 0; i < len; i++) {
if (i == 0) {
maxSum = arr[i];
}
if (arr[i] > maxSum) {
maxSum = arr[i];
start=end=i;
}
}
}
System.out.println("开始:"+start+"--结束:"+end);
return maxSum;
}

15.Spring MVC的工作流程

1).将 Web 页面中的输入元素封装为一个(请求)数据对象。
2).根据请求的不同,调度相应的逻辑处理单元,并将(请求)数据对象作为参数传入。
3).逻辑处理单元完成运算后,返回一个结果数据对象。
4).将结果数据对象中的数据与预先设计的表现层相融合并展现给用户。

16.java生成树形结构数据

如下数据生成树形结构

IDNAMEPID
100ANULL
110B100
120C100
111D110
112E110
121F120

//建立数据结构

public class Node {


//ID
private Integer id;
//名称
private String name;
//父类ID
private Integer pid; 
//孩子
private List<Node> children = new ArrayList<Node>();
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getPid() {
return pid;
}
public void setPid(Integer pid) {
this.pid = pid;
}
public List<Node> getChildren() {
return children;
}
public void setChildren(List<Node> children) {
this.children = children;
}

}


//创建递归方法生成树形数据

public class DataTree {


public Node getDateTree(Integer id) {
// 根据ID查询出当前Node值
// select * from table t where t.ID = id;
Node node = getNode();
// 根据ID查询出所有孩子Node值
// select * from table t where t.PID = id;
List<Node> childrenNodes = getChildrenNodes();
// 遍历子节点
for (Node child : childrenNodes) {
Node n = getDateTree(child.getId()); // 递归
node.getChildren().add(n);//将子节点加入孩子List
}
return node;//返回树形的Node数据
}
}


17.java单向链表反转

//链表节点数据结构

public class Node {
private Node next; // 指针域
private Object data;// 数据域
public Node(Object data) {
this.data = data;
}
public Node getNext() {
return next;
}
public void setNext(Node next) {
this.next = next;
}
public Object getData() {
return data;
}
public void setData(int data) {
this.data = data;
}
}

//链式结构如下:


//通过两个临时Node节点(指针)完成反转

public void reversalLink() {
Node previousNode = null;//当前节点交换指针
Node nextNode = null;//下一节点指针
while (firstNode != null) {
// save the next node
nextNode = firstNode.getNext();
// update the value of "next"
firstNode.setNext(previousNode);
// shift the pointers
previousNode = firstNode;
firstNode = nextNode;
}
firstNode = previousNode;
}

18.创建线程安全的单实例

public class Singleton {
// 1、私有类类型的属性
private static Singleton instance;


// 2、私有的构造函数(不允许外部实例化)
private Singleton() {
};


// 3、提供一个实例化方法
public static Singleton getInstance() {
if (instance == null) {//如果对象为空
synchronized (Singleton.class) {//同步
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;//返回对象
}
}

19、反转一个int类型数据,如123转成321

//考虑正负、考虑位数,以32位为例
public int ReversalInt(int x){
if(x==(0x1<<31)){//超出32位,返回0
return 0;
}
//设置标志位区分正负
int flag = 1;//默认为正数
if(x<0){
flag=-1;
x=-x;
}
int Remainder=0;
int _x=0;
while(true){
Remainder=x%10;//取出最低位上的数字
_x=_x*10+Remainder;//依次的反转存储得到反转的数字
x=x/10;//出10取整
if(x==0){//如果为0跳出循环
break;
}
}
return _x*flag;
}

20、进程之间通讯的方式

管道(Pipe)、命名管道(named pipe)、信号(Signal)、消息(Message)队列、共享内存、套接口(Socket)

java开发常用的两种进程之间通讯是"共享变量"和"管道流"这两种方法

共享变量:

class Mythread implements Runnable {
int index = 0;
public synchronized void run() {
while (true)
System.out.println(Thread.currentThread().getName() + "is running and the index is " + index++);
}
}
public  class  Interfacaesharethread {
public  static  void  main(String[] args) {
Mythread mythread =  new  Mythread();
new  Thread(mythread).start();
new  Thread(mythread).start();
new  Thread(mythread).start();
new  Thread(mythread).start();
}
}
管道流:

public  class  CommunicateWhitPiping {
public  static  void  main(String[] args) {
//创建管道输出流
PipedOutputStream pos =  new  PipedOutputStream();
//创建管道输入流
PipedInputStream pis =  new  PipedInputStream();
try  {
// 将管道输入流与输出流连接
pos.connect(pis);
catch  (IOException e) {
e.printStackTrace();
}
//创建生产者线程
Producer p =  new  Producer(pos);
p.start();
// 创建消费者线程
Consumer c =  new   Consumer(pis);
c.start();
}
}
/**
* 生产者线程(与一个管道输入流相关联)
*
*/
class  Producer  extends  Thread {
private  PipedOutputStream pos;
public  Producer(PipedOutputStream pos) {
this .pos = pos;
}
public  void  run() {
int  i =  8 ;
try  {
pos.write(i);
catch  (IOException e) {
e.printStackTrace();
}
}
}
/**
* 消费者线程(与一个管道输入流相关联)
*
*/
class  Consumer  extends  Thread {
private  PipedInputStream pis;
public  Consumer(PipedInputStream pis) {
this .pis = pis;
}
public  void  run() {
try  {
System.out.println(pis.read());
catch  (IOException e) {
e.printStackTrace();
}
}
}
21、TCP和IP协议分别处于网络的哪一层?
TCP:传输层 IP:网络层

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值