Java 基础知识点

Java 基础篇

1、一个”.java”源文件中是否可以包括多个类(不是内部类)?有什么限制?

可以有多个类,但只能有一个public的类,并且public的类名必须与文件名相一致。

2、Java有没有goto?

java中的保留字,现在没有在java中使用。

3、说说&和&&的区别。

&和&&都可以用作逻辑与的运算符,表示逻辑与(and),当运算符两边的表达式的结果都为true时,整个运算结果才为true,否则,只要有一方为false,则结果为false。

&&还具有短路的功能,即如果第一个表达式为false,则不再计算第二个表达式,例如,对于if(str!= null&& !str.equals(s))表达式,当str为null时,后面的表达式不会执行,所以不会出现NullPointerException如果将&&改为&,则会抛出NullPointerException异常。If(x==33 &++y>0) y会增长,If(x==33 && ++y>0)不会增长

&还可以用作位运算符,当&操作符两边的表达式不是boolean类型时,&表示按位与操作,我们通常使用0x0f来与一个整数进行&运算,来获取该整数的最低4个bit位,例如,0x31 & 0x0f的结果为0x01。

4、在JAVA中如何跳出当前的多重嵌套循环?

在Java中,要想跳出多重循环,可以在外面的循环语句前定义一个标号,然后在里层循环体的代码中使用带有标号的break语句,即可跳出外层循环。

例如:

for(int i=0;i<10;i++){
   for(intj=0;j<10;j++){
       System.out.println(“i=” + i + “,j=” + j);
       if(j == 5) break ok;
   }
}

另外,我个人通常并不使用标号这种方式,而是让外层的循环条件表达式的结果可以受到里层循环体代码的控制,例如,要在二维数组中查找到某个数字。

int arr[][] ={{1,2,3},{4,5,6,7},{9}};

boolean found = false;

for(int i=0;i<arr.length&&!found;i++)       {

    for(intj=0;j<arr[i].length;j++){

        System.out.println(“i=” + i + “,j=” + j);

        if(arr[i][j] ==5) {

            found =true;

            break;

        }

    }

}

5、switch语句能否作用在byte上,能否作用在long上,能否作用在String上?

在switch(e)中,e只能是一个整数表达式或者枚举常量(更大字体),整数表达式可以是int基本类型或Integer包装类型,由于byte,short,char都可以隐含转换为int,所以,这些类型以及这些类型的包装类型也是可以的。显然,long和String类型都不符合switch的语法规定,并且不能被隐式转换成int类型,所以,它们不能作用于swtich语句中。

switch语句能否作用在String上说错了,Java1.7之后已经支持这种写法了!

6、short s1= 1; s1 = (s1+1是int类型,而等号左边的是short类型,所以需要强转)1 + 1;有什么错? short s1 = 1; s1 += 1;有什么错?(没有错)

对于short s1= 1; s1 = s1 + 1;由于s1+1运算时会自动提升表达式的类型,所以结果是int型,再赋值给short类型s1时,编译器将报告需要强制转换类型的错误。

对于short s1= 1; s1 += 1;由于 +=是java语言规定的运算符,java编译器会对它进行特殊处理,因此可以正确编译。

7、char型变量中能不能存贮一个中文汉字?为什么?

char型变量是用来存储Unicode编码的字符的,unicode编码字符集中包含了汉字,所以,char型变量中当然可以存储汉字啦。不过,如果某个特殊的汉字没有被包含在unicode编码字符集中,那么,这个char型变量中就不能存储这个特殊汉字。补充说明:unicode编码占用两个字节,所以,char类型的变量也是占用两个字节。

8、用最有效率的方法算出2乘以8等于几?

2<< 3,(左移三位)因为将一个数左移n位,就相当于乘以了2的n次方,那么,一个数乘以8只要将其左移3位即可,而位运算cpu直接支持的,效率最高,所以,2乘以8等於几的最效率的方法是2<< 3。

9、使用final关键字修饰一个变量时,是引用不能变,还是引用的对象不能变?

使用final关键字修饰一个变量时,是指引用变量不能变,引用变量所指向的对象中的内容还是可以改变的。例如,对于如下语句:

finalStringBuffer a=new StringBuffer("immutable");

执行如下语句将报告编译期错误:

a=new StringBuffer("");

但是,执行如下语句则可以通过编译:

a.append(" broken!");

有人在定义方法的参数时,可能想采用如下形式来阻止方法内部修改传进来的参数对象:

public void method(final  StringBuffer param){

}

实际上,这是办不到的,在该方法内部仍然可以增加如下代码来修改参数对象:

param.append("a");

10,静态变量和实例变量的区别?

在语法定义上的区别:静态变量前要加 static 关键字,而实例变量前则不加。

在程序运行时的区别:实例变量属于某个对象的属性,必须创建了实例对象,其中的实例变量才会被分配空间,才能使用这个实例变量。静态变量不属于某个实例对象,而是属于类,所以也称为类变量,只要程序加载了类的字节码,不用创建任何实例对象,静态变量就会被分配空间,静态变量就可以被使用了。总之,实例变量必须创建对象后才可以通过这个对象来使用,静态变量则可以直接使用类名来引用。

例如,对于下面的程序,无论创建多少个实例对象,永远都只分配了一个staticVar变量,并且每创建一个实例对象,这个staticVar就会加1;但是,每创建一个实例对象,就会分配一个instanceVar,即可能分配多个instanceVar,并且每个instanceVar的值都只自加了1次。

public class VariantTest{

    public static int staticVar = 0;

    public int instanceVar = 0;

    publicVariantTest(){

          staticVar++;

          instanceVar++;

          System.out.println(staticVar +instanceVar);

    }

}

11、是否可以从一个 static 方法内部发出对非 static 方法的调用?

不可以。因为非static方法是要与对象关联在一起的,必须创建一个对象后,才可以在该对象上进行方法调用,而 static 方法调用时不需要创建对象,可以直接调用。也就是说,当一个 static 方法被调用时,可能还没有创建任何实例对象,如果从一个 static 方法中发出对非 static 方法的调用,那个非 static 方法是关联到哪个对象上的呢?这个逻辑无法成立,所以,一个 static 方法内部发出对非 static 方法的调用。

12、Integer与int的区别

int 是 java 提供的 8 种原始数据类型之一。Java 为每个原始类型提供了封装类,Integer 是 java 为 int 提供的封装类。int 的默认值为0,而 Integer 的默认值为 null,即 Integer 可以区分出未赋值和值为0的区别,int 则无法表达出未赋值的情况。

例如:要想表达出没有参加考试和考试成绩为 0 的区别,则只能使用 Integer。在 JSP 开发中,Integer 的默认为 null,所以用 el 表达式在文本框中显示时,值为空白字符串,而 int 默认的默认值为 0,所以用 el 表达式在文本框中显示时,结果为 0,所以,int 不适合作为 web 层的表单数据的类型。

在 Hibernate 中,如果将 OID 定义为 Integer 类型,那么 Hibernate 就可以根据其值是否为 null 而判断一个对象是否是临时的,如果将OID 定义为了 int 类型,还需要在 hbm 映射文件中设置其 unsaved-value 属性为 0。

另外,Integer 提供了多个与整数相关的操作方法,例如,将一个字符串转换成整数,Integer 中还定义了表示整数的最大值和最小值的常量。

13、Math.round(11.5)等於多少?Math.round(-11.5)等於多少?

Math类中提供了三个与取整有关的方法:ceil、floor、round,这些方法的作用与它们的英文名称的含义相对应。

例如,ceil 的英文意义是天花板,该方法就表示向上取整,Math.ceil(11.3) 的结果为12,Math.ceil(-11.3) 的结果是-11;floor 的英文意义是地板,该方法就表示向下取整,Math.floor(11.6) 的结果为 11,Math.floor(-11.6) 的结果是-12;最难掌握的是round方法,它表示“四舍五入”,算法为Math.floor(x+0.5),即将原来的数字加上0.5后再向下取整,所以,Math.round(11.5)的结果为12,Math.round(-11.5)的结果为-11。

14、Overload 和 Override 的区别?Overloaded 的方法是否可以改变返回值的类型?

Overload 是重载的意思,Override 是覆盖的意思,也就是重写。

重载 Overload 表示同一个类中可以有多个名称相同的方法,但这些方法的参数列表各不相同(即参数个数或类型不同)。

重写 Override 表示子类中的方法可以与父类中的某个方法的名称和参数完全相同,通过子类创建的实例对象调用这个方法时,将调用子类中的定义方法,这相当于把父类中定义的那个完全相同的方法给覆盖了,这也是面向对象编程的多态性的一种表现。子类覆盖父类的方法时,只能比父类抛出更少的异常,或者是抛出父类抛出的异常的子异常,因为子类可以解决父类的一些问题,不能比父类有更多的问题。子类方法的访问权限只能比父类的更大,不能更小。如果父类的方法是 private 类型,那么,子类则不存在覆盖的限制,相当于子类中增加了一个全新的方法。

至于 Overloaded 的方法是否可以改变返回值的类型这个问题,要看你倒底想问什么呢?这个题目很模糊。如果几个 Overloaded 的方法的参数列表不一样,它们的返回者类型当然也可以不一样。但我估计你想问的问题是:如果两个方法的参数列表完全一样,是否可以让它们的返回值不同来实现重载 Overload。这是不行的,我们可以用反证法来说明这个问题,因为我们有时候调用一个方法时也可以不定义返回结果变量,即不要关心其返回结果,例如,我们调用 map.remove(key) 方法时,虽然 remove 方法有返回值,但是我们通常都不会定义接收返回结果的变量,这时候假设该类中有两个名称和参数列表完全相同的方法,仅仅是返回类型不同,java 就无法确定编程者倒底是想调用哪个方法了,因为它无法通过返回结果类型来判断。

override 可以翻译为覆盖,从字面就可以知道,它是覆盖了一个方法并且对其重写,以求达到不同的作用。对我们来说最熟悉的覆盖就是对接口方法的实现,在接口中一般只是对方法进行了声明,而我们在实现时,就需要实现接口声明的所有方法。除了这个典型的用法以外,我们在继承中也可能会在子类覆盖父类中的方法。在覆盖要注意以下的几点:

  • 1、覆盖的方法的标志必须要和被覆盖的方法的标志完全匹配,才能达到覆盖的效果;

  • 2、覆盖的方法的返回值必须和被覆盖的方法的返回一致;

  • 3、覆盖的方法所抛出的异常必须和被覆盖方法的所抛出的异常一致,或者是其子类;

  • 4、被覆盖的方法不能为private,否则在其子类中只是新定义了一个方法,并没有对其进行覆盖。

Overload 对我们来说可能比较熟悉,可以翻译为重载,它是指我们可以定义一些名称相同的方法,通过定义不同的输入参数来区分这些方法,然后再调用时,VM 就会根据不同的参数样式,来选择合适的方法执行。在使用重载要注意以下的几点:

  • 1、在使用重载时只能通过不同的参数样式。例如,不同的参数类型,不同的参数个数,不同的参数顺序(当然,同一方法内的几个参数类型必须不一样,例如可以是 fun(int,float),但是不能为 fun(int,int));

  • 2、不能通过访问权限、返回类型、抛出的异常进行重载;

  • 3、方法的异常类型和数目不会对重载造成影响;

  • 4、对于继承来说,如果某一方法在父类中是访问权限是 priavte,那么就不能在子类对其进行重载,如果定义的话,也只是定义了一个新方法,而不会达到重载的效果。

15、接口是否可继承接口?抽象类是否可实现 (implements) 接口?抽象类是否可继承具体类 (concreteclass) ?抽象类中是否可以有静态的main方法?

接口可以继承接口。抽象类可以实现(implements)接口,抽象类可以继承具体类。抽象类中可以有静态的main方法。

备注:只要明白了接口和抽象类的本质和作用,这些问题都很好回答,你想想,如果你是java语言的设计者,你是否会提供这样的支持,如果不提供的话,有什么理由吗?如果你没有道理不提供,那答案就是肯定的了。

只要记住抽象类与普通类的唯一区别就是不能创建实例对象和允许有abstract方法。

6、Java 中实现多态的机制是什么?

靠的是父类或接口定义的引用变量可以指向子类或具体实现类的实例对象,而程序调用的方法在运行期才动态绑定,就是引用变量所指向的具体实例对象的方法,也就是内存里正在运行的那个对象的方法,而不是引用变量的类型中定义的方法。

17、abstract class 和 interface 语法上有什么区别?

  • 1.抽象类可以有构造方法,接口中不能有构造方法。

  • 2.抽象类中可以有普通成员变量,接口中没有普通成员变量

  • 3.抽象类中可以包含非抽象的普通方法,接口中的所有方法必须都是抽象的,不能有非抽象的普通方法。

  • 4.抽象类中的抽象方法的访问类型可以是 public,protected 和(默认类型,虽然 eclipse 下不报错,但应该也不行),但接口中的抽象方法只能是 public 类型的,并且默认即为 public abstract 类型。

  • 5.抽象类中可以包含静态方法,接口中不能包含静态方法

  • 6.抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量的访问类型可以任意,但接口中定义的变量只能是 public static final类型,并且默认即为 public static final类型。

  • 7.一个类可以实现多个接口,但只能继承一个抽象类。

18、abstract 的 method 是否可同时是 static,是否可同时是 native,是否可同时是 synchronized?

abstract 的 method 不可以是 static 的,因为抽象的方法是要被子类实现的,而 static 与子类扯不上关系!

native 方法表示该方法要用另外一种依赖平台的编程语言实现的,不存在着被子类实现的问题,所以,它也不能是抽象的,不能与 abstract 混用。例如,FileOutputSteam 类要硬件打交道,底层的实现用的是操作系统相关的 api 实现;例如,在 windows 用 c 语言实现的,所以,查看jdk 的源代码,可以发现 FileOutputStream 的 open 方法的定义如下:

private native void open(Stringname) throwsFileNotFoundException;

如果我们要用 java 调用别人写的 c 语言函数,我们是无法直接调用的,我们需要按照 java 的要求写一个 c 语言的函数,由我们的这个 c 语言函数去调用别人的 c 语言函数。由于我们的 c 语言函数是按 java 的要求来写的,我们这个 c 语言函数就可以与 java 对接上,java 那边的对接方式就是定义出与我们这个 c 函数相对应的方法,java 中对应的方法不需要写具体的代码,但需要在前面声明 native。

关于 synchronized 与 abstract 合用的问题,我觉得也不行,因为在我几年的学习和开发中,从来没见到过这种情况,并且我觉得synchronized 应该是作用在一个具体的方法上才有意义。而且,方法上的 synchronized 同步所使用的同步锁对象是 this,而抽象方法上无法确定 this 是什么。

19、内部类可以引用它的包含类的成员吗?有没有什么限制?

完全可以。如果不是静态内部类,那没有什么限制!

如果你把静态嵌套类当作内部类的一种特例,那在这种情况下不可以访问外部类的普通成员变量,而只能访问外部类中的静态成员,例如,下面的代码:

class Outer{

    static int x;

    static class Inner{

        voidtest(){

            syso(x);

        }
    }
}

20、String s = “Hello”;s = s + “world!”;这两行代码执行后,原始的 String 对象中的内容到底变了没有?

