Java面试总结

1、同步异步区别

同步:发送一个请求,等待返回,然后再发送下一个请求 。

异步:发送一个请求,不等待返回,随时可以再发送下一个请求 。

并发:同时发送多个请求

同步可以避免出现死锁,一般共享某一资源的时候用,如果每个人都有修改权限,同时修改一个文件,有可能使一个人读取另一个人已经删除的内容,就会出错,同步就会按顺序来修改。
异步则是可以提高效率了,现在cpu都是双核,四核,异步处理的话可以同时做多项工作,当然必须保证是可以并发处理的。

2、线程和进程区别

进程是程序的一次执行,而线程可以理解为进程中执行的一段程序片段。进程间是独立的,而线程运行在进程空间中。

3、线程的5种状态

新建、就绪、运行、阻塞、死亡

4、servlet的九大内置对象

1)request   封装了用户的提交信息,该对象调用相应的方法获取用户提交参数信息。

2)response 对客户端的请求做出响应,然后向客户端发送数据。

3)session 从浏览器的开启到关闭称为一个会话。session可以保存用户的登录信息。

4)application 服务器启动的时候创建此对象,所有客户共享这个application对象,直到服务器关闭。

5)out 输出对象 用来向客户端输出数据。

6)page 当前jsp页面本身。

7)config 该实例代表jsp的配置信息。

8)exception 处理界面中出现的异常信息。

9)pagecontext  代表当前页面的常用属性,通过他获取其他的8大内置对象。

5、Ajax请求

 $.ajax({  

                    type : "POST",  //提交方式  
                    url : "${pageContext.request.contextPath}/org/doDelete.action",//路径  
                    data : {  
                        "org.id" : "${org.id}"  
                    },//数据,这里使用的是Json格式进行传输  
                    success : function(result) {//返回数据根据结果进行相应的处理  
                        if ( result.success ) {  
                            $("#tipMsg").text("删除数据成功");  
                           tree.deleteItem("${org.id}", true);  
                        } else {  
                            $("#tipMsg").text("删除数据失败");  
                        }  
                    }  
                });  

6、跨域

7、数据结构

List:有序的可重复的。

实现方式ArrayList和LinkedList 区别

不同点:ArrayList数据结构是基于数组实现的,数据的查询速度比较快,不适合数据的插入和删除操作,因为需要移动元素。

             LinkedList的数据结构是基于双向链表实现的,适合数据的插入和删除操作,不适合数据的查询操作。

相同点:两者都是线程不安全的,对于ArrayList集合如果两个线程同时往集合中添加数据 可能出现数组越界异常和null的情况。

             对于LinkedList集合如果多个线程同时往某个节点添加数据的时候、再去查询可能查到的数据不是当前线程插入的数据元素。         

            解决方法就是使用线程安全的集合(vector)或者使用同步代码块。

Set:无序的不可重复的。

实现方式HashSet和LinkHashSet 都是线性不安全的。

Map:键是唯一的。

实现方式 HashMap和HashTable 

区别:HashTable是线性安全的,HashMap是线性不安全的。对于HashMap可以使用currentHashMap替换HashMap

8、String、StringBuilder和StringBuffer三者的区别

String是被final修饰的,不能被继承。其成员属性也是final修饰。

String的常用方法 substring、split、replace、format、contract、equals、indexof

“对String对象的任何改变都不影响到原对象,相关的任何change操作都会生成新的对象”。(String是不可变的对象)

1)String str="hello world"和String str=new String("hello world")的区别

public class Main {
         
    public static void main(String[] args) {
        String str1 = "hello world";
        String str2 = new String("hello world");
        String str3 = "hello world";
        String str4 = new String("hello world");
         
        System.out.println(str1==str2);
        System.out.println(str1==str3);
        System.out.println(str2==str4);
    }
}

这段代码的输出结果为

false

true

false