没有。因为 String 被设计成不可变 (immutable) 类,所以它的所有对象都是不可变对象。在这段代码中,s 原先指向一个 String 对象,内容是 “Hello”,然后我们对 s 进行了+操作,那么 s 所指向的那个对象是否发生了改变呢?答案是没有。这时,s 不指向原来那个对象了,而指向了另一个 String 对象,内容为”Hello world!”,原来那个对象还存在于内存之中,只是 s 这个引用变量不再指向它了。

通过上面的说明,我们很容易导出另一个结论,如果经常对字符串进行各种各样的修改,或者说,不可预见的修改,那么使用 String 来代表字符串的话会引起很大的内存开销。因为 String 对象建立之后不能再改变,所以对于每一个不同的字符串,都需要一个 String 对象来表示。这时,应该考虑使用 StringBuffer 类,它允许修改,而不是每个不同的字符串都要生成一个新的对象。并且,这两种类的对象转换十分容易。

同时,我们还可以知道,如果要使用内容相同的字符串,不必每次都 new 一个 String。例如我们要在构造器中对一个名叫 s 的 String 引用变量进行初始化,把它设置为初始值,应当这样做:

public class Demo {
    private String s;
    ...
    public Demo {
        s = "Initial Value";
    }
    ...
}

而非

s = new String("Initial Value");

后者每次都会调用构造器,生成新对象,性能低下且内存开销大,并且没有意义,因为 String 对象不可改变,所以对于内容相同的字符串,只要一个 String 对象来表示就可以了。也就说,多次调用上面的构造器创建多个对象,他们的 String 类型属性 s 都指向同一个对象。

上面的结论还基于这样一个事实:对于字符串常量,如果内容相同,Java 认为它们代表同一个 String 对象。而用关键字 new 调用构造器,总是会创建一个新的对象,无论内容是否相同。

至于为什么要把 String 类设计成不可变类,是它的用途决定的。其实不只 String,很多 Java 标准类库中的类都是不可变的。在开发一个系统的时候,我们有时候也需要设计不可变类,来传递一组相关的值,这也是面向对象思想的体现。不可变类有一些优点,比如因为它的对象是只读的,所以多线程并发访问也不会有任何问题。当然也有一些缺点,比如每个不同的状态都要一个对象来代表,可能会造成性能上的问题。所以 Java 标准类库还提供了一个可变版本,即 StringBuffer。

21、ArrayList 和 Vector 的区别

这两个类都实现了 List 接口(List 接口继承了 Collection 接口),他们都是有序集合,即存储在这两个集合中的元素的位置都是有顺序的,相当于一种动态的数组,我们以后可以按位置索引号取出某个元素,并且其中的数据是允许重复的,这是与 HashSet 之类的集合的最大不同处,HashSet 之类的集合不可以按索引号去检索其中的元素,也不允许有重复的元素。

ArrayList 与 Vector 的区别主要包括两个方面:

  • (1)同步性:

Vector 是线程安全的,也就是说是它的方法之间是线程同步的,而 ArrayList 是线程序不安全的,它的方法之间是线程不同步的。如果只有一个线程会访问到集合,那最好是使用 ArrayList,因为它不考虑线程安全,效率会高些;如果有多个线程会访问到集合,那最好是使用 Vector,因为不需要我们自己再去考虑和编写线程安全的代码。

  • (2)数据增长:

ArrayList 与 Vector 都有一个初始的容量大小,当存储进它们里面的元素的个数超过了容量时,就需要增加 ArrayList 与 Vector 的存储空间,每次要增加存储空间时,不是只增加一个存储单元,而是增加多个存储单元,每次增加的存储单元的个数在内存空间利用与程序效率之间要取得一定的平衡。Vector 默认增长为原来两倍,而 ArrayList 的增长策略在文档中没有明确规定(从源代码看到的是增长为原来的1.5倍)。ArrayList 与 Vector 都可以设置初始的空间大小,Vector 还可以设置增长的空间大小,而 ArrayList 没有提供设置增长空间的方法。

总结:即 Vector 增长原来的一倍,ArrayList 增加原来的0.5倍。

22、HashMap 和 Hashtable 的区别

HashMap 是 Hashtable 的轻量级实现(非线程安全的实现),他们都完成了 Map 接口,主要区别在于 HashMap 允许空(null)键值(key),由于非线程安全,在只有一个线程访问的情况下,效率要高于 Hashtable。

HashMap 允许将 null 作为一个 entry 的 key 或者 value,而 Hashtable 不允许。

HashMap 把 Hashtable 的 contains 方法去掉了,改成 containsvalue 和 containsKey。因为 contains 方法容易让人引起误解。

Hashtable 继承自 Dictionary 类,而 HashMap 是 Java1.2 引进的 Map interface 的一个实现。

最大的不同是,Hashtable 的方法是 Synchronize 的,而 HashMap 不是,在多个线程访问 Hashtable 时,不需要自己为它的方法实现同步,而 HashMap 就必须为之提供同步。

就 HashMap 与 HashTable 主要从三方面来说。

  • 一.历史原因:Hashtable 是基于陈旧的 Dictionary 类的,HashMap 是 Java 1.2 引进的 Map 接口的一个实现

  • 二.同步性:Hashtable 是线程安全的,也就是说是同步的,而 HashMap 是线程序不安全的,不是同步的

  • 三.值:只有 HashMap 可以让你将空值作为一个表的条目的 key 或 value

23、List和 Map区别?

一个是存储单列数据的集合,另一个是存储键和值这样的双列数据的集合,List 中存储的数据是有顺序,并且允许重复;Map 中存储的数据是没有顺序的,其键是不能重复的,它的值是可以有重复的。

24、List,Set, Map是否继承自Collection接口?

List,Set 是,Map 不是.

25、List、Map、Set 三个接口,存取元素时,各有什么特点?

(这样的题比较考水平,两个方面的水平:一是要真正明白这些内容,二是要有较强的总结和表述能力。)

首先,List 与 Set 具有相似性,它们都是单列元素的集合,所以,它们有一个共同的父接口,叫 Collection。Set 里面不允许有重复的元素,即不能有两个相等(注意,不是仅仅是相同)的对象,即假设 Set 集合中有了一个 A 对象,现在我要向 Set 集合再存入一个 B 对象,但 B 对象与 A 对象 equals 相等,则 B 对象存储不进去,所以,Set 集合的 add 方法有一个 boolean 的返回值,当集合中没有某个元素,此时 add 方法可成功加入该元素时,则返回 true,当集合含有与某个元素 equals 相等的元素时,此时 add 方法无法加入该元素,返回结果为 false。Set 取元素时,不能细说要取第几个,只能以 Iterator 接口取得所有的元素,再逐一遍历各个元素。

List 表示有先后顺序的集合,注意,不是那种按年龄、按大小、按价格之类的排序。当我们多次调用 add(Obje) 方法时,每次加入的对象就像火车站买票有排队顺序一样,按先来后到的顺序排序。有时候,也可以插队,即调用 add(int index,Obj e) 方法,就可以指定当前对象在集合中的存放位置。一个对象可以被反复存储进 List 中,每调用一次 add 方法,这个对象就被插入进集合中一次,其实,并不是把这个对象本身存储进了集合中,而是在集合中用一个索引变量指向这个对象,当这个对象被 add 多次时,即相当于集合中有多个索引指向了这个对象。List 除了可以用Iterator 接口取得所有的元素,再逐一遍历各个元素之外,还可以调用 get(index i) 来明确说明取第几个。

Map 与 List 和 Set 不同,它是双列的集合,其中有 put 方法,定义如下:put(obj key,obj value),每次存储时,要存储一对 key/value,不能存储重复的 key,这个重复的规则也是按 equals 比较相等。取则可以根据key获得相应的 value,即 get(Object key) 返回值为key 所对应的 value。另外,也可以获得所有的 key 的结合,还可以获得所有的 value 的结合,还可以获得 key 和 value 组合成的Map.Entry 对象的集合。

List 以特定次序来持有元素,可有重复元素。Set 无法拥有重复元素,内部排序。Map 保存 key-value 值,value 可多值。

26、说出 ArrayList,Vector,LinkedList 的存储性能和特性

ArrayList 和 Vector 都是使用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入数据慢,Vector 由于使用了 synchronized 方法(线程安全),通常性能上较ArrayList 差。而 LinkedList 使用双向链表实现存储,按序号索引数据需要进行前向或后向遍历,索引就变慢了,但是插入数据时只需要记录本项的前后项即可,所以插入速度较快。

LinkedList 也是线程不安全的,LinkedList 提供了一些方法,使得 LinkedList 可以被当作堆栈和队列来使用。

27、去掉一个Vector集合中重复的元素

Vector newVector = new Vector();

for (int i=0;i<vector.size();i++){

    Object obj = vector.get(i);

        if(!newVector.contains(obj);

        newVector.add(obj); 
}

还有一种简单的方式,利用了 Set 不允许重复元素:

HashSetset = new HashSet(vector);

28、Collection 和 Collections 的区别。

Collection 是集合类的上级接口,继承他的接口主要有 Set 和 List.

Collections 是针对集合类的一个帮助类,他提供一系列静态方法实现对各种集合的搜索、排序、线程安全化等操作。

29、Set 里的元素是不能重复的,那么用什么方法来区分重复与否呢?是用 == 还是 equals() ?它们有何区别?

Set 里的元素是不能重复的,元素重复与否是使用 equals() 方法进行判断的。

== 和 equal 区别也是考烂了的题,这里说一下:

== 操作符专门用来比较两个变量的值是否相等,也就是用于比较变量所对应的内存中所存储的数值是否相同,要比较两个基本类型的数据或两个引用变量是否相等,只能用 == 操作符。

equals 方法是用于比较两个独立对象的内容是否相同,就好比去比较两个人的长相是否相同,它比较的两个对象是独立的。

比如:两条 new 语句创建了两个对象,然后用 a/b 这两个变量分别指向了其中一个对象,这是两个不同的对象,它们的首地址是不同的,即 a 和 b 中存储的数值是不相同的,所以,表达式 a==b 将返回 false,而这两个对象中的内容是相同的,所以,表达式 a.equals(b) 将返回 true。

30、你所知道的集合类都有哪些?主要方法?

最常用的集合类是 List 和 Map。 List 的具体实现包括 ArrayList 和 Vector,它们是可变大小的列表,比较适合构建、存储和操作任何类型对象的元素列表。 List 适用于按数值索引访问元素的情形。

Map 提供了一个更通用的元素存储方法。 Map 集合类用于存储元素对(称作”键”和”值”),其中每个键映射到一个值。

它们都有增删改查的方法。

对于 set,大概的方法是 add,remove, contains 等

对于 map,大概的方法就是 put,remove,contains 等

List 类会有 get(int index) 这样的方法,因为它可以按顺序取元素,而 set 类中没有 get(int index) 这样的方法。List 和 set 都可以迭代出所有元素,迭代时先要得到一个 iterator 对象,所以,set 和 list 类都有一个 iterator 方法,用于返回那个 iterator 对象。map 可以返回三个集合,一个是返回所有的 key 的集合,另外一个返回的是所有 value 的集合,再一个返回的 key 和 value 组合成的 EntrySet 对象的集合,map 也有 get 方法,参数是 key,返回值是 key 对应的 value,这个自由发挥,也不是考记方法的能力,这些编程过程中会有提示,结合他们三者的不同说一下用法就行。

31、String s = new String(“xyz”);创建了几个StringObject?是否可以继承String类?

两个或一个都有可能,”xyz”对应一个对象,这个对象放在字符串常量缓冲区,常量”xyz”不管出现多少遍,都是缓冲区中的那一个。New String 每写一遍,就创建一个新的对象,它使用常量”xyz”对象的内容来创建出一个新 String 对象。如果以前就用过’xyz’,那么这里就不会创建”xyz”了,直接从缓冲区拿,这时创建了一个 StringObject;但如果以前没有用过”xyz”,那么此时就会创建一个对象并放入缓冲区,这种情况它创建两个对象。至于 String 类是否继承,答案是否定的,因为 String 默认 final 修饰,是不可继承的。

32、String 和 StringBuffer 的区别

JAVA 平台提供了两个类:String 和 StringBuffer,它们可以储存和操作字符串,即包含多个字符的字符数据。这个 String 类提供了数值不可改变的字符串。而这个 StringBuffer 类提供的字符串可以进行修改。当你知道字符数据要改变的时候你就可以使用 StringBuffer。典型地,你可以使用 StringBuffers 来动态构造字符数据。

33、下面这条语句一共创建了多少个对象:String s=”a”+”b”+”c”+”d”;

对于如下代码:

String s1 = "a";

String s2 = s1 + "b";

String s3 = "a" + "b";

System.out.println(s2 == "ab");

System.out.println(s3 == "ab");

第一条语句打印的结果为 false,第二条语句打印的结果为 true,这说明 javac 编译可以对字符串常量直接相加的表达式进行优化,不必要等到运行期再去进行加法运算处理,而是在编译时去掉其中的加号,直接将其编译成一个这些常量相连的结果。

题目中的第一行代码被编译器在编译时优化后,相当于直接定义了一个”abcd”的字符串,所以,上面的代码应该只创建了一个 String 对象。写如下两行代码,

String s ="a" + "b" +"c" + "d";

System.out.println(s== "abcd");

最终打印的结果应该为 true。

34、try {} 里有一个 return 语句,那么紧跟在这个 try 后的 finally{} 里的 code 会不会被执行,什么时候被执行,在 return 前还是后?

我们知道 finally{} 中的语句是一定会执行的,那么这个可能正常脱口而出就是 return 之前,return之后可能就出了这个方法了,鬼知道跑哪里去了,但更准确的应该是在 return 中间执行,请看下面程序代码的运行结果:

public class Test {

    public static void main(String[] args) {

        System.out.println(new Test().test());;

    }

    static int test(){

        int x = 1;

        try {

            return x;

        } finally {

            ++x;

        }

    }
}



---------执行结果 ---------

1

运行结果是1,为什么呢?主函数调用子函数并得到结果的过程,好比主函数准备一个空罐子,当子函数要返回结果时,先把结果放在罐子里,然后再将程序逻辑返回到主函数。所谓返回,就是子函数说,我不运行了,你主函数继续运行吧,这没什么结果可言,结果是在说这话之前放进罐子里的。

35、final, finally, finalize 的区别。

final 用于声明属性,方法和类,分别表示属性不可变,方法不可覆盖,类不可继承。内部类要访问局部变量,局部变量必须定义成 final 类型。

finally 是异常处理语句结构的一部分,表示总是执行。

finalize 是 Object 类的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法,可以覆盖此方法提供垃圾收集时的其他资源回收,例如关闭文件等。但是 JVM 不保证此方法总被调用

36、运行时异常与一般异常有何异同?

异常表示程序运行过程中可能出现的非正常状态,运行时异常表示虚拟机的通常操作中可能遇到的异常,是一种常见运行错误。java 编译器要求方法必须声明抛出可能发生的非运行时异常,但是并不要求必须声明抛出未被捕获的运行时异常。

37、error 和 exception 有什么区别?

error 表示恢复不是不可能但很困难的情况下的一种严重问题。比如说内存溢出。不可能指望程序能处理这样的情况。exception 表示一种设计或实现问题。也就是说,它表示如果程序运行正常,从不会发生的情况。

38、简单说说 Java 中的异常处理机制的简单原理和应用。

异常是指 java 程序运行时(非编译)所发生的非正常情况或错误,与现实生活中的事件很相似,现实生活中的事件可以包含事件发生的时间、地点、人物、情节等信息,可以用一个对象来表示,Java 使用面向对象的方式来处理异常,它把程序中发生的每个异常也都分别封装到一个对象来表示的,该对象中包含有异常的信息。

Java 对异常进行了分类,不同类型的异常分别用不同的 Java 类表示,所有异常的根类为 java.lang.Throwable,Throwable 下面又派生了两个子类:

Error 和 Exception,Error 表示应用程序本身无法克服和恢复的一种严重问题,程序只有奔溃了,例如,说内存溢出和线程死锁等系统问题。

Exception 表示程序还能够克服和恢复的问题,其中又分为系统异常和普通异常:

系统异常是软件本身缺陷所导致的问题,也就是软件开发人员考虑不周所导致的问题,软件使用者无法克服和恢复这种问题,但在这种问题下还可以让软件系统继续运行或者让软件挂掉,例如,数组脚本越界(ArrayIndexOutOfBoundsException),空指针异常(NullPointerException)、类转换异常(ClassCastException);

普通异常是运行环境的变化或异常所导致的问题,是用户能够克服的问题,例如,网络断线,硬盘空间不够,发生这样的异常后,程序不应该死掉。

java 为系统异常和普通异常提供了不同的解决方案,编译器强制普通异常必须 try..catch 处理或用 throws 声明继续抛给上层调用方法处理,所以普通异常也称为 checked 异常,而系统异常可以处理也可以不处理,所以,编译器不强制用 try..catch 处理或用 throws 声明,所以系统异常也称为 unchecked 异常。

39、Java 中堆和栈有什么区别?

JVM 中堆和栈属于不同的内存区域,使用目的也不同。栈常用于保存方法帧和局部变量,而对象总是在堆上分配。栈通常都比堆小,也不会在多个线程之间共享,而堆被整个 JVM 的所有线程共享。

栈:在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配,当在一段代码块定义一个变量时,Java 就在栈中为这个变量分配内存空间,当超过变量的作用域后,Java 会自动释放掉为该变量分配的内存空间,该内存空间可以立即被另作它用。

堆:堆内存用来存放由 new 创建的对象和数组,在堆中分配的内存,由 Java 虚拟机的自动垃圾回收器来管理。在堆中产生了一个数组或者对象之后,还可以在栈中定义一个特殊的变量,让栈中的这个变量的取值等于数组或对象在堆内存中的首地址,栈中的这个变量就成了数组或对象的引用变量,以后就可以在程序中使用栈中的引用变量来访问堆中的数组或者对象,引用变量就相当于是为数组或者对象起的一个名称。

40、能将 int 强制转换为 byte 类型的变量吗?如果该值大于 byte 类型的范围,将会出现什么现象?

我们可以做强制转换,但是 Java 中 int 是 32 位的,而 byte 是 8 位的,所以,如果强制转化,int 类型的高 24 位将会被丢弃,因为 byte 类型的范围是从 -128 到 127。

41、a.hashCode() 有什么用?与 a.equals(b) 有什么关系?

hashCode() 方法对应对象整型的 hash 值。它常用于基于 hash 的集合类,如 Hashtable、HashMap、LinkedHashMap 等等。它与 equals() 方法关系特别紧密。根据 Java 规范,两个使用 equal() 方法来判断相等的对象,必须具有相同的 hash code。

42、字节流与字符流的区别

要把一段二进制数据数据逐一输出到某个设备中,或者从某个设备中逐一读取一段二进制数据,不管输入输出设备是什么,我们要用统一的方式来完成这些操作,用一种抽象的方式进行描述,这个抽象描述方式起名为 IO 流,对应的抽象类为 OutputStream 和 InputStream,不同的实现类就代表不同的输入和输出设备,它们都是针对字节进行操作的。

计算机中的一切最终都是二进制的字节形式存在。对于经常用到的中文字符,首先要得到其对应的字节,然后将字节写入到输出流。读取时,首先读到的是字节,可是我们要把它显示为字符,我们需要将字节转换成字符。由于这样的需求很广泛,Java 专门提供了字符流包装类。

底层设备永远只接受字节数据,有时候要写字符串到底层设备,需要将字符串转成字节再进行写入。字符流是字节流的包装,字符流则是直接接受字符串,它内部将串转成字节,再写入底层设备,这为我们向 IO 设备写入或读取字符串提供了一点点方便。

字符向字节转换时,要注意编码的问题,因为字符串转成字节数组,其实是转成该字符的某种编码的字节形式,读取也是反之的道理。

43、什么是 java 序列化,如何实现 java 序列化?或者请解释 Serializable 接口的作用。

我们有时候将一个 java 对象变成字节流的形式传出去或者从一个字节流中恢复成一个 java 对象,例如,要将 java 对象存储到硬盘或者传送给网络上的其他计算机,这个过程我们可以自己写代码去把一个 java 对象变成某个格式的字节流再传输。

但是,jre 本身就提供了这种支持,我们可以调用 OutputStream 的 writeObject 方法来做,如果要让 java 帮我们做,要被传输的对象必须实现 serializable 接口,这样,javac 编译时就会进行特殊处理,编译的类才可以被 writeObject 方法操作,这就是所谓的序列化。需要被序列化的类必须实现 Serializable 接口,该接口是一个 mini 接口,其中没有需要实现方法,implements Serializable 只是为了标注该对象是可被序列化的。

例如,在 web 开发中,如果对象被保存在了 Session 中,tomcat 在重启时要把 Session 对象序列化到硬盘,这个对象就必须实现Serializable 接口。如果对象要经过分布式系统进行网络传输,被传输的对象就必须实现 Serializable 接口。

44、描述一下 JVM 加载 class 文件的原理机制?

JVM 中类的装载是由 ClassLoader 和它的子类来实现的,Java ClassLoader 是一个重要的 Java 运行时系统组件。它负责在运行时查找和装入类文件的类。

45、heap 和 stack 有什么区别。

java 的内存分为两类,一类是栈内存,一类是堆内存。栈内存是指程序进入一个方法时,会为这个方法单独分配一块私属存储空间,用于存储这个方法内部的局部变量,当这个方法结束时,分配给这个方法的栈会释放,这个栈中的变量也将随之释放。

堆是与栈作用不同的内存,一般用于存放不在当前方法栈中的那些数据,例如,使用 new 创建的对象都放在堆里,所以,它不会随方法的结束而消失。方法中的局部变量使用 final 修饰后,放在堆中,而不是栈中。

46、GC 是什么?为什么要有 GC?

GC 是垃圾收集的意思(Gabage Collection),内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java 提供的 GC 功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的,Java 语言没有提供释放已分配内存的显示操作方法。

47、垃圾回收的优点和原理。并考虑2种回收机制。

Java 语言中一个显著的特点就是引入了垃圾回收机制,使 c++ 程序员最头疼的内存管理的问题迎刃而解,它使得 Java 程序员在编写程序的时候不再需要考虑内存管理。由于垃圾回收机制,Java 中的对象不再有”作用域”的概念,只有对象的引用才有”作用域”。

垃圾回收可以有效的防止内存泄露,有效的使用可以使用的内存。垃圾回收器通常是作为一个单独的低级别的线程运行,不可预知的情况下对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收,程序员不能实时的调用垃圾回收器对某个对象或所有对象进行垃圾回收。

回收机制有分代复制垃圾回收和标记垃圾回收,增量垃圾回收。

48、垃圾回收器的基本原理是什么?垃圾回收器可以马上回收内存吗?有什么办法主动通知虚拟机进行垃圾回收?

对于 GC 来说,当程序员创建对象时,GC 就开始监控这个对象的地址、大小以及使用情况。通常,GC 采用有向图的方式记录和管理堆(heap)中的所有对象。通过这种方式确定哪些对象是”可达的”,哪些对象是”不可达的”。当 GC 确定一些对象为”不可达”时,GC 就有责任回收这些内存空间。

程序员可以手动执行 System.gc(),通知 GC 运行,但是 Java 语言规范并不保证 GC 一定会执行。

49、Java 中,throw 和 throws 有什么区别

throw 用于抛出 java.lang.Throwable 类的一个实例化对象,意思是说你可以通过关键字 throw 抛出一个Exception,如:

throw new IllegalArgumentException(“XXXXXXXXX″)

而 throws 的作用是作为方法声明和签名的一部分,方法被抛出相应的异常以便调用者能处理。Java 中,任何未处理的受检查异常强制在 throws 子句中声明。

50,java 中会存在内存泄漏吗,请简单描述。

先解释什么是内存泄漏:所谓内存泄露就是指一个不再被程序使用的对象或变量一直被占据在内存中。java 中有垃圾回收机制,它可以保证当对象不再被引用的时候,对象将自动被垃圾回收器从内存中清除掉。

由于 Java 使用有向图的方式进行垃圾回收管理,可以消除引用循环的问题,例如有两个对象,相互引用,只要它们和根进程不可达,那么 GC 也是可以回收它们的。

java 中的内存泄露的情况:长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收,这就是 java 中内存泄露的发生场景,通俗地说,就是程序员可能创建了一个对象,以后一直不再使用这个对象,这个对象却一直被引用,即这个对象无用但是却无法被垃圾回收器回收的,这就是 java 中可能出现内存泄露的情况,例如,缓存系统,我们加载了一个对象放在缓存中(例如放在一个全局 map 对象中),然后一直不再使用它,这个对象一直被缓存引用,但却不再被使用。

51、说一说 Servlet 的生命周期?

Servlet 有良好的生存期的定义,包括加载和实例化、初始化、处理请求以及服务结束。这个生存期由 javax.servlet.Servlet 接口的 init(),service() 和 destroy 方法表达。

Servlet 被服务器实例化后,容器运行其 init 方法,请求到达时运行其 service 方法,service 方法自动派遣运行与请求对应的 doXXX 方法(doGet,doPost)等,当服务器决定将实例销毁的时候调用其 destroy 方法。

web 容器加载 servlet,生命周期开始。通过调用 servlet 的 init() 方法进行 servlet 的初始化。通过调用 service() 方法实现,根据请求的不同调用不同的 do***() 方法。结束服务,web 容器调用 servlet 的 destroy() 方法。

52、Servlet API 中 forward() 与 redirect() 的区别?

  • 1.从地址栏显示来说

forward 是服务器请求资源,服务器直接访问目标地址的 URL,把那个 URL 的响应内容读取过来,然后把这些内容再发给浏览器。浏览器根本不知道服务器发送的内容从哪里来的,所以它的地址栏还是原来的地址。

redirect 是服务端根据逻辑,发送一个状态码,告诉浏览器重新去请求那个地址.所以地址栏显示的是新的 URL。所以 redirect 等于客户端向服务器端发出两次 request,同时也接受两次 response。

  • 2.从数据共享来说

forward:转发页面和转发到的页面可以共享 request 里面的数据。

redirect:不能共享数据。

redirect 不仅可以重定向到当前应用程序的其他资源,还可以重定向到同一个站点上的其他应用程序中的资源,甚至是使用绝对 URL 重定向到其他站点的资源。

forward 方法只能在同一个 Web 应用程序内的资源之间转发请求。forward 是服务器内部的一种操作。

redirect 是服务器通知客户端,让客户端重新发起请求。

所以,你可以说 redirect 是一种间接的请求, 但是你不能说”一个请求是属于 forward 还是 redirect “。

  • 3.从运用地方来说

forward:一般用于用户登陆的时候,根据角色转发到相应的模块。

redirect:一般用于用户注销登陆时返回主页面和跳转到其它的网站等。

  • 4.从效率来说

forward:高。

redirect:低。

53、request.getAttribute() 和 request.getParameter() 有何区别?

  • 1,request.getParameter() 取得是通过容器的实现来取得通过类似 post,get 等方式传入的数据。
    request.setAttribute() 和 getAttribute() 只是在 web 容器内部流转,仅仅是请求处理阶段。

  • 2,getAttribute 是返回对象,getParameter 返回字符串

  • 3,getAttribute() 一向是和 setAttribute() 一起使用的,只有先用 setAttribute() 设置之后,才能够通过 getAttribute() 来获得值,它们传递的是 Object 类型的数据。而且必须在同一个 request 对象中使用才有效。,而 getParameter() 是接收表单的 get 或者 post 提交过来的参数。

54,jsp 静态包含和动态包含的区别

1、<%@include file=”xxx.jsp”%> 为 jsp 中的编译指令,其文件的包含是发生在 jsp 向 servlet 转换的时期,而 是 jsp 中的动作指令,其文件的包含是发生在编译时期,也就是将 java 文件编译为 class 文件的时期。

2、使用静态包含只会产生一个 class 文件,而使用动态包含会产生多个 class 文件。

3、使用静态包含,包含页面和被包含页面的 request 对象为同一对象,因为静态包含只是将被包含的页面的内容复制到包含的页面中去;而动态包含包含页面和被包含页面不是同一个页面,被包含的页面的 request 对象可以取到的参数范围要相对大些,不仅可以取到传递到包含页面的参数,同样也能取得在包含页面向下传递的参数。

55,MVC 的各个部分都有那些技术来实现?如何实现?

MVC 是 Model-View-Controller 的简写。Model 代表的是应用的业务逻辑(通过JavaBean,EJB 组件实现),View 是应用的表示面(由JSP 页面产生),Controller 是提供应用的处理过程控制(一般是一个 Servlet),通过这种设计模型把应用逻辑,处理过程和显示逻辑分成不同的组件实现。这些组件可以进行交互和重用。

56,jsp 有哪些内置对象?作用分别是什么?

JSP共有以下9个内置的对象:

  • 1,request 用户端请求,此请求会包含来自 GET/POST 请求的参数

  • 2,response 网页传回用户端的回应

  • 3,pageContext 网页的属性是在这里管理

  • 4,session 与请求有关的会话期

  • 5,application servlet 正在执行的内容

  • 6,out 用来传送回应的输出

  • 7,config servlet 的构架部件

  • 8,page JSP 网页本身

  • 9,exception 针对错误网页,未捕捉的例外

57,Http 中,get 和 post 方法的区别

  • 1,Get 是向服务器发索取数据的一种请求,而 Post 是向服务器提交数据的一种请求。

  • 2,Get 是获取信息,而不是修改信息,类似数据库查询功能一样,数据不会被修改。

  • 3,Get 请求的参数会跟在 url 后进行传递,请求的数据会附在 URL 之后,以 ? 分割 URL 和传输数据,参数之间以 & 相连,%XX 中的 XX 为该符号以 16 进制表示的 ASCII,如果数据是英文字母/数字,原样发送,如果是空格,转换为 +,如果是中文 / 其他字符,则直接把字符串用 BASE64 加密。

  • 4,Get 传输的数据有大小限制,因为 GET 是通过 URL 提交数据,那么 GET 可提交的数据量就跟 URL 的长度有直接关系了,不同的浏览器对 URL 的长度的限制是不同的。

  • 5,GET 请求的数据会被浏览器缓存起来,用户名和密码将明文出现在 URL 上,其他人可以查到历史浏览记录,数据不太安全。在服务器端,用 Request.QueryString 来获取 Get 方式提交来的数据 Post 请求则作为 http 消息的实际内容发送给 web 服务器,数据放置在 HTML Header 内提交,Post 没有限制提交的数据。Post 比 Get 安全,当数据是中文或者不敏感的数据,则用 get,因为使用 get,参数会显示在地址,对于敏感数据和不是中文字符的数据,则用 post。

  • 6,POST 表示可能修改变服务器上的资源的请求,在服务器端,用 Post 方式提交的数据只能用 Request.Form 来获取。

Cookie 是会话技术,将用户的信息保存到浏览器的对象.

区别:

  • (1)cookie 数据存放在客户的浏览器上,session 数据放在服务器上。

  • (2)cookie 不是很安全,别人可以分析存放在本地的 COOKIE 并进行 COOKIE 欺骗,如果主要考虑到安全应当使用 session。

  • (3)session 会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能,如果主要考虑到减轻服务器性能方面,应当使用 COOKIE。

  • (4)单个 cookie 在客户端的限制是 3K,就是说一个站点在客户端存放的 COOKIE 不能 3K。

结论:

将登陆信息等重要信息存放为 SESSION;其他信息如果需要保留,可以放在 COOKIE 中。

59,jsp 和 servlet 的区别、共同点、各自应用的范围?

JSP 是 Servlet 技术的扩展,本质上就是 Servlet 的简易方式。JSP 编译后是“类servlet”。

Servlet 和 JSP 最主要的不同点在于:Servlet 的应用逻辑是在 Java 文件中,并且完全从表示层中的 HTML 里分离开来。而 JSP 的情况是 Java 和 HTML 可以组合成一个扩展名为 .jsp 的文件。

JSP 侧重于视图,Servlet 主要用于控制逻辑。在 struts 框架中,JSP 位于 MVC 设计模式的视图层,而 Servlet 位于控制层。

60,tomcat 容器是如何创建 servlet 类实例?用到了什么原理?

当容器启动时,会读取在 webapps 目录下所有的 web 应用中的 web.xml 文件,然后对 xml 文件进行解析,并读取 servlet 注册信息。然后,将每个应用中注册的 servlet 类都进行加载,并通过反射的方式实例化。(有时候也是在第一次请求时实例化)

在 servlet 注册时加上 1 如果为正数,则在一开始就实例化,如果不写或为负数,则第一次请求实例化。

61,JDBC 访问数据库的基本步骤是什么?

  • 1,加载驱动

  • 2,通过 DriverManager 对象获取连接对象 Connection

  • 3,通过连接对象获取会话

  • 4,通过会话进行数据的增删改查,封装对象

  • 5,关闭资源

62,说说 preparedStatement 和 Statement 的区别

  • 1,效率:预编译会话比普通会话对象,数据库系统不会对相同的 sql 语句不会再次编译。

  • 2,安全性:可以有效的避免 sql 注入攻击!sql 注入攻击就是从客户端输入一些非法的特殊字符,而使服务器端在构造 sql 语句的时候仍然能够正确构造,从而收集程序和服务器的信息和数据。

比如:

“select * from t_user where userName = ‘” + userName + “ ’ and password =’” + password + “’”

如果用户名和密码输入的是

’1’ or ‘1’=’1’ ;  

则生产的 sql 语句是:

“select * from t_user where userName = ‘1’ or ‘1’ =’1’  and password =’1’  or ‘1’=’1’  

这个语句中的where 部分没有起到对数据筛选的作用。

63,说说事务的概念,在 JDBC 编程中处理事务的步骤。

  • 1 事务是作为单个逻辑工作单元执行的一系列操作。

  • 2,一个逻辑工作单元必须有四个属性,称为原子性、一致性、隔离性和持久性 (ACID) 属性,只有这样才能成为一个事务
    事务处理步骤。

  • 3,conn.setAutoComit(false); 设置提交方式为手工提交。

  • 4,conn.commit() 提交事务。

  • 5,出现异常,回滚 conn.rollback();

64,数据库连接池的原理。为什么要使用连接池。

  • 1,数据库连接是一件费时的操作,连接池可以使多个操作共享一个连接。

  • 2,数据库连接池的基本思想就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。我们可以通过设定连接池最大连接数来防止系统无尽的与数据库连接。更为重要的是我们可以通过连接池的管理机制监视数据库的连接的数量、使用情况,为系统开发,测试及性能调整提供依据。

  • 3,使用连接池是为了提高对数据库连接资源的管理。

65,JDBC 的脏读是什么?哪种数据库隔离级别能防止脏读?

当我们使用事务时,有可能会出现这样的情况,有一行数据刚更新,与此同时另一个查询读到了这个刚更新的值。这样就导致了脏读,因为更新的数据还没有进行持久化,更新这行数据的业务可能会进行回滚,这样这个数据就是无效的。数据库的 TRANSACTIONREADCOMMITTEDTRANSACTIONREPEATABLEREAD,和 TRANSACTION_SERIALIZABLE 隔离级别可以防止脏读。

66,什么是幻读,哪种隔离级别可以防止幻读?

幻读是指一个事务多次执行一条查询返回的却是不同的值。假设一个事务正根据某个条件进行数据查询,然后另一个事务插入了一行满足这个查询条件的数据。之后这个事务再次执行了这条查询,返回的结果集中会包含刚插入的那条新数据。这行新数据被称为幻行,而这种现象就叫做幻读。

只有 TRANSACTION_SERIALIZABLE 隔离级别才能防止产生幻读。

67,JDB C的 DriverManager 是用来做什么的?

JDBC 的 DriverManager 是一个工厂类,我们通过它来创建数据库连接。当 JDBC 的 Driver 类被加载进来时,它会自己注册到 DriverManager 类里面,然后我们会把数据库配置信息传成 DriverManager.getConnection() 方法,DriverManager 会使用注册到它里面的驱动来获取数据库连接,并返回给调用的程序。

68,execute,executeQuery,executeUpdate 的区别是什么?

  • 1,Statement 的 execute(String query) 方法用来执行任意的 SQL 查询,如果查询的结果是一个 ResultSet,这个方法就返回 true。如果结果不是 ResultSet,比如 insert 或者 update 查询,它就会返回 false。我们可以通过它的 getResultSet 方法来获取 ResultSet,或者通过 getUpdateCount() 方法来获取更新的记录条数。

  • 2,Statement 的 executeQuery(String query) 接口用来执行 select 查询,并且返回 ResultSet。即使查询不到记录返回的 ResultSet 也不会为 null。我们通常使用 executeQuery 来执行查询语句,这样的话如果传进来的是 insert 或者 update 语句的话,它会抛出错误信息为

    “executeQuery method can not be used for update”
    

    java.util.SQLException。 
  • 3,Statement 的 executeUpdate(String query) 方法用来执行 insert 或者 update/delete(DML)语句,或者什么也不返回,对于 DDL 语句,返回值是 int 类型,如果是 DML 语句的话,它就是更新的条数,如果是 DDL 的话,就返回 0。只有当你不确定是什么语句的时候才应该使用 execute() 方法,否则应该使用 executeQuery 或者 executeUpdate 方法。

69,SQL 查询出来的结果分页展示一般怎么做?

Oracle

select * from 
(select *,rownum as tempid from student )  t 
where t.tempid between ” + pageSize*(pageNumber-1) + ” and ” + pageSize*pageNumber

MySQL:

select * from students limit ” + pageSize*(pageNumber-1) + “,” + pageSize;

sql server:

select top ” + pageSize + ” * from students where id not in + 
(select top ” + pageSize * (pageNumber-1) +  id from students order by id) +  
“order by id;

70,JDBC 的 ResultSet 是什么?

在查询数据库后会返回一个 ResultSet,它就像是查询结果集的一张数据表。

ResultSet 对象维护了一个游标,指向当前的数据行。开始的时候这个游标指向的是第一行。如果调用了 ResultSet 的 next() 方法游标会下移一行,如果没有更多的数据了,next() 方法会返回 false。可以在 for 循环中用它来遍历数据集。

默认的 ResultSet 是不能更新的,游标也只能往下移。也就是说你只能从第一行到最后一行遍历一遍。不过也可以创建可以回滚或者可更新的ResultSet。

当生成 ResultSet 的 Statement 对象要关闭或者重新执行或是获取下一个 ResultSet 的时候,ResultSet 对象也会自动关闭。

可以通过 ResultSet 的 getter 方法,传入列名或者从 1 开始的序号来获取列数据。

71,谈谈你对 Struts 的理解。

    1. struts 是一个按 MVC 模式设计的 Web 层框架,其实它就是一个 Servlet,这个 Servlet 名为 ActionServlet,或是 ActionServlet 的子类。我们可以在 web.xml 文件中将符合某种特征的所有请求交给这个 Servlet 处理,这个 Servlet 再参照一个配置文件将各个请求分别分配给不同的 action 去处理。

(struts 的配置文件可以有多个,可以按模块配置各自的配置文件,这样可以防止配置文件的过度膨胀)

  • 2.ActionServlet 把请求交给 action 去处理之前,会将请求参数封装成一个 formbean 对象(就是一个 java 类,这个类中的每个属性对应一个请求参数),

  • 3.要说明的是, ActionServlet 把 formbean 对象传递给 action 的 execute 方法之前,可能会调用 formbean 的 validate 方法进行校验,只有校验通过后才将这个 formbean 对象传递给 action 的 execute 方法,否则,它将返回一个错误页面,这个错误页面由 input 属性指定。

  • 4.action 执行完后要返回显示的结果视图,这个结果视图是用一个 ActionForward 对象来表示的,actionForward 对象通过 struts-config.xml 配置文件中的配置关联到某个 jsp 页面,因为程序中使用的是在 struts-config.xml 配置文件为 jsp 页面设置的逻辑名,这样可以实现 action 程序代码与返回的 jsp 页面名称的解耦。

72、谈谈你对 Hibernate 的理解。

  • 1.面向对象设计的软件内部运行过程可以理解成就是在不断创建各种新对象、建立对象之间的关系,调用对象的方法来改变各个对象的状态和对象消亡的过程,不管程序运行的过程和操作怎么样,本质上都是要得到一个结果,程序上一个时刻和下一个时刻的运行结果的差异就表现在内存中的对象状态发生了变化。

  • 2.为了在关机和内存空间不够的状况下,保持程序的运行状态,需要将内存中的对象状态保存到持久化设备和从持久化设备中恢复出对象的状态,通常都是保存到关系数据库来保存大量对象信息。从 Java 程序的运行功能上来讲,保存对象状态的功能相比系统运行的其他功能来说,应该是一个很不起眼的附属功能,java 采用 jdbc 来实现这个功能,这个不起眼的功能却要编写大量的代码,而做的事情仅仅是保存对象和恢复对象,并且那些大量的 jdbc 代码并没有什么技术含量,基本上是采用一套例行公事的标准代码模板来编写,是一种苦活和重复性的工作。

  • 3.通过数据库保存 java 程序运行时产生的对象和恢复对象,其实就是实现了 java 对象与关系数据库记录的映射关系,称为 ORM(即 Object RelationMapping),人们可以通过封装 JDBC 代码来实现了这种功能,封装出来的产品称之为 ORM 框架,Hibernate 就是其中的一种流行 ORM框架。使用 Hibernate 框架,不用写 JDBC 代码,仅仅是调用一个 save 方法,就可以将对象保存到关系数据库中,仅仅是调用一个 get 方法,就可以从数据库中加载出一个对象。

  • 4.使用 Hibernate 的基本流程是:配置 Configuration 对象、产生 SessionFactory、创建 session 对象,启动事务,完成 CRUD 操作,提交事务,关闭 session。

  • 5.使用 Hibernate 时,先要配置 hibernate.cfg.xml 文件,其中配置数据库连接信息和方言等,还要为每个实体配置相应的 hbm.xml 文件,hibernate.cfg.xml 文件中需要登记每个 hbm.xml 文件。

  • 6.在应用 Hibernate 时,重点要了解 Session 的缓存原理,级联,延迟加载和 hql 查询。

73,谈谈你对 Spring 的理解。

  • 1.Spring 是实现了工厂模式的工厂类(在这里有必要解释清楚什么是工厂模式),这个类名为 BeanFactory(实际上是一个接口),在程序中通常 BeanFactory 的子类 ApplicationContext。Spring 相当于一个大的工厂类,在其配置文件中通过 元素配置用于创建实例对象的类名和实例对象的属性。

  • 2.Spring 提供了对 IOC 良好支持,IOC 是一种编程思想,是一种架构艺术,利用这种思想可以很好地实现模块之间的解耦,IOC 也称为 DI(Depency Injection)。

  • 3.Spring 提供了对 AOP 技术的良好封装, AOP 称为面向切面编程,就是系统中有很多各不相干的类的方法,在这些众多方法中要加入某种系统功能的代码,例如,加入日志,加入权限判断,加入异常处理,这种应用称为 AOP。

实现 AOP 功能采用的是代理技术,客户端程序不再调用目标,而调用代理类,代理类与目标类对外具有相同的方法声明,有两种方式可以实现相同的方法声明,一是实现相同的接口,二是作为目标的子类。

在 JDK 中采用 Proxy 类产生动态代理的方式为某个接口生成实现类,如果要为某个类生成子类,则可以用 CGLI B。在生成的代理类的方法中加入系统功能和调用目标类的相应方法,系统功能的代理以 Advice 对象进行提供,显然要创建出代理对象,至少需要目标类和 Advice 类。spring 提供了这种支持,只需要在 spring 配置文件中配置这两个元素即可实现代理和 aop 功能。

74,谈谈 Struts 的优缺点

优点:

  • 1.实现 MVC 模式,结构清晰,使开发者只关注业务逻辑的实现。

  • 2有丰富的 tag 可以用 ,Struts 的标记库(Taglib),如能灵活动用,则能大大提高开发效率。

  • 3.页面导航使系统的脉络更加清晰。通过一个配置文件,即可把握整个系统各部分之间的联系,这对于后期的维护有着莫大的好处。尤其是当另一批开发者接手这个项目时,这种优势体现得更加明显。

  • 4.提供 Exception 处理机制 .

  • 5.数据库链接池管理

  • 6.支持 I18N

缺点:

  • 一,转到展示层时,需要配置 forward,如果有十个展示层的 jsp,需要配置十次 struts,而且还不包括有时候目录、文件变更,需要重新修改 forward,注意,每次修改配置之后,要求重新部署整个项目,而 tomcate 这样的服务器,还必须重新启动服务器

  • 二,Struts 的 Action 必需是 thread-safe 方式,它仅仅允许一个实例去处理所有的请求。所以 action 用到的所有的资源都必需统一同步,这个就引起了线程安全的问题。

  • 三,测试不方便。 Struts 的每个 Action 都同 Web 层耦合在一起,这样它的测试依赖于 Web 容器,单元测试也很难实现。不过有一个 Junit的扩展工具 Struts TestCase 可以实现它的单元测试。

  • 四,类型的转换。 Struts 的 FormBean 把所有的数据都作为 String 类型,它可以使用工具 Commons-Beanutils 进行类型转化。但它的转化都是在 Class 级别,而且转化的类型是不可配置的。类型转化时的错误信息返回给用户也是非常困难的。

  • 五,对 Servlet 的依赖性过强。 Struts 处理 Action 时必需要依赖 ServletRequest 和 ServletResponse,所有它摆脱不了 Servlet 容器。

  • 六,前端表达式语言方面。 Struts 集成了 JSTL,所以它主要使用 JSTL 的表达式语言来获取数据。可是 JSTL 的表达式语言在 Collection 和索引属性方面处理显得很弱。

  • 七,对 Action 执行的控制困难。 Struts 创建一个 Action,如果想控制它的执行顺序将会非常困难。甚至你要重新去写 Servlet 来实现你的这个功能需求。

  • 八,对 Action 执行前和后的处理。 Struts 处理 Action 的时候是基于 class 的 hierarchies,很难在 action 处理前和后进行操作。

  • 九,对事件支持不够。在 struts 中,实际是一个表单 Form 对应一个 Action 类(或DispatchAction),换一句话说:在 Struts 中实际是一个表单只能对应一个事件,struts 这种事件方式称为 application event,application event 和 component event 相比是一种粗粒度的事件。

75,iBatis 与 Hibernate 有什么不同?

  • 相同点:屏蔽 jdbc api 的底层访问细节,使用我们不用与 jdbc api 打交道,就可以访问数据。

jdbc api 编程流程固定,还将 sql 语句与 java 代码混杂在了一起,经常需要拼凑 sql 语句,细节很繁琐。

  • ibatis 的好处:

屏蔽 jdbc api 的底层访问细节;

将 sql 语句与 java 代码进行分离;

提供了将结果集自动封装称为实体对象和对象的集合的功能,queryForList 返回对象集合,用 queryForObject 返回单个对象;

提供了自动将实体对象的属性传递给 sql 语句的参数。

Hibernate 是一个全自动的 orm 映射工具,它可以自动生成 sql 语句,ibatis 需要我们自己在 xml 配置文件中写 sql 语句,hibernate 要比 ibatis 功能负责和强大很多。因为 hibernate 自动生成 sql 语句,我们无法控制该语句,我们就无法去写特定的高效率的 sql。对于一些不太复杂的 sql 查询,hibernate 可以很好帮我们完成,但是,对于特别复杂的查询,hibernate 就很难适应了,这时候用 ibatis 就是不错的选择,因为 ibatis 还是由我们自己写 sql 语句。

76,在 hibernate 进行多表查询每个表中各取几个字段,也就是说查询出来的结果集没有一个实体类与之对应如何解决?

  • 解决方案一:按照 Object[] 数据取出数据,然后自己组 bean。

  • 解决方案二:对每个表的 bean 写构造函数,比如表一要查出 field1,field2 两个字段,那么有一个构造函数就是 Bean(type1 filed1,type2 field2) ,然后在 hql 里面就可以直接生成这个 bean 了。

77,介绍一下 Hibernate 的二级缓存

按照以下思路来回答

  • (1)首先说清楚什么是缓存

  • (2)再说有了 hibernate 的 Session 就是一级缓存,即有了一级缓存,为什么还要有二级缓存

  • (3)最后再说如何配置 Hibernate 的二级缓存。

1,缓存就是把以前从数据库中查询出来和使用过的对象保存在内存中(一个数据结构中),这个数据结构通常是或类似 HashMap,当以后要使用某个对象时,先查询缓存中是否有这个对象,如果有则使用缓存中的对象,如果没有则去查询数据库,并将查询出来的对象保存在缓存中,以便下次使用。

2,Hibernate 的 Session 就是一种缓存,我们通常将之称为 Hibernate 的一级缓存,当想使用 session 从数据库中查询出一个对象时,Session 也是先从自己内部查看是否存在这个对象,存在则直接返回,不存在才去访问数据库,并将查询的结果保存在自己内部。

由于 Session 代表一次会话过程,一个 Session 与一个数据库连接相关连,所以 Session 最好不要长时间保持打开,通常仅用于一个事务当中,在事务结束时就应关闭。并且 Session 是线程不安全的,被多个线程共享时容易出现问题。通常只有那种全局意义上的缓存才是真正的缓存应用,才有较大的缓存价值,因此,Hibernate 的 Session 这一级缓存的缓存作用并不明显,应用价值不大。Hibernate 的二级缓存就是要为Hibernate 配置一种全局缓存,让多个线程和多个事务都可以共享这个缓存。我们希望的是一个人使用过,其他人也可以使用,session 没有这种效果。

3,二级缓存是独立于 Hibernate 的软件部件,属于第三方的产品,多个厂商和组织都提供有缓存产品,例如,EHCache 和 OSCache 等等。在Hibernate 中使用二级缓存,首先就要在 hibernate.cfg.xml 配置文件中配置使用哪个厂家的缓存产品,接着需要配置该缓存产品自己的配置文件,最后要配置 Hibernate 中的哪些实体对象要纳入到二级缓存的管理中。明白了二级缓存原理和有了这个思路后,很容易配置起 Hibernate 的二级缓存。

扩展知识:一个 SessionFactory 可以关联一个二级缓存,也即一个二级缓存只能负责缓存一个数据库中的数据,当使用 Hibernate 的二级缓存后,注意不要有其他的应用或 SessionFactory 来更改当前数据库中的数据,这样缓存的数据就会与数据库中的实际数据不一致。

78,JDO 是什么?

JDO 是 Java 对象持久化的新的规范,为 java data object 的简称,也是一个用于存取某种数据仓库中的对象的标准化 API。JDO 提供了透明的对象存储,因此对开发人员来说,存储数据对象完全不需要额外的代码(如 JDBC API 的使用)。这些繁琐的例行工作已经转移到 JDO 产品提供商身上,使开发人员解脱出来,从而集中时间和精力在业务逻辑上。另外,JDO 很灵活,因为它可以在任何数据底层上运行。

比较:JDBC 只是面向关系数据库(RDBMS)JDO 更通用,提供到任何数据底层的存储功能,比如关系数据库、文件、XML以及对象数据库(ODBMS)等等,使得应用可移植性更强。

79,Hibernate 的一对多和多对一双向关联的区别??

一对多关联映射和多对一关联映射实现的基本原理都是一样的,既是在多的一端加入一个外键指向一的一端外键,而主要的区别就是维护端不同。

它们的区别在于维护的关系不同:

一对多关联映射是指在加载一的一端数据的同时加载多的一端的数据多对一关联映射是指在加载多的一端数据的同时加载一的一端的数据。

80,Hibernate 是如何延迟加载?

    1. Hibernate2 延迟加载实现:

a)实体对象