String str1 = "hello world";和String str3 = "hello world"; 都在编译期间生成了字面常量和符号引用,运行期间字面常量"hello world"被存储在运行时常量池(当然只保存了一份)。JVM执行引擎会先在运行时常量池查找是否存在相同的字面常量,如果存在,则直接将引用指向已经存在的字面常量;否则在运行时常量池开辟一个空间来存储该字面常量,并将引用指向该字面常量。

通过new关键字来生成对象是在堆区进行的,而在堆区进行对象生成的过程是不会去检测该对象是否已经存在的。因此通过new来创建对象,创建出的一定是不同的对象,即使字符串的内容是相同的。

2)StringBuffer是线性安全的可变对象,可以使用append方法进行字符串的追加。

3)Stringbuilder是线性不安全的可变对象,同样可以使用append的方法进行字符串的追加。如果不是多线程操作的情况下可以使用Stringbuilder.

效率上Stringbuilder>StringBuffer>String

三者使用的场景:

当字符串相加操作或者改动较少的情况下,建议使用 String str="hello"这种形式;

当字符串相加操作较多的情况下,建议使用StringBuilder,如果采用了多线程,则使用StringBuffer。

1. 下面这段代码的输出结果是什么?

  String a = "hello2";   String b = "hello" + 2;   System.out.println((a == b));

  输出结果为:true。原因很简单,"hello"+2在编译期间就已经被优化成"hello2",因此在运行期间,变量a和变量b指向的是同一个对象。

2.下面这段代码的输出结果是什么?

  String a = "hello2";    String b = "hello";       String c = b + 2;       System.out.println((a == c));

  输出结果为:false。由于有符号引用的存在,所以  String c = b + 2;不会在编译期间被优化,不会把b+2当做字面常量来处理的,因此这种方式生成的对象事实上是保存在堆上的。因此a和c指向的并不是同一个对象

3.下面这段代码的输出结果是什么?

  String a = "hello2";     final String b = "hello";       String c = b + 2;       System.out.println((a == c));

  输出结果为:true。对于被final修饰的变量,会在class文件常量池中保存一个副本,也就是说不会通过连接而进行访问,对final变量的访问在编译期间都会直接被替代为真实的值。那么String c = b + 2;在编译期间就会被优化成:String c = "hello" + 2;

4.下面这段代码输出结果为:

1
2
3
4
5
6
7
8
9
10
11
12
public class Main {
    public static void main(String[] args) {
        String a = "hello2";
        final String b = getHello();
        String c = b + 2;
        System.out.println((a == c));
    }
     
    public static String getHello() {
        return "hello";
    }
}

  输出结果为false。这里面虽然将b用final修饰了,但是由于其赋值是通过方法调用返回的,那么它的值只能在运行期间确定,因此a和c指向的不是同一个对象。

5.String str = new String("abc")创建了多少个对象?

该段代码执行过程和类的加载过程是有区别的。在类加载的过程中,确实在运行时常量池中创建了一个"abc"对象,而在代码执行过程中确实只创建了一个String对象。

6.下面这段代码1)和2)的区别是什么?

1
2
3
4
5
6
7
8
public class Main {
    public static void main(String[] args) {
        String str1 = "I";
        //str1 += "love"+"java";        1)
        str1 = str1+"love"+"java";      //2)
         
    }
}

  1)的效率比2)的效率要高,1)中的"love"+"java"在编译期间会被优化成"lovejava",而2)中的不会被优化

值传递和引用传递

1、按值传递:值传递是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。简单来说就是直接复制了一份数据过去,因为是直接复制,所以这种方式在传递时如果数据量非常大的话,运行效率自然就变低了,所以java在传递数据量很小的数据是值传递,比如java中的各种基本类型:int,float,double,boolean等类型的,具体可以自己测试。

结果:

2、按引用传递引用传递其实就弥补了上面说的不足,如果每次传参数的时候都复制一份的话,如果这个参数占用的内存空间太大的话,运行效率会很底下,所以引用传递就是直接把内存地址传过去,也就是说引用传递时,操作的其实都是源数据,这样的话修改有时候会冲突,记得用逻辑弥补下就好了,具体的数据类型就比较多了,比如Object,二维数组,List,Map等除了基本类型的参数都是引用传递。代码:

有些文章中写的是java中所有的传参方式都是按值传递,这也说得通,无非就是文字游戏,因为无论是按值传递还是按引用传递都是把值传递过去了,所以就叫按值传递。

接口和抽象类区别

1)接口中的方法都是抽象的方法,属性都是常量,接口可以多实现,实现接口后必须实现其抽象方法。

2)抽象类中的方法可以是抽象的也可以是非抽象的,抽象类不能实例化,子类继承抽象父类后可以实现其抽象方法,这样的话子类就可以进行实例化,否则子类仍然是抽象类。子类可以重写父类的非抽象方法。

3)抽象类可以包含构造函数,其目的不是为了构造对象,而是为了子类进行初始化。

4) 抽象类中不能出现final、private、static等关键字。

5)接口是多实现的,而类是单继承的。 

多线程操作


常用的设计模式

单例模式

/**  
 * 方法四
 * 单例模式最优方案
 * 线程安全  并且效率高  
 *  
 */  
public class SingletonTest { 

    // 定义一个私有构造方法
    private SingletonTest() { 
     
    }   
    //定义一个静态私有变量(不初始化,不使用final关键字,使用volatile保证了多线程访问时instance变量的可见性,避免了instance初始化时其他变量属性还没赋值完时,被另外线程调用)
    private static volatile SingletonTest instance;  

    //定义一个共有的静态方法,返回该类型实例
    public static SingletonTest getIstance() { 
        // 对象实例化时与否判断(不使用同步代码块,instance不等于null时,直接返回对象,提高运行效率)
        if (instance == null) {
            //同步代码块(对象未初始化时,使用同步代码块,保证多线程访问时对象在第一次创建后,不再重复被创建)
            synchronized (SingletonTest.class) {
                //未初始化,则初始instance变量
                if (instance == null) {
                    instance = new SingletonTest();   
                }   
            }   
        }   
        return instance;   
    }   
}

工厂设计模式

工厂模式是我们最常用的实例化对象模式了,是用工厂方法代替new操作的一种模式。

一个栗子: 
我喜欢吃面条,抽象一个面条基类,(接口也可以),这是产品的抽象类

public abstract class INoodles {
    /**
     * 描述每种面条啥样的
     */
    public abstract void desc();
}

工厂如下:

public class MulWayNoodlesFactory {

    /**
     * 模仿Executors 类
     * 生产泡面
     *
     * @return
     */
    public static INoodles createPm() {
        return new PaoNoodles();
    }

    /**
     * 模仿Executors 类
     * 生产兰州拉面
     *
     * @return
     */
    public static INoodles createLz() {
        return new LzNoodles();
    }

    /**
     * 模仿Executors 类
     * 生产干扣面
     *
     * @return
     */
    public static INoodles createGk() {
        return new GankouNoodles();
    }
}

使用时:

        /**
         * 多方法静态工厂(模仿Executor类)
         */
        System.out.println("==============================模仿Executor类==============================" +
                "\n 这种我比较青睐,增加一个新面条,只要去增加一个static方法即可,也不修改原方法逻辑");
        INoodles lz2 = MulWayNoodlesFactory.createLz();
        lz2.desc();

        INoodles gk2 = MulWayNoodlesFactory.createGk();
        gk2.desc();

面向切面编程

将业务逻辑代码和繁琐事务代码(日志的处理、事务的开始、事务的提交)分开,将精力主要放在业务逻辑的处理上。最后将繁琐的代码动态的植入到程序中。

依赖注入(控制反转)

依赖注入和控制反转是同一个概念,当我们在一个类中需要另一个类的实例的时候,通常我们会new 这个类的实例,在Spring中创建被调用者的实例由Spring容器来完成,然后注入到调用者,这就是依赖注入。

drop truncate delete区别
drop table stu; 删除表,释放空间
truncate table stu; 保留表的结构,删除数据,释放空间,不能恢复,速度快
delete from stu 删除数据,可以恢复,速度慢


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值