b)集合(Collection)

    1. Hibernate3 提供了属性的延迟加载功能 当 Hibernate 在查询数据的时候,数据并没有存在与内存中,当程序真正对数据的操作时,对象才存在与内存中,就实现了延迟加载,他节省了服务器的内存开销,从而提高了服务器的性能。

81,使用 Spring 框架的好处是什么?

**轻量:**Spring 是轻量的,基本的版本大约 2MB。

**控制反转:**Spring 通过控制反转实现了松散耦合,对象们给出它们的依赖,而不是创建或查找依赖的对象们。

**面向切面的编程(AOP):**Spring 支持面向切面的编程,并且把应用业务逻辑和系统服务分开。

**容器:**Spring 包含并管理应用中对象的生命周期和配置。

**MVC框架:**Spring 的 WEB 框架是个精心设计的框架,是 Web 框架的一个很好的替代品。

**事务管理:**Spring 提供一个持续的事务管理接口,可以扩展到上至本地事务下至全局事务(JTA)。

**异常处理:**Spring 提供方便的 API 把具体技术相关的异常(比如由 JDBC,Hibernate or JDO 抛出的)转化为一致的 unchecked 异常。

82. ApplicationContext 通常的实现是什么?

FileSystemXmlApplicationContext :此容器从一个 XML 文件中加载 beans 的定义,XML Bean 配置文件的全路径名必须提供给它的构造函数。

ClassPathXmlApplicationContext:此容器也从一个 XML 文件中加载 beans 的定义,这里,你需要正确设置 classpath 因为这个容器将在 classpath 里找 bean 配置。

WebXmlApplicationContext:此容器加载一个XML文件,此文件定义了一个 WEB 应用的所有 bean。

83,什么是 Spring 的依赖注入?有哪些方法进行依赖注入?

依赖注入,是 IOC 的一个方面,是个通常的概念,它有多种解释。这概念是说你不用创建对象,而只需要描述它如何被创建。你不在代码里直接组装你的组件和服务,但是要在配置文件里描述哪些组件需要哪些服务,之后一个容器(IOC容器)负责把他们组装起来。

构造器依赖注入:构造器依赖注入通过容器触发一个类的构造器来实现的,该类有一系列参数,每个参数代表一个对其他类的依赖。

**Setter 方法注入:**Setter 方法注入是容器通过调用无参构造器或无参 static 工厂方法实例化 bean 之后,调用该 bean 的 setter 方法,即实现了基于 setter 的依赖注入。

84,什么是 Spring beans?

Spring beans 是那些形成 Spring 应用的主干的 java 对象。它们被 Spring IOC 容器初始化,装配,和管理。这些 beans 通过容器中配置的元数据创建。比如,以 XML 文件中 的形式定义。

Spring 框架定义的 beans 都是单件 beans。在 bean tag 中有个属性”singleton”,如果它被赋为 TRUE,bean 就是单件,否则就是一个 prototype bean。默认是 TRUE,所以所有在 Spring 框架中的 beans 缺省都是单件。

85,解释 Spring 支持的几种 bean 的作用域。

Spring框架支持以下五种bean的作用域:

  • singleton : bean在每个 Spring ioc 容器中只有一个实例。

  • prototype:一个 bean 的定义可以有多个实例。

  • request:每次 http 请求都会创建一个 bean,该作用域仅在基于 web 的 Spring ApplicationContext 情形下有效。

  • session:在一个 HTTP Session 中,一个 bean 定义对应一个实例。该作用域仅在基于 web 的 Spring ApplicationContext 情形下有效。

  • global-session:在一个全局的 HTTP Session 中,一个 bean 定义对应一个实例。该作用域仅在基于 web 的 Spring ApplicationContext 情形下有效。

缺省的 Spring bean 的作用域是 Singleton.

86,解释 Spring 框架中 bean 的生命周期。

  • 1,Spring 容器从 XML 文件中读取 bean 的定义,并实例化 bean。

  • 2,Spring 根据 bean 的定义填充所有的属性。

  • 3,如果 bean 实现了 BeanNameAware 接口,Spring 传递 bean 的 ID 到 setBeanName方法。

  • 4,如果 Bean 实现了 BeanFactoryAware 接口, Spring 传递 beanfactory 给 setBeanFactory 方法。

  • 5,如果有任何与 bean 相关联的 BeanPostProcessors,Spring 会在 postProcesserBeforeInitialization() 方法内调用它们。

  • 6,如果 bean 实现 IntializingBean 了,调用它的 afterPropertySet 方法,如果 bean 声明了初始化方法,调用此初始化方法。

  • 7,如果有 BeanPostProcessors 和 bean 关联,这些 bean 的 postProcessAfterInitialization() 方法将被调用。

  • 8,如果 bean 实现了 DisposableBean,它将调用 destroy() 方法。

87,在 Spring 中如何注入一个 java 集合?

Spring 提供以下几种集合的配置元素:

<list> 类型用于注入一列值,允许有相同的值。

<set> 类型用于注入一组值,不允许有相同的值。

<map> 类型用于注入一组键值对,键和值都可以为任意类型。

<props> 类型用于注入一组键值对,键和值都只能为 String 类型。

88,解释不同方式的自动装配 。

有五种自动装配的方式,用来指导 Spring 容器用自动装配方式进行依赖注入。

  • no:默认的方式是不进行自动装配,通过显式设置ref 属性来进行装配。

  • byName:通过参数名 自动装配,Spring 容器在配置文件中发现 bean 的 autowire 属性被设置成 byname,之后容器试图匹配、装配和该 bean 的属性具有相同名字的 bean。

  • byType:通过参数类型自动装配,Spring 容器在配置文件中发现 bean 的 autowire 属性被设置成 byType,之后容器试图匹配、装配和该 bean 的属性具有相同类型的 bean。如果有多个 bean 符合条件,则抛出错误。

  • constructor:这个方式类似于 byType, 但是要提供给构造器参数,如果没有确定的带参数的构造器参数类型,将会抛出异常。

  • autodetect:首先尝试使用 constructor 来自动装配,如果无法工作,则使用 byType 方式。

89,Spring 框架的事务管理有哪些优点?

它为不同的事务 API 如 JTA,JDBC,Hibernate,JPA 和 JDO,提供一个不变的编程模式。

它为编程式事务管理提供了一套简单的 API 而不是一些复杂的事务 API 如

它支持声明式事务管理。

它和 Spring 各种数据访问抽象层很好得集成。

90.什么是基于 Java 的 Spring 注解配置? 给一些注解的例子。

基于 Java 的配置,允许你在少量的 Java 注解的帮助下,进行你的大部分 Spring 配置而非通过 XML 文件。

以 @Configuration 注解为例,它用来标记类可以当做一个 bean 的定义,被 Spring IOC 容器使用。另一个例子是 @Bean 注解,它表示此方法将要返回一个对象,作为一个 bean 注册进 Spring 应用上下文。

91,什么是 ORM?

对象关系映射(Object-Relational Mapping,简称 ORM)是一种为了解决程序的面向对象模型与数据库的关系模型互不匹配问题的技术;

简单的说,ORM 是通过使用描述对象和数据库之间映射的元数据(在 Java 中可以用 XML 或者是注解),将程序中的对象自动持久化到关系数据库中或者将关系数据库表中的行转换成 Java 对象,其本质上就是将数据从一种形式转换到另外一种形式。

92,Hibernate 中 SessionFactory 是线程安全的吗?Session 是线程安全的吗(两个线程能够共享同一个 Session 吗)?

SessionFactory 对应 Hibernate 的一个数据存储的概念,它是线程安全的,可以被多个线程并发访问。SessionFactory 一般只会在启动的时候构建。对于应用程序,最好将 SessionFactory 通过单例模式进行封装以便于访问。

Session 是一个轻量级非线程安全的对象(线程间不能共享 session),它表示与数据库进行交互的一个工作单元。Session 是由SessionFactory 创建的,在任务完成之后它会被关闭。Session 是持久层服务对外提供的主要接口。

Session 会延迟获取数据库连接(也就是在需要的时候才会获取)。为了避免创建太多的 session,可以使用 ThreadLocal 将 session 和当前线程绑定在一起,这样可以让同一个线程获得的总是同一个 session。Hibernate 3 中 SessionFactory 的 getCurrentSession() 方法就可以做到。

93,Session 的 save()、update()、merge()、lock()、saveOrUpdate() 和 persist() 方法分别是做什么的?有什么区别?

Hibernate 的对象有三种状态:瞬时态(transient)持久态(persistent)游离态(detached)

瞬时态的实例可以通过调用 save()、persist()或者 saveOrUpdate()方法变成持久态;

游离态的实例可以通过调用 update()、saveOrUpdate()、lock()或者 replicate()变成持久态。save()和 persist()将会引发 SQL 的 INSERT 语句,而 update()或 merge()会引发UPDATE语句。

save() 和 update()的区别在于一个是将瞬时态对象变成持久态,一个是将游离态对象变为持久态。merge()方法可以完成 save()和 update()方法的功能,它的意图是将新的状态合并到已有的持久化对象上或创建新的持久化对象。

对于 persist()方法,按照官方文档的说明:

  • 1、persist()方法把一个瞬时态的实例持久化,但是并不保证标识符被立刻填入到持久化实例中,标识符的填入可能被推迟到 flush 的时间;

  • 2、persist()方法保证当它在一个事务外部被调用的时候并不触发一个 INSERT 语句,当需要封装一个长会话流程的时候,persist()方法是很有必要的;

  • 3、save()方法不保证第 2 条,它要返回标识符,所以它会立即执行 INSERT 语句,不管是在事务内部还是外部。至于 lock()方法和 update()方法的区别,update()方法是把一个已经更改过的脱管状态的对象变成持久状态;lock()方法是把一个没有更改过的脱管状态的对象变成持久状态。

94,阐述 Session 加载实体对象的过程。

  • 1、Session 在调用数据库查询功能之前,首先会在一级缓存中通过实体类型和主键进行查找,如果一级缓存查找命中且数据状态合法,则直接返回;

  • 2、如果一级缓存没有命中,接下来 Session 会在当前 NonExists 记录(相当于一个查询黑名单,如果出现重复的无效查询可以迅速做出判断,从而提升性能)中进行查找,如果 NonExists 中存在同样的查询条件,则返回 null;

  • 3、如果一级缓存查询失败查询二级缓存,如果二级缓存命中直接返回;

  • 4、如果之前的查询都未命中,则发出 SQL 语句,如果查询未发现对应记录则将此次查询添加到 Session 的 NonExists 中加以记录,并返回null;

  • 5、根据映射配置和 SQL 语句得到 ResultSet,并创建对应的实体对象;

  • 6、将对象纳入 Session(一级缓存)的管理;

  • 7、如果有对应的拦截器,则执行拦截器的 onLoad 方法;

  • 8、如果开启并设置了要使用二级缓存,则将数据对象纳入二级缓存;

  • 9、返回数据对象。

95,MyBatis 中使用 # 和 $ 书写占位符有什么区别?

# 将传入的数据都当成一个字符串,会对传入的数据自动加上引号;

$ 将传入的数据直接显示生成在 SQL 中。

注意:使用 $ 占位符可能会导致 SQL 注射攻击,能用 # 的地方就不要使用 $,写 order by 子句的时候应该用 $ 而不是 #。

96,解释一下 MyBatis 中命名空间(namespace)的作用。

在大型项目中,可能存在大量的 SQL 语句,这时候为每个 SQL 语句起一个唯一的标识(ID)就变得并不容易了。为了解决这个问题,在 MyBatis 中,可以为每个映射文件起一个唯一的命名空间,这样定义在这个映射文件中的每个 SQL 语句就成了定义在这个命名空间中的一个 ID。只要我们能够保证每个命名空间中这个 ID 是唯一的,即使在不同映射文件中的语句 ID 相同,也不会再产生冲突了。

97、MyBatis 中的动态 SQL 是什么意思?

对于一些复杂的查询,我们可能会指定多个查询条件,但是这些条件可能存在也可能不存在,如果不使用持久层框架我们可能需要自己拼装 SQL 语句,不过 MyBatis 提供了动态 SQL 的功能来解决这个问题。MyBatis 中用于实现动态 SQL 的元素主要有:

if    - choose / when / otherwise    - trim    - where    - set     - foreach

用法举例:

<select id="foo" parameterType="Blog" resultType="Blog">        
    select * from t_blog where 1 = 1
    <if test="title != null">            
       and title = #{title}
    </if>
    <if test="content != null">            
       and content = #{content}
    </if>
    <if test="owner != null">            
       and owner = #{owner}
    </if>
</select>

98,JDBC 编程有哪些不足之处,MyBatis 是如何解决这些问题的?

  • 1、JDBC:数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库链接池可解决此问题。

MyBatis:在 SqlMapConfig.xml 中配置数据链接池,使用连接池管理数据库链接。

  • 2、JDBC:Sql语句写在代码中造成代码不易维护,实际应用 sql 变化的可能较大,sql 变动需要改变 java 代码。

MyBatis:将 Sql 语句配置在 XXXXmapper.xml 文件中与 java 代码分离。

  • 3、JDBC:向 sql 语句传参数麻烦,因为 sql 语句的 where 条件不一定,可能多也可能少,占位符需要和参数一一对应。

MyBatis: Mybatis 自动将 java 对象映射至 sql 语句。

  • 4,JDBC:对结果集解析麻烦,sql 变化导致解析代码变化,且解析前需要遍历,如果能将数据库记录封装成 pojo 对象解析比较方便。

MyBatis:Mybatis 自动将 sql 执行结果映射至 java 对象。

99,MyBatis 与 Hibernate 有哪些不同?

  • 1、Mybatis 和 hibernate 不同,它不完全是一个 ORM 框架,因为 MyBatis 需要程序员自己编写 Sql 语句,不过 mybatis 可以通过 XML 或注解方式灵活配置要运行的 sql 语句,并将 java 对象和 sql 语句映射生成最终执行的 sql,最后将 sql 执行的结果再映射生成 java 对象。

  • 2、Mybatis 学习门槛低,简单易学,程序员直接编写原生态 sql,可严格控制 sql 执行性能,灵活度高,非常适合对关系数据模型要求不高的软件开发,例如互联网软件、企业运营类软件等,因为这类软件需求变化频繁,一但需求变化要求成果输出迅速。但是灵活的前提是 mybatis 无法做到数据库无关性,如果需要实现支持多种数据库的软件则需要自定义多套 sql 映射文件,工作量大。

  • 3、Hibernate 对象/关系映射能力强,数据库无关性好,对于关系模型要求高的软件(例如需求固定的定制化软件)如果用 hibernate 开发可以节省很多代码,提高效率。但是 Hibernate 的缺点是学习门槛高,要精通门槛更高,而且怎么设计 O/R 映射,在性能和对象模型之间如何权衡,以及怎样用好 Hibernate 需要具有很强的经验和能力才行。

总之,按照用户的需求在有限的资源环境下只要能做出维护性、扩展性良好的软件架构都是好架构,所以框架只有适合才是最好。

100,简单的说一下 MyBatis 的一级缓存和二级缓存?

Mybatis 首先去缓存中查询结果集,如果没有则查询数据库,如果有则从缓存取出返回结果集就不走数据库。Mybatis 内部存储缓存使用一个 HashMap,key 为 hashCode+sqlId+Sql 语句。value 为从查询出来映射生成的 java 对象。

Mybatis 的二级缓存即查询缓存,它的作用域是一个 mapper 的 namespace,即在同一个 namespace 中查询 sql 可以从缓存中获取数据。二级缓存是可以跨 SqlSession 的。

数据库操作

基本表结构:

student(sno,sname,sage,ssex)学生表

course(cno,cname,tno) 课程表

sc(sno,cno,score) 成绩表

teacher(tno,tname) 教师表

101,查询课程1的成绩比课程2的成绩高的所有学生的学号

select a.sno from

(select sno,score from sc where cno=1) a,

(select sno,score from sc where cno=2) b

where a.score>b.score and a.sno=b.sno

102,查询平均成绩大于60分的同学的学号和平均成绩

select a.sno as "学号", avg(a.score) as "平均成绩" 

from 

(select sno,score from sc) a 

group by sno having avg(a.score)>60

103,查询所有同学的学号、姓名、选课数、总成绩

select a.sno as 学号, b.sname as 姓名,

count(a.cno) as 选课数, sum(a.score) as 总成绩

from sc a, student b

where a.sno = b.sno

group by a.sno, b.sname

或者:

selectstudent.sno as 学号, student.sname as 姓名,

 count(sc.cno) as 选课数, sum(score) as 总成绩

from student left Outer join sc on student.sno = sc.sno

group by student.sno, sname

104,查询姓“张”的老师的个数

selectcount(distinct(tname)) from teacher where tname like '张%'

或者:

select tname as "姓名", count(distinct(tname)) as "人数" 

from teacher 

where tname like'张%'

group by tname

105,查询没学过“张三”老师课的同学的学号、姓名

select student.sno,student.sname from student

where sno not in (select distinct(sc.sno) from sc,course,teacher

where sc.cno=course.cno and teacher.tno=course.tno and teacher.tname='张三')

106,查询同时学过课程1和课程2的同学的学号、姓名

select sno, sname from student

where sno in (select sno from sc where sc.cno = 1)

and sno in (select sno from sc where sc.cno = 2)

或者:

selectc.sno, c.sname from

(select sno from sc where sc.cno = 1) a,

(select sno from sc where sc.cno = 2) b,

student c

where a.sno = b.sno and a.sno = c.sno

或者:

select student.sno,student.sname from student,sc where student.sno=sc.sno and sc.cno=1

and exists( select * from sc as sc_2 where sc_2.sno=sc.sno and sc_2.cno=2)

107,查询学过“李四”老师所教所有课程的所有同学的学号、姓名

select a.sno, a.sname from student a, sc b

where a.sno = b.sno and b.cno in

(select c.cno from course c, teacher d where c.tno = d.tno and d.tname = '李四')

或者:

select a.sno, a.sname from student a, sc b,

(select c.cno from course c, teacher d where c.tno = d.tno and d.tname = '李四') e

where a.sno = b.sno and b.cno = e.cno

108,查询课程编号1的成绩比课程编号2的成绩高的所有同学的学号、姓名

select a.sno, a.sname from student a,

(select sno, score from sc where cno = 1) b,

(select sno, score from sc where cno = 2) c

where b.score > c.score and b.sno = c.sno and a.sno = b.sno

109,查询所有课程成绩小于60分的同学的学号、姓名

select sno,sname from student

where sno not in (select distinct sno from sc where score > 60)

110,查询至少有一门课程与学号为1的同学所学课程相同的同学的学号和姓名

select distinct a.sno, a.sname

from student a, sc b

where a.sno <> 1 and a.sno=b.sno and

b.cno in (select cno from sc where sno = 1)

或者:

select s.sno,s.sname 

from student s,

(select sc.sno 

from sc

where sc.cno in (select sc1.cno from sc sc1 where sc1.sno=1)and sc.sno<>1

group by sc.sno)r1

where r1.sno=s.sno

111、把“sc”表中“王五”所教课的成绩都更改为此课程的平均成绩

update sc set score = (select avg(sc_2.score) from sc sc_2 wheresc_2.cno=sc.cno)

from course,teacher where course.cno=sc.cno and course.tno=teacher.tno andteacher.tname='王五'

112、查询和编号为2的同学学习的课程完全相同的其他同学学号和姓名

这一题分两步查:

第一步,

select sno

from sc

where sno <> 2

group by sno

having sum(cno) = (select sum(cno) from sc where sno = 2)

第二步,

select b.sno, b.sname

from sc a, student b

where b.sno <> 2 and a.sno = b.sno

group by b.sno, b.sname

having sum(cno) = (select sum(cno) from sc where sno = 2)

113、删除学习“王五”老师课的sc表记录

delete sc from course, teacher

where course.cno = sc.cno and course.tno = teacher.tno and tname = '王五'

114、向 sc 表中插入一些记录,这些记录要求符合以下条件:

将没有课程 3 成绩同学的该成绩补齐, 其成绩取所有学生的课程 2 的平均成绩

insert sc select sno, 3, (select avg(score) from sc where cno = 2)

from student

where sno not in (select sno from sc where cno = 3)

115、按平平均分从高到低显示所有学生的如下统计报表:

– 学号,企业管理,马克思,UML,数据库,物理,课程数,平均分

select sno as 学号

,max(case when cno = 1 then score end) AS 企业管理

,max(case when cno = 2 then score end) AS 马克思

,max(case when cno = 3 then score end) AS UML

,max(case when cno = 4 then score end) AS 数据库

,max(case when cno = 5 then score end) AS 物理

,count(cno) AS 课程数

,avg(score) AS 平均分

FROM sc

GROUP by sno

ORDER by avg(score) DESC

116、查询各科成绩最高分和最低分:

以如下形式显示:课程号,最高分,最低分

select cno as 课程号, max(score) as 最高分, min(score) 最低分

from sc group by cno

select  course.cno as '课程号'

,MAX(score) as '最高分'

,MIN(score) as '最低分'

from sc,course

where sc.cno=course.cno

group by course.cno

117、按各科平均成绩从低到高和及格率的百分数从高到低顺序

SELECT t.cno AS 课程号,

max(course.cname)AS 课程名,

isnull(AVG(score),0) AS 平均成绩,

100 * SUM(CASE WHEN isnull(score,0)>=60 THEN 1 ELSE 0 END)/count(1) AS 及格率

FROM sc t, course

where t.cno = course.cno

GROUP BY t.cno

ORDER BY 及格率 desc

118、查询如下课程平均成绩和及格率的百分数(用”1行”显示):

企业管理(001),马克思(002),UML (003),数据库(004)

select 

avg(case when cno = 1 then score end) as 平均分1,

avg(case when cno = 2 then score end) as 平均分2,

avg(case when cno = 3 then score end) as 平均分3,

avg(case when cno = 4 then score end) as 平均分4,

100 * sum(case when cno = 1 and score > 60 then 1 else 0 end) / sum(casewhen cno = 1 then 1 else 0 end) as 及格率1,

100 * sum(case when cno = 2 and score > 60 then 1 else 0 end) / sum(casewhen cno = 2 then 1 else 0 end) as 及格率2,

100 * sum(case when cno = 3 and score > 60 then 1 else 0 end) / sum(casewhen cno = 3 then 1 else 0 end) as 及格率3,

100 * sum(case when cno = 4 and score > 60 then 1 else 0 end) / sum(casewhen cno = 4 then 1 else 0 end) as 及格率4

from sc

119、查询不同老师所教不同课程平均分, 从高到低显示

select max(c.tname) as 教师, max(b.cname) 课程, avg(a.score) 平均分

from sc a, course b, teacher c

where a.cno = b.cno and b.tno = c.tno

group by a.cno

order by 平均分 desc

或者:

select r.tname as '教师',r.rname as '课程' , AVG(score) as '平均分'

from sc,

(select  t.tname,c.cno as rcso,c.cname as rname

from teacher t ,course c

where t.tno=c.tno)r

where sc.cno=r.rcso

group by sc.cno,r.tname,r.rname 

order by AVG(score) desc

120、查询如下课程成绩均在第3名到第6名之间的学生的成绩:

– [学生ID],[学生姓名],企业管理,马克思,UML,数据库,平均成绩

select top 6 max(a.sno) 学号, max(b.sname) 姓名,

max(case when cno = 1 then score end) as 企业管理,

max(case when cno = 2 then score end) as 马克思,

max(case when cno = 3 then score end) as UML,

max(case when cno = 4 then score end) as 数据库,

avg(score) as 平均分

from sc a, student b

where a.sno not in 

(select top 2 sno from sc where cno = 1 order by score desc)

  and a.sno not in (select top 2 sno from sc where cno = 2 order by scoredesc)

  and a.sno not in (select top 2 sno from sc where cno = 3 order by scoredesc)

  and a.sno not in (select top 2 sno from sc where cno = 4 order by scoredesc)

  and a.sno = b.sno

group by a.sno

121,什么是线程?

线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。程序员可以通过它进行多处理器编程,你可以使用多线程对运算密集型任务提速。比如,如果一个线程完成一个任务要 100 毫秒,那么用十个线程完成改任务只需 10 毫秒。

122,线程和进程有什么区别?

线程是进程的子集,一个进程可以有很多线程,每条线程并行执行不同的任务。不同的进程使用不同的内存空间,而所有的线程共享一片相同的内存空间。每个线程都拥有单独的栈内存用来存储本地数据。

123,如何在 Java 中实现线程?

两种方式:java.lang.Thread 类的实例就是一个线程但是它需要调用 java.lang.Runnable 接口来执行,由于线程类本身就是调用的 Runnable 接口所以你可以继承java.lang.Thread 类或者直接调用 Runnable 接口来重写 run() 方法实现线程。

124,Java 关键字 volatile 与 synchronized 作用与区别?

1,volatile

它所修饰的变量不保留拷贝,直接访问主内存中的。

在 Java 内存模型中,有 main memory,每个线程也有自己的 memory (例如寄存器)。为了性能,一个线程会在自己的 memory 中保持要访问的变量的副本。这样就会出现同一个变量在某个瞬间,在一个线程的 memory 中的值可能与另外一个线程 memory 中的值,或者 main memory 中的值不一致的情况。 一个变量声明为 volatile,就意味着这个变量是随时会被其他线程修改的,因此不能将它 cache 在线程 memory 中。

2,synchronized

当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。

  • 一、当两个并发线程访问同一个对象 object 中的这个 synchronized(this) 同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。

  • 二、然而,当一个线程访问 object 的一个 synchronized(this) 同步代码块时,另一个线程仍然可以访问该 object 中的非 synchronized(this) 同步代码块。

  • 三、尤其关键的是,当一个线程访问 object 的一个 synchronized(this) 同步代码块时,其他线程对 object 中所有其它 synchronized(this) 同步代码块的访问将被阻塞。

  • 四、当一个线程访问 object 的一个 synchronized(this) 同步代码块时,它就获得了这个 object 的对象锁。结果,其它线程对该 object 对象所有同步代码部分的访问都被暂时阻塞。

  • 五、以上规则对其它对象锁同样适用.

125,有哪些不同的线程生命周期?

当我们在 Java 程序中新建一个线程时,它的状态是 New。当我们调用线程的 start() 方法时,状态被改变为 Runnable。线程调度器会为 Runnable 线程池中的线程分配 CPU 时间并且讲它们的状态改变为 Running。其他的线程状态还有 Waiting,Blocked 和 Dead。

126,你对线程优先级的理解是什么?

每一个线程都是有优先级的,一般来说,高优先级的线程在运行时会具有优先权,但这依赖于线程调度的实现,这个实现是和操作系统相关的(OS dependent)。我们可以定义线程的优先级,但是这并不能保证高优先级的线程会在低优先级的线程前执行。线程优先级是一个 int 变量(从1-10),1 代表最低优先级,10 代表最高优先级。

127,什么是死锁(Deadlock)?如何分析和避免死锁?

死锁是指两个以上的线程永远阻塞的情况,这种情况产生至少需要两个以上的线程和两个以上的资源。

分析死锁,我们需要查看 Java 应用程序的线程转储。我们需要找出那些状态为 BLOCKED 的线程和他们等待的资源。每个资源都有一个唯一的 id,用这个 id 我们可以找出哪些线程已经拥有了它的对象锁。

避免嵌套锁,只在需要的地方使用锁和避免无限期等待是避免死锁的通常办法。

128,什么是线程安全?Vector 是一个线程安全类吗?

如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。一个线程安全的计数器类的同一个实例对象在被多个线程使用的情况下也不会出现计算失误。很显然你可以将集合类分成两组,线程安全和非线程安全的。Vector 是用同步方法来实现线程安全的, 而和它相似的 ArrayList 不是线程安全的。

129,Java 中如何停止一个线程?

Java 提供了很丰富的 API 但没有为停止线程提供 API。JDK 1.0 本来有一些像 stop(), suspend() 和 resume()的控制方法但是由于潜在的死锁威胁因此在后续的 JDK 版本中他们被弃用了,之后 Java API 的设计者就没有提供一个兼容且线程安全的方法来停止一个线程。当 run() 或者 call() 方法执行完的时候线程会自动结束,如果要手动结束一个线程,你可以用 volatile 布尔变量来退出 run()方法的循环或者是取消任务来中断线程

130,什么是 ThreadLocal?

ThreadLocal 用于创建线程的本地变量,我们知道一个对象的所有线程会共享它的全局变量,所以这些变量不是线程安全的,我们可以使用同步技术。但是当我们不想使用同步的时候,我们可以选择 ThreadLocal 变量。

每个线程都会拥有他们自己的 Thread 变量,它们可以使用 get()\set() 方法去获取他们的默认值或者在线程内部改变他们的值。ThreadLocal 实例通常是希望它们同线程状态关联起来是 private static 属性。

131,Sleep()、suspend()和 wait()之间有什么区别?

Thread.sleep() 使当前线程在指定的时间处于“非运行”(Not Runnable)状态。线程一直持有对象的监视器。比如一个线程当前在一个同步块或同步方法中,其它线程不能进入该块或方法中。如果另一线程调用了 interrupt()方法,它将唤醒那个“睡眠的”线程。

注意:sleep()是一个静态方法。这意味着只对当前线程有效,一个常见的错误是调用 t.sleep(),(这里的 t 是一个不同于当前线程的线程)。即便是执行 t.sleep(),也是当前线程进入睡眠,而不是 t 线程。t.suspend()是过时的方法,使用 suspend()导致线程进入停滞状态,该线程会一直持有对象的监视器,suspend()容易引起死锁问题。

object.wait()使当前线程出于“不可运行”状态,和 sleep()不同的是 wait 是 object 的方法而不是 thread。调用 object.wait()时,线程先要获取这个对象的对象锁,当前线程必须在锁对象保持同步,把当前线程添加到等待队列中,随后另一线程可以同步同一个对象锁来调用 object.notify(),这样将唤醒原来等待中的线程,然后释放该锁。基本上 wait()/notify()与 sleep()/interrupt()类似,只是前者需要获取对象锁。

132,什么是线程饿死,什么是活锁?

当所有线程阻塞,或者由于需要的资源无效而不能处理,不存在非阻塞线程使资源可用。

JavaAPI 中线程活锁可能发生在以下情形:

1,当所有线程在程序中执行 Object.wait(0),参数为 0 的 wait 方法。程序将发生活锁直到在相应的对象上有线程调用 Object.notify()或者 Object.notifyAll()。

2,当所有线程卡在无限循环中。

133,什么是 Java Timer 类?如何创建一个有特定时间间隔的任务?

java.util.Timer 是一个工具类,可以用于安排一个线程在未来的某个特定时间执行。Timer 类可以用安排一次性任务或者周期任务。

java.util.TimerTask 是一个实现了 Runnable 接口的抽象类,我们需要去继承这个类来创建我们自己的定时任务并使用 Timer 去安排它的执行。

134,Java 中的同步集合与并发集合有什么区别?

同步集合与并发集合都为多线程和并发提供了合适的线程安全的集合,不过并发集合的可扩展性更高。

在 Java1.5 之前程序员们只有同步集合来用且在多线程并发的时候会导致争用,阻碍了系统的扩展性。

Java5 介绍了并发集合像 ConcurrentHashMap,不仅提供线程安全还用锁分离和内部分区等现代技术提高了可扩展性。

135,同步方法和同步块,哪个是更好的选择?

同步块是更好的选择,因为它不会锁住整个对象(当然你也可以让它锁住整个对象)。同步方法会锁住整个对象,哪怕这个类中有多个不相关联的同步块,这通常会导致他们停止执行并需要等待获得这个对象上的锁。

136,什么是线程池? 为什么要使用它?

创建线程要花费昂贵的资源和时间,如果任务来了才创建线程那么响应时间会变长,而且一个进程能创建的线程数有限。

为了避免这些问题,在程序启动的时候就创建若干线程来响应处理,它们被称为线程池,里面的线程叫工作线程。

从 JDK1.5 开始,Java API 提供了 Executor 框架让你可以创建不同的线程池。比如单线程池,每次处理一个任务;数目固定的线程池或者是缓存线程池(一个适合很多生存期短的任务的程序的可扩展线程池)。

137,Java 中 invokeAndWait 和 invokeLater 有什么区别?

这两个方法是 Swing API 提供给 Java 开发者用来从当前线程而不是事件派发线程更新 GUI 组件用的。InvokeAndWait()同步更新 GUI 组件,比如一个进度条,一旦进度更新了,进度条也要做出相应改变。如果进度被多个线程跟踪,那么就调用 invokeAndWait()方法请求事件派发线程对组件进行相应更新。而 invokeLater()方法是异步调用更新组件的。

138,多线程中的忙循环是什么?

忙循环就是程序员用循环让一个线程等待,不像传统方法 wait(), sleep() 或 yield() 它们都放弃了 CPU 控制,而忙循环不会放弃 CPU,它就是在运行一个空循环。这么做的目的是为了保留 CPU 缓存。

在多核系统中,一个等待线程醒来的时候可能会在另一个内核运行,这样会重建缓存。为了避免重建缓存和减少等待重建的时间就可以使用它了。

139. Java中的泛型是什么 ? 使用泛型的好处是什么?

泛型是 Java SE 1.5 的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。

好处:

  • 1、类型安全,提供编译期间的类型检测

  • 2、前后兼容

  • 3、泛化代码,代码可以更多的重复利用

  • 4、性能较高,用 GJ(泛型JAVA)编写的代码可以为 java 编译器和虚拟机带来更多的类型信息,这些信息对 java 程序做进一步优化提供条件。

140,Java 的泛型是如何工作的 ? 什么是类型擦除 ?如何工作?

  • 1、类型检查:在生成字节码之前提供类型检查

  • 2、类型擦除:所有类型参数都用他们的限定类型替换,包括类、变量和方法(类型擦除)

  • 3、如果类型擦除和多态性发生了冲突时,则在子类中生成桥方法解决

  • 4、如果调用泛型方法的返回类型被擦除,则在调用该方法时插入强制类型转换

类型擦除:

所有类型参数都用他们的限定类型替换:

比如

T->Object   ? extends BaseClass->BaseClass

如何工作:

泛型是通过类型擦除来实现的,编译器在编译时擦除了所有类型相关的信息,所以在运行时不存在任何类型相关的信息。例如 List 在运行时仅用一个 List 来表示。这样做的目的,是确保能和 Java 5 之前的版本开发二进制类库进行兼容。你无法在运行时访问到类型参数,因为编译器已经把泛型类型转换成了原始类型。根据你对这个泛型问题的回答情况,你会得到一些后续提问,比如为什么泛型是由类型擦除来实现的或者给你展示一些会导致编译器出错的错误泛型代码。

141,你可以把 List传递给一个接受 List参数的方法吗?

对任何一个不太熟悉泛型的人来说,这个 Java 泛型题目看起来令人疑惑,因为乍看起来 String 是一种 Object,所以 List应当可以用在需要 List的地方,但是事实并非如此。真这样做的话会导致编译错误。如果你再深一步考虑,你会发现 Java 这样做是有意义的,因为 List可以存储任何类型的对象包括 String, Integer等等,而 List却只能用来存储 String s。

List<Object> objectList;

List<String> stringList;

objectList = stringList; //compilation error incompatible types

142,如何阻止 Java 中的类型未检查的警告?

如果你把泛型和原始类型混合起来使用,例如下列代码,java 5 的 javac 编译器会产生类型未检查的警告,例如

List<String> rawList = newArrayList()

注意: Hello.java 使用了未检查或称为不安全的操作;

这种警告可以使用 @SuppressWarnings(“unchecked”) 注解来屏蔽。

143,Java 中 List和原始类型 List 之间的区别?

原始类型和带参数类型 之间的主要区别是,在编译时编译器不会对原始类型进行类型安全检查,却会对带参数的类型进行检查,通过使用 Object 作为类型,可以告知编译器该方法可以接受任何类型的对象,比如 String 或 Integer。

这道题的考察点在于对泛型中原始类型的正确理解。它们之间的第二点区别是,你可以把任何带参数的类型传递给原始类型 List,但却不能把 List 传递给接受 List 的方法,因为会产生编译错误。

144,编写一段泛型程序来实现 LRU 缓存?

对于喜欢 Java 编程的人来说这相当于是一次练习。给你个提示,LinkedHashMap 可以用来实现固定大小的 LRU 缓存,当 LRU 缓存已经满了的时候,它会把最老的键值对移出缓存。

LinkedHashMap 提供了一个称为 removeEldestEntry() 的方法,该方法会被 put() 和 putAll()调用来删除最老的键值对。当然,如果你已经编写了一个可运行的 JUnit 测试,你也可以随意编写你自己的实现代码。

145,Array 中可以用泛型吗?

这可能是 Java 泛型面试题中最简单的一个了,当然前提是你要知道 Array 事实上并不支持泛型,这也是为什么 Joshua Bloch 在 Effective Java 一书中建议使用 List 来代替 Array,因为 List 可以提供编译期的类型安全保证,而 Array 却不能。

146,如何编写一个泛型方法,让它能接受泛型参数并返回泛型类型?

编写泛型方法并不困难,你需要用泛型类型来替代原始类型,比如使用T, E or K,V等被广泛认可的类型占位符。最简单的情况下,一个泛型方法可能会像这样:

public V put(K key, V value) {

    return cahe.put(key,value);

}

147,C++ 模板和 java 泛型之间有何不同?

java 泛型实现根植于“类型消除”这一概念。当源代码被转换为 Java 虚拟机字节码时,这种技术会消除参数化类型。有了 Java 泛型,我们可以做的事情也并没有真正改变多少;他只是让代码变得漂亮些。鉴于此,Java 泛型有时也被称为“语法糖”。

这和 C++ 模板截然不同。在 C++ 中,模板本质上就是一套宏指令集,只是换了个名头,编译器会针对每种类型创建一份模板代码的副本。

由于架构设计上的差异,Java 泛型和 C++ 模板有很多不同点:

C++ 模板可以使用 int 等基本数据类型。Java 则不行,必须转而使用 Integer。

在 Java 中,可以将模板的参数类型限定为某种特定类型。

在 C++ 中,类型参数可以实例化,但 java 不支持。

在 Java 中,类型参数不能用于静态方法( ? )和变量,因为它们会被不同类型参数指定的实例共享。在 C++,这些类时不同的,因此类型参数可以用于静态方法和静态变量。

在 Java 中,不管类型参数是什么,所有的实例变量都是同一类型。类型参数会在运行时被抹去。在 C++ 中,类型参数不同,实例变量也不同。

148,AJAX有哪些有点和缺点?

优点:

  • 1、最大的一点是页面无刷新,用户的体验非常好。

  • 2、使用异步方式与服务器通信,具有更加迅速的响应能力。

  • 3、可以把以前一些服务器负担的工作转嫁到客户端,利用客户端闲置的能力来处理,减轻服务器和带宽的负担,节约空间和宽带租用成本。并且减轻服务器的负担,ajax 的原则是“按需取数据”,可以最大程度的减少冗余请求,和响应对服务器造成的负担。

  • 4、基于标准化的并被广泛支持的技术,不需要下载插件或者小程序。

缺点:

  • 1、ajax 不支持浏览器 back 按钮。

  • 2、安全问题 AJAX 暴露了与服务器交互的细节。

  • 3、对搜索引擎的支持比较弱。

  • 4、破坏了程序的异常机制。

  • 5、不容易调试。

149,AJAX 应用和传统 Web 应用有什么不同?

在传统的 Javascript 编程中,如果想得到服务器端数据库或文件上的信息,或者发送客户端信息到服务器,需要建立一个 HTML form 然后 GET 或者 POST 数据到服务器端。用户需要点击” Submit ”按钮来发送或者接受数据信息,然后等待服务器响应请求,页面重新加载。

因为服务器每次都会返回一个新的页面, 所以传统的 web 应用有可能很慢而且用户交互不友好。

使用 AJAX 技术, 就可以使 Javascript 通过 XMLHttpRequest 对象直接与服务器进行交互。

通过 HTTP Request, 一个 web 页面可以发送一个请求到 web 服务器并且接受 web 服务器返回的信息(不用重新加载页面),展示给用户的还是同一个页面,用户感觉不到页面刷新,也看不到到 Javascript 后台进行的发送请求和接受响应,体验非常好。

150,Ajax 的实现流程是怎样的?

  • (1)创建 XMLHttpRequest 对象,也就是创建一个异步调用对象.

  • (2)创建一个新的 HTTP 请求,并指定该 HTTP 请求的方法、URL 及验证信息.

  • (3)设置响应 HTTP 请求状态变化的函数.

  • (4)发送 HTTP 请求.

  • (5)获取异步调用返回的数据.

  • (6)使用 JavaScript 和 DOM 实现局部刷新.

具体一点:

  • 1,创建 XNLHttpRequest 对象

    (不考虑 ie)XMLHttpRequest request = new XMLHttprequest();
    
  • 2,创建新的 Http 请求

    XMLHttprequest.open(method,url,flag,name,password);
    
  • 3,设置响应 Http 请求变化的函数

    XMLHttprequest.onreadystatechange=getData;
    
    function getData(){
    
        if(XMLHttprequest.readyState==4){
    
            获取数据
    
        }
    
    }
    
  • 4,发送 http 请求

    XMLHttprequest.send(data);
    
  • 5,获取异步调用返回的对象

    function(data){
    
        //异步提交后,交互成功,返回的data便是异步调用返回的对象,该对象是一个string类型的
    
    }
    
  • 6,使用 js、DOM 实现局部刷新

    myDiv.innerHTML=''这是刷新后的数据''
    

151,简单说一下数据库的三范式?

  • 第一范式:数据库表的每一个字段都是不可分割的

  • 第二范式:数据库表中的非主属性只依赖于主键

  • 第三范式:不存在非主属性对关键字的传递函数依赖关系

152,Java 集合框架是什么?说出一些集合框架的优点?

每种编程语言中都有集合,最初的 Java 版本包含几种集合类:Vector、Stack、HashTable 和 Array。

随着集合的广泛使用,Java1.2 提出了囊括所有集合接口、实现和算法的集合框架。在保证线程安全的情况下使用泛型和并发集合类,Java 已经经历了很久。它还包括在 Java 并发包中,阻塞接口以及它们的实现。

集合框架的部分优点如下:

  • (1)使用核心集合类降低开发成本,而非实现我们自己的集合类。

  • (2)随着使用经过严格测试的集合框架类,代码质量会得到提高。

  • (3)通过使用 JDK 附带的集合类,可以降低代码维护成本。

  • (4)复用性和可操作性。

153,Java 集合框架的基础接口有哪些?

Collection 为集合层级的根接口。一个集合代表一组对象,这些对象即为它的元素。Java 平台不提供这个接口任何直接的实现。

Set 是一个不能包含重复元素的集合。这个接口对数学集合抽象进行建模,被用来代表集合,就如一副牌。

List 是一个有序集合,可以包含重复元素。你可以通过它的索引来访问任何元素。List 更像长度动态变换的数组。

Map 是一个将 key 映射到 value 的对象.一个 Map 不能包含重复的 key:每个 key 最多只能映射一个 value。

一些其它的接口有 Queue、Dequeue、SortedSet、SortedMap 和 ListIterator。

154,集合框架中的泛型有什么优点?

Java1.5 引入了泛型,所有的集合接口和实现都大量地使用它。泛型允许我们为集合提供一个可以容纳的对象类型。

因此,如果你添加其它类型的任何元素,它会在编译时报错。这避免了在运行时出现 ClassCastException,因为你将会在编译时得到报错信息。

泛型也使得代码整洁,我们不需要使用显式转换和 instanceOf 操作符。它也给运行时带来好处,因为不会产生类型检查的字节码指令。

155,Enumeration 和 Iterator 接口的区别?

Enumeration 的速度是 Iterator 的两倍,也使用更少的内存。Enumeration 是非常基础的,也满足了基础的需要。

但是,与 Enumeration 相比,Iterator 更加安全,因为当一个集合正在被遍历的时候,它会阻止其它线程去修改集合。

迭代器取代了 Java 集合框架中的 Enumeration。迭代器允许调用者从集合中移除元素,而 Enumeration 不能做到。为了使它的功能更加清晰,迭代器方法名已经经过改善。

156,Iterater 和 ListIterator 之间有什么区别?

  • 1,我们可以使用 Iterator 来遍历 Set 和 List 集合,而 ListIterator 只能遍历 List。

  • 2,Iterator 只可以向前遍历,而 ListIterator 可以双向遍历。

  • 3,ListIterator 从 Iterator 接口继承,然后添加了一些额外的功能,比如添加一个元素、替换一个元素、获取前面或后面元素的索引位置。

157,我们如何对一组对象进行排序?

如果我们需要对一个对象数组进行排序,我们可以使用 Arrays.sort() 方法。如果我们需要排序一个对象列表,我们可以使用 Collection.sort() 方法。

两个类都有用于自然排序(使用 Comparable)或基于标准的排序(使用 Comparator)的重载方法 sort()。

Collections 内部使用数组排序方法,所有它们两者都有相同的性能,只是 Collections 需要花时间将列表转换为数组。

158,与 Java 集合框架相关的有哪些最好的实践?

  • 1,根据需要选择正确的集合类型。比如,如果指定了大小,我们会选用 Array 而非 ArrayList。如果我们想根据插入顺序遍历一个 Map,我们需要使用 TreeMap。如果我们不想重复,我们应该使用 Set。

  • 2,一些集合类允许指定初始容量,所以如果我们能够估计到存储元素的数量,我们可以使用它,就避免了重新哈希或大小调整。

  • 3,基于接口编程,而非基于实现编程,它允许我们后来轻易地改变实现。

  • 4,总是使用类型安全的泛型,避免在运行时出现 ClassCastException。

  • 5,使用 JDK 提供的不可变类作为 Map 的 key,可以避免自己实现 hashCode()和 equals()。

  • 6,尽可能使用 Collections 工具类,或者获取只读、同步或空的集合,而非编写自己的实现。它将会提供代码重用性,它有着更好的稳定性和可维护性。

159,什么是事务?

事务是恢复和并发控制的基本单位

事务的四个基本特征

原子性,一致性,隔离性,持久性

  • 原子性和一致性差不多,意思是要么全部成功,要么就失败

  • 一致性是说,从一个一致性状态到另一个一致性状态

  • 隔离性是说一个事务执行的过程中不能被另一个事务干扰

  • 持久性也就是事务一旦提交,他对数据库中数据的改变就应该是永久的,不能变的

160,Java 内存模型是什么?

Java 内存模型规定和指引 Java 程序在不同的内存架构、CPU 和操作系统间有确定性地行为。它在多线程的情况下尤其重要。Java 内存模型对一个线程所做的变动能被其它线程可见提供了保证,它们之间是先行发生关系。这个关系定义了一些规则让程序员在并发编程时思路更清晰。比如,先行发生关系确保了:

线程内的代码能够按先后顺序执行,这被称为程序次序规则。

对于同一个锁,一个解锁操作一定要发生在时间上后发生的另一个锁定操作之前,也叫做管程锁定规则。

前一个对 volatile 的写操作在后一个 volatile 的读操作之前,也叫 volatile 变量规则。

一个线程内的任何操作必需在这个线程的 start() 调用之后,也叫作线程启动规则。

一个线程的所有操作都会在线程终止之前,线程终止规则。

一个对象的终结操作必需在这个对象构造完成之后,也叫对象终结规则。

可传递性

更多介绍可以移步并发编程网:

(深入理解java内存模型系列文章:http://ifeve.com/java-memory-model-0

161,Java 中 interrupted 和 isInterruptedd 方法的区别?

interrupted() 和 isInterrupted() 的主要区别是前者会将中断状态清除而后者不会。Java 多线程的中断机制是用内部标识来实现的,调用 Thread.interrupt() 来中断一个线程就会设置中断标识为 true。当中断线程调用静态方法 Thread.interrupted() 来检查中断状态时,中断状态会被清零。

非静态方法 isInterrupted() 用来查询其它线程的中断状态且不会改变中断状态标识。简单的说就是任何抛出 InterruptedException 异常的方法都会将中断状态清零。无论如何,一个线程的中断状态都有可能被其它线程调用中断来改变。

162,Java 中的同步集合与并发集合有什么区别?

同步集合与并发集合都为多线程和并发提供了合适的线程安全的集合,不过并发集合的可扩展性更高。在 Java1.5 之前程序员们只有同步集合来用且在多线程并发的时候会导致争用,阻碍了系统的扩展性。Java5 介绍了并发集合像 ConcurrentHashMap,不仅提供线程安全还用锁分离和内部分区等现代技术提高了可扩展性。

不管是同步集合还是并发集合他们都支持线程安全,他们之间主要的区别体现在性能和可扩展性,还有他们如何实现的线程安全上。

同步 HashMap, Hashtable, HashSet, Vector, ArrayList 相比他们并发的实现(ConcurrentHashMap, CopyOnWriteArrayList, CopyOnWriteHashSet)会慢得多。造成如此慢的主要原因是锁, 同步集合会把整个 Map 或 List 锁起来,而并发集合不会。并发集合实现线程安全是通过使用先进的和成熟的技术像锁剥离。

比如 ConcurrentHashMap 会把整个 Map 划分成几个片段,只对相关的几个片段上锁,同时允许多线程访问其他未上锁的片段。

同样的,CopyOnWriteArrayList 允许多个线程以非同步的方式读,当有线程写的时候它会将整个 List 复制一个副本给它。

如果在读多写少这种对并发集合有利的条件下使用并发集合,这会比使用同步集合更具有可伸缩性。

163,什么是线程池? 为什么要使用它?

创建线程要花费昂贵的资源和时间,如果任务来了才创建线程那么响应时间会变长,而且一个进程能创建的线程数有限。为了避免这些问题,在程序启动的时候就创建若干线程来响应处理,它们被称为线程池,里面的线程叫工作线程。从 JDK1.5 开始,Java API 提供了 Executor 框架让你可以创建不同的线程池。比如单线程池,每次处理一个任务;数目固定的线程池或者是缓存线程池(一个适合很多生存期短的任务的程序的可扩展线程池)

线程池的作用,就是在调用线程的时候初始化一定数量的线程,有线程过来的时候,先检测初始化的线程还有空的没有,没有就再看当前运行中的线程数是不是已经达到了最大数,如果没有,就新分配一个线程去处理。

就像餐馆中吃饭一样,从里面叫一个服务员出来;但如果已经达到了最大数,就相当于服务员已经用尽了,那没得办法,另外的线程就只有等了,直到有新的“服务员”为止。

线程池的优点就是可以管理线程,有一个高度中枢,这样程序才不会乱,保证系统不会因为大量的并发而因为资源不足挂掉。

164,Java 中活锁和死锁有什么区别?

活锁:一个线程通常会有会响应其他线程的活动。如果其他线程也会响应另一个线程的活动,那么就有可能发生活锁。同死锁一样,发生活锁的线程无法继续执行。然而线程并没有阻塞——他们在忙于响应对方无法恢复工作。这就相当于两个在走廊相遇的人:甲向他自己的左边靠想让乙过去,而乙向他的右边靠想让甲过去。可见他们阻塞了对方。甲向他的右边靠,而乙向他的左边靠,他们还是阻塞了对方。

死锁:两个或更多线程阻塞着等待其它处于死锁状态的线程所持有的锁。死锁通常发生在多个线程同时但以不同的顺序请求同一组锁的时候,死锁会让你的程序挂起无法完成任务。

165,如何避免死锁?

死锁的发生必须满足以下四个条件:

互斥条件:一个资源每次只能被一个进程使用。

请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。

不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。

循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

三种用于避免死锁的技术:

加锁顺序(线程按照一定的顺序加锁)

加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁)

死锁检测

(死锁原因及如何避免更深理解移步:http://blog.csdn.net/ls5718/article/details/51896159

166,notify() 和 notifyAll()有什么区别?

  • 1,notify() 和 notifyAll()都是 Object 对象用于通知处在等待该对象的线程的方法。

  • 2,void notify(): 唤醒一个正在等待该对象的线程。

  • 3,void notifyAll(): 唤醒所有正在等待该对象的线程。

两者的最大区别在于:

notifyAll 使所有原来在该对象上等待被 notify 的线程统统退出 wait 的状态,变成等待该对象上的锁,一旦该对象被解锁,他们就会去竞争。

notify 他只是选择一个 wait 状态线程进行通知,并使它获得该对象上的锁,但不惊动其他同样在等待被该对象 notify 的线程们,当第一个线程运行完毕以后释放对象上的锁,此时如果该对象没有再次使用 notify 语句,即便该对象已经空闲,其他 wait 状态等待的线程由于没有得到该对象的通知,继续处在 wait 状态,直到这个对象发出一个 notify 或 notifyAll,它们等待的是被 notify 或 notifyAll,而不是锁。

167,什么是可重入锁(ReentrantLock)? ##

Java.util.concurrent.lock 中的 Lock 框架是锁定的一个抽象,它允许把锁定的实现作为 Java 类,而不是作为语言的特性来实现。这就为 Lock 的多种实现留下了空间,各种实现可能有不同的调度算法、性能特性或者锁定语义。

ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,但是添加了类似锁投票、定时锁等候和可中断锁等候的一些特性。此外,它还提供了在激烈争用情况下更佳的性能。(换句话说,当许多线程都想访问共享资源时,JVM 可以花更少的时候来调度线程,把更多时间用在执行线程上。)

Reentrant 锁意味着什么呢?简单来说,它有一个与锁相关的获取计数器,如果拥有锁的某个线程再次得到锁,那么获取计数器就加 1,然后锁需要被释放两次才能获得真正释放。这模仿了 synchronized 的语义;如果线程进入由线程已经拥有的监控器保护的 synchronized 块,就允许线程继续进行,当线程退出第二个(或者后续)synchronized 块的时候,不释放锁,只有线程退出它进入的监控器保护的第一个 synchronized 块时,才释放锁。

168,读写锁可以用于什么应用场景?

读写锁可以用于 “多读少写” 的场景,读写锁支持多个读操作并发执行,写操作只能由一个线程来操作 ReadWriteLock 对向数据结构相对不频繁地写入,但是有多个任务要经常读取这个数据结构的这类情况进行了优化。ReadWriteLock 使得你可以同时有多个读取者,只要它们都不试图写入即可。如果写锁已经被其他任务持有,那么任何读取者都不能访问,直至这个写锁被释放为止。

ReadWriteLock 对程序性能的提高主要受制于如下几个因素:

  • 1,数据被读取的频率与被修改的频率相比较的结果。

  • 2,读取和写入的时间

  • 3,有多少线程竞争

  • 4,是否在多处理机器上运行

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值