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语句,即可跳出外层循环。

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

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

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,所以,这些类型及其包装类也是可以的。特例,Java1.7以后,switch语句支持String。综上可知,long类型不符合switch的语法规定,并且不能隐式转换成int类型,故不能作用于switch语句。

6、short s1=1;s1=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编码字符集中包含了汉字,故可以存储。不过,个别特殊汉字未被包含,即不可存储某些特殊汉字。补充说明:Unicode编码占用两个字节,因此,char类型的变量也是占用两个字节。

8、最有效率的方法算出2的三次方?

  2 << 3。左移三位,因为将一个数左移n位,就相当于乘以了2的n次方,且位运算cpu直接支持的,效率最高。

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

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

final StringBuffer a=new StringBuffer("immutable");

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

a=new StringBuffer("");

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

a.append(" broken!");

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

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

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

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

public class VariantTest{
        publicstatic int staticVar = 0;
        publicint 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。

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

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

  Math类中提供了三个与取整有关的方法:ceil 向上取整、floor 向下取整 、round 四舍五入,这些方法的作用与它们的英文名称的含义相对应。故,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)接口?抽象类是否可继承具体类(concrete class)?抽象类中是否可以有静态的main方法?

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

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

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

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

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

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

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

  3.抽象类中可以包含非抽象的普通方法,Java 1.8后,接口可以包含一个default的实现方法,其余必须为抽象的普通方法。(java.util.List接口,新增sort(),可直接传参比较器Comparator)。

  4. 抽象类中的抽象方法的访问类型可以是public,protected,但接口中的抽象方法只能是public类型的,并且默认即为public abstract类型。

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

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

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

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

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

   native方法表示该方法要用另外一种依赖平台的编程语言实现的,不存在着被子类实现的问题,所以,它也不能是抽象的,不能与abstract混用。

  关于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不允许。

  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(intindex,Obj e)方法,就可以指定当前对象在集合中的存放位置。一个对象可以被反复存储进List中,每调用一次add方法,这个对象就被插入进集合中一次,其实,并不是把这个对象本身存储进了集合中,而是在集合中用一个索引变量指向这个对象,当这个对象被add多次时,即相当于集合中有多个索引指向了这个对象,如图x所示。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不允许重复元素:

HashSet set = new HashSet(vector);

28、Collection和Collections的区别

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

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

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

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

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

  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");创建了几个String Object?是否可以继承String类?

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

32、String和StringBuffer的区别

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

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 classTest {
    public static void main(String[]args) {
       System.out.println(newTest().test());;
    }

    static int test() {
       intx = 1;
       try {
          returnx;
       } 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本身就提供了这种支持,可以调用ObjectOutputStream的writeObject方法来做,这时就需要被传输的对象必须实现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即Garbage Collection,内存处理是开发人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java提供的GC功能可以自动检测对象是否超过作用域从而达到自动回收内存的目的,Java语言没有提供释放已分配内存的显式操作方法。

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

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

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

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

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

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

  System.gc()和Runtime.getRuntime().gc(),但二者的行为没有任何不同,前者仅可以理解为后者的简写。其实基本没有什么机会用得到这个命令,因为这个命令只是建议JVM安排GC运行,还有可能完全被拒绝。GC本身是会周期性的自动运行的,由JVM决定运行的时机,而且现在的版本有多种更智能的模式可以选择,还会根据运行的机器自动去做选择,就算真的有性能上的需求,也应该去对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对象中),然后一直不再使用它,这个对象一直被缓存引用,但却不再被使用。

  同时,本地资源的释放,如Opencv中的VideoCapture、Mat等,IO、Connection等流资源的释放,都是内存泄漏的源头。

 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、什么是Cookie?Session和Cookie有什么区别?

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

  区别:

    1.Cookie数据存放在客户的浏览器上,session数据放在服务器上

    2.Cookie不是很安全,别人可以分析存放在本地的Cookie并进行Cookie欺骗,如果主要考虑到安全应当使用Session

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

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

53、Tomcat容器是如何创建Servlet类实例的?用到了什么原理?

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

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

54、JDBC访问数据库的基本操作是什么?

  1.加载驱动

  2.通过DriverManager对象获取连接对象Connection

  3.通过连接对象获取会话

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

  5.关闭资源

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

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

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

  事务处理步骤:

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

  4.conn.commit()提交事务

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

56、数据库连接池的原理,为什么要使用连接池?

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

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

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

57、JDBC的DriverManager是用来做什么的?

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

58、谈谈你对Spring的理解

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

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

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

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

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

59、使用Spring框架的好处是什么?

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

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

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

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

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

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

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

60、ApplicationContext通常的实现是什么?

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

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

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

61、解释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。

62、解释Spring框架中bean的生命周期

  1、Spring容器从XML文件中读取bean的定义,或者扫描实现@Component的类,并实例化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的postProcesserAfterInitialization方法将被调用。

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

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

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

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

  注意:使用$占位符可能会导致SQL注射攻击,能用#的地方就不要使用$。而$方式一般用于传入数据库对象,例如传入表名或列名,尤其是在写order by子句的时候应该用$而不是#。如:order by ${user_id},如果传入的值是111,那么解析成sql时的值为order by 111, 如果传入的值是id,则解析成的sql为order by id,而用#则为order by 'id',可以通过执行,但无效。

64、解释一下MyBatis中命名空间namespace的作用

   在大型项目中,可能存在大量的SQL语句,这时候为每个SQL语句起一个唯一的标识(ID)就变得并不容易了。为了解决这个问题,在MyBatis中,可以为每个映射文件起一个唯一的命名空间,这样定义在这个映射文件中的每个SQL语句就成了定义在这个命名空间中的一个ID。只要我们能够保证每个命名空间中这个ID是唯一的,即使在不同映射文件中的语句ID相同,也不会再产生冲突了。命名空间,一般命名为xml对应Mapper接口的参考路径名 reference path,com.rosetta.image.mapper.UserMapper。

65、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>

67、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对象。

68、简单说一下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) 教师表

69、查询课程一的成绩比课程二的成绩高的所有学生的学号

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

70、查询平均成绩大于60分的同学的学号和平均成绩

select a.sno as "学号", avg(a.score) as "平均成绩" 
from
(select sno,score from sc) a 
group by sno having avg(a.score)>60

71、查询所有同学的学号、姓名、选课书、总成绩

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

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

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='张三')

73、查询同时学过课程一和课程二的同学的学号、姓名

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)

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

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

75、查询课程编号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

76、查询至少有一门课程与学号为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

77、把 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='王五'

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

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

79、向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)

80、按平均分从高到低显示所有学生的如下统计报表:学号、企业管理、马克思、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

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

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

82、查询如下课程平均成绩和及格率的百分数(用“1行”显示):企业管理(1),马克思(2),UML(3),数据库(4)

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

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

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

84、查询如下课程成绩均在第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

85、什么是线程?

  线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。程序员可以通过它进行多处理器编程,可以使用多线程对运算密集型任务提速。

86、线程和进程有什么区别?

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

87、如何在Java中实现线程?

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

88、Java关键字volatile与synchronized作用与区别?

  1.volatile

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

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

  2.synchronized

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

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

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

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

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

    e、以上规则对其它对象锁同样适用。

 89、有哪些不同的线程生命周期?

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

90、你对线程优先级的理解是什么?

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

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

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

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

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

92、什么是线程安全?Vector是一个线程安全类吗?

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

 93、Java中如何停止一个线程?

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

94、什么是ThreadLocal?

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

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

 95、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()类似,只是前者需要获取对象锁。

96、什么是线程饿死,什么是活锁?

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

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

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

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

97、同步方法和同步块,哪个是更好地选择?

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

98、Java中的泛型是什么?使用泛型的好处是什么?

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

  好处:

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

    2.前后兼容

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

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

99、Java的泛型是如何工作的?什么是类型擦除?如何工作?

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

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

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

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

  类型擦除:所有类型参数都用他们的限定类型替换 ,比如:

T -> Object ? extends BaseClass -> BaseClass;

  如何工作:

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

100、可以把List<String>传递给一个接受List<Object>参数的方法吗?

  不可以,因为List<Object>可以存储任何类型的对象包括String, Integer等等,而List<String>却只能用来存储String s。

List<Object> objectList;
List<String> stringList;
objectList = stringList; //compilation error incompatible types

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

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

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

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

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

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

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

 102、Java内存模型是什么?

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

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

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

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

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

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

  一个对象的终结操作必须在这个对象构造完成之后,即对象终结原则。

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

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

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

  同步HashMap, Hashtable, HashSet, Vector, ArrayList 相比他们并发的实现(ConcurrentHashMap, CopyOnWriteArrayList, CopyOnWriteHashSet)会慢得多。主要原因是锁,同步集合会把整个Map或List锁起来,而并发集合不会。并发集合实现线程安全通过使用锁剥离等新手段。

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

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

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

104、什么是线程池?为什么要使用它?

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

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

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

102、Java中活锁和死锁有什么区别?

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

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

103、如何避免死锁?

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

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

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

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

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

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

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

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

    死锁检测

104、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,而不是锁。

105、什么是可重入锁(ReentrantLock)?

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

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

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

106、读写锁可以用于什么应用场景?

  读写锁可以用于 “多读少写” 的场景,读写锁支持多个读操作并发执行,写操作只能由一个线程来操作。

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

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

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

  2.读取和写入的时间

  3.有多少线程竞争

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

 107、堆和栈在内存中的区别是什么?

  概念:

  栈(stack)是为了执行线程留出的内存空间。当函数被调用的时候,栈顶为局部变量和一些“簿记(bookkeeping)”数据预留块。当函数执行完毕,块就没有了,可能在下次的函数调用的时候再被使用。栈通常用后进先出的方式预留空间;因此最近的保留块通常最先被释放。这样做可以使跟踪堆栈变得简单;从栈中释放块只不过是指针的偏移而已。

  堆(heap)是为动态分配预留的内存空间。和栈不一样,从堆上分配和重新分配块没有固定模式;你可以在任何时候分配和释放它。这样使得跟踪哪部分堆已经被分配和被释放变的异常复杂;有许多定制的堆分配策略来为不同的使用模式下调整堆的性能。

  区别:

  内存分配:

    栈:由编译器自动分配和释放,存放函数的参数、局部变量、临时变量、函数返回地址等。

    堆:一般人为分配和释放,对Java而言是由系统释放回收,但对于C++等,必须手动释放,如果没有手动释放会引起内存泄漏。

  系统响应:

    栈:只要栈的剩余空间大于所申请的空间,系统将为程序提供内存,否则将报异常提示栈溢出。

    堆:在记录空闲内存地址的链表中寻找一个空间大于所申请空间的堆节点,然后将该节点从空闲节点链表中删除,并将该节点的空间分配给程序。

  大小限制:

    栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。即栈顶的地址和栈的最大容量是系统预先规定好的,在 windows下,栈的大小是2M,如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。

    堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。

    结论:堆获得的空间比较灵活,也比较大。

  分配效率:

    栈:由系统自动分配,速度较快,无法人为控制。

    堆:由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便。

  存储内容:

    栈:在栈中,第一个进栈的是主函数下一条指令的地址,然后是函数的各个参数,在大多数编译器中,参数是由右向左入栈,然后是函数中的局部变量。注意,静态变量不入栈。

    堆:一般在堆的头部用一个字节存放堆的大小,具体内容受人为控制。

108、比较一下几种常用的排序算法,简单的写一下你知道的几种排序算法

  比较:

  1.稳定性比较

  插入排序、冒泡排序、二叉树排序、二路归并排序及其它线形排序是稳定的

  选择排序、希尔排序、快速排序、堆排序是不稳定的

  2.时间复杂度比较

  插入排序、冒泡排序、选择排序的时间复杂度为 O(n2)

  其它非线形排序的时间复杂度为 O(nlog2n)

  线形排序的时间复杂度为O(n)

  3.辅助空间的比较

  线形排序、二路归并排序的辅助空间为O(n)

  其它排序的辅助空间为O(1)

  4.其它比较

  *插入、冒泡排序的速度较慢,但参加排序的序列局部或整体有序时,这种排序能达到较快的速度,但是在这种情况下,快速排序反而慢了。

  *当n较小时,对稳定性不作要求时宜用选择排序,对稳定性有要求时宜用插入或冒泡排序。

  *若待排序的记录的关键字在一个明显有限范围内时,且空间允许是用桶排序。

  *当n较大时,关键字元素比较随机,对稳定性没要求宜用快速排序。

   *当n较大时,关键字元素可能出现本身是有序的,对稳定性有要求时,空间允许的情况下宜用归并排序。

  *当n较大时,关键字元素可能出现本身是有序的,对稳定性没有要求时宜用堆排序。

  常见的排序算法:

  选择排序

public class SelectionSort {
    public void selectionSort(int[] array) {
       int temp;
       for (int i = 0; i < array.length - 1; i++) {
              for (int j = i + 1; j <= array.length - 1; j++) {
              if (array[i] > array[j]) {    
              // 注意和冒泡排序的区别,这里是i和j比较。
              temp = array[i];
              array[i] = array[j];
              array[j] = temp;
             }
        }            // 打印每趟排序结果
       for (int m = 0; m <= array.length - 1; m++) {
             System.out.print(array[m] + "\t");
       }
          System.out.println();
       }
    }    
    public static void main(String[] args) {
        SelectionSort selectionSort = new SelectionSort(); 
        int[] array = { 5, 69, 12, 3, 56, 789, 2, 5648, 23 };
        selectionSort.selectionSort(array);        
    for (int m = 0; m <= array.length - 1; m++) {
            System.out.print(array[m] + "\t");
        }
    }
}

  插入排序

public class InsertSort {
    public void insertSort(int[] array, int first, int last) {        
    int temp, i, j;        
    for (i = first + 1; i <= last - 1; i++) {
            temp = array[i];
            j = i - 1;            
        while (j >= first && array[j] > temp) {
                array[j + 1] = array[j];
                j--;
            }
            array[j + 1] = temp;            // 打印每次排序结果
            for (int m = 0; m <= array.length - 1; m++) {
                System.out.print(array[m] + "\t");
            }
            System.out.println();
        }
    }    
     public static void main(String[] args) {
        InsertSort insertSort = new InsertSort();
            int[] array = { 5, 69, 12, 3, 56, 789, 2, 5648, 23 };
        insertSort.insertSort(array, 0, array.length);      
             for (int i = 0; i <= array.length - 1; i++) {
            System.out.print(array[i] + "\t");
        }
    }
}

  快速排序

public class QuickSort {    
    public int partition(int[] sortArray, int low, int height) {        
    int key = sortArray[low];        
    while (low < height) {            
        while (low < height && sortArray[height] >= key)
            height--;
            sortArray[low] = sortArray[height];            
            while (low < height && sortArray[low] <= key)
                low++;
                sortArray[height] = sortArray[low];
            }
            sortArray[low] = key;        // 打印每次排序结果
        for (int i = 0; i <= sortArray.length - 1; i++) {
            System.out.print(sortArray[i] + "\t");
        }
        System.out.println();        
        return low;
    }    
    public void sort(int[] sortArray, int low, int height) {        
        if (low < height) {            
            int result = partition(sortArray, low, height);
            sort(sortArray, low, result - 1);
            sort(sortArray, result + 1, height);
        }
    }
    public static void main(String[] args) {
        QuickSort quickSort = new QuickSort();        
        int[] array = { 5, 69, 12, 3, 56, 789, 2, 5648, 23 };        
        for (int i = 0; i <= array.length - 1; i++) {
            System.out.print(array[i] + "\t");
        }
        System.out.println();
        quickSort.sort(array, 0, 8);        
        for (int i = 0; i <= array.length - 1; i++) {
            System.out.print(array[i] + "\t");
        }
    }
}

  希尔排序

public class ShellSort {
    public void shellSort(int[] array, int n) {
        int i, j, gap;
        int temp;
        for (gap = n / 2; gap > 0; gap /= 2) {
            for (i = gap; i < n; i++) {       
                for (j = i - gap; j >= 0 && array[j] > array[j + gap]; j -= gap) {
                    temp = array[j];
                    array[j] = array[j + gap];
                    array[j + gap] = temp;
                }                // 打印每趟排序结果
                for (int m = 0; m <= array.length - 1; m++) {
                    System.out.print(array[m] + "\t");
                }
                System.out.println();
            }
        }
    } 
     public static void main(String[] args) {
        ShellSort shellSort = new ShellSort();
        int[] array = { 5, 69, 12, 3, 56, 789, 2, 5648, 23 };
        shellSort.shellSort(array, array.length);        
        for (int m = 0; m <= array.length - 1; m++) {
            System.out.print(array[m] + "\t");
        }
    }
}

 109、列出几个常见的runtime exception

ArithmeticException,   数学运算异常,eg:整数除以零
ArrayStoreException,    数组保存异常,eg:数组类型不匹配
BufferOverflowException,  缓冲区上溢异常,eg:ByteBuffer.allocate(2);分配的两个字节而put了三个
BufferUnderflowException, 缓冲区下溢异常,eg:ByteBuffer中保存了一个字节,而请求两个字节
ClassCastException,     类转换异常
ConcurrentModificationException, 对Vector、ArrayList迭代同时对其修改抛该异常,原因:成员内部类中成员变量更新不及时,参见:https://www.cnblogs.com/dolphin0520/p/3933551.html
IllegalArgumentException, 非法参数异常
IllegalMonitorStateException, 
抛出这个异常表明线程尝试等待一个对象的监视器或者去通知其他正在等待这个对象监视器的线程时,但是没有拥有这个监视器的所有权。
1>当前线程不含有当前对象的锁资源的时候,调用obj.wait()方法;
2>当前线程不含有当前对象的锁资源的时候,调用obj.notify()方法。
3>当前线程不含有当前对象的锁资源的时候,调用obj.notifyAll()方法。
IndexOutOfBoundsException, 角标越界异常
MissingResourceException,  找不到指定的配置文件
NegativeArraySizeException, 创建大小为负的数组,抛出该异常
NoSuchElementException,    多见流操作,hasNext()判断后再next()
NullPointerException,     空指针异常  
UndeclaredThrowableException, 使用jdk动态代理接口时,
若方法执行过程中抛出了受检异常但方法签名又没有声明该异常时则会被代理 类包装成UndeclaredThrowableException抛出 UnmodifiableSetException, 当由于设置不可修改而无法执行请求的操作时,抛出该异常。 UnsupportedOperationException 常见数组转集合后,给集合add/remove时报错。
这是因为Arrays.asList(String[] aa)转换的类型为其内部类ArrayList,不包含add/remove

110、超键、候选键、主键、外键

  超键:在关系中能唯一标识元组的属性集称为关系模式的超键。一个属性可以为作为一个超键,多个属性组合在一起也可以作为一个超键。超键包含候选键和主键。

  候选键:是最小超键,即没有冗余元素的超键。

  主键:数据库表中对储存数据对象予以唯一和完整标识的数据列或属性的组合。一个数据列只能有一个主键,且主键的取值不能缺失,即不能为空值(Null)。

  外键:在一个表中存在的另一个表的主键称此表的外键。

 111、什么是事务?什么是锁?

  事务:就是被绑定在一起作为一个逻辑工作单元的 SQL 语句分组,如果任何一个语句操作失败那么整个操作就被失败,以后操作就会回滚到操作前状态,或者是上有个节点。为了确保要么执行,要么不执行,就可以使用事务。要将有组语句作为事务考虑,就需要通过 ACID 测试,即原子性,一致性,隔离性和持久性。

  锁:在所有的 DBMS 中,锁是实现事务的关键,锁可以保证事务的完整性和并发性。与现实生活中锁一样,它可以使某些数据的拥有者,在某段时间内不能使用某些数据或数据结构。当然锁还分级别的。

112、数据库事务的四个特性及含义

  原子性:整个事务中的所有操作,要么全部完成,要么全部不完成,不可能停滞在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态。

  一致性:在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏。

  隔离性:隔离状态执行事务,使其好像是系统在给定时间内执行的唯一操作。如果有两个事务,运行在相同的时间内,执行相同的功能,事务的隔离性将确保每个事务在系统中认为只有该事务在使用系统。这种属性有时成为串行化,为了防止事务操作间的混淆,必须串行化或序列化请求,使得在同一时间仅有一个请求用于同一数据。

  持久性:在事务完成以后,该事务对数据库所做的更改便保存在数据库之中,并不会被回滚。

113、什么是视图 view?

  视图是一种虚拟的表,具有和物理表相同的功能。可以对视图进行增,改,查,操作,试图通常是有一个表或者多个表的行或列的子集。对视图的修改不影响基本表。它使得我们获取数据更容易,相比多表查询。

  如下两种场景一般会使用到视图:

  (1)不希望访问者获取整个表的信息,只暴露部分字段给访问者,所以就建一个虚表,就是视图。

  (2)查询的数据来源于不同的表,而查询者希望以统一的方式查询,这样也可以建立一个视图,把多个表查询结果联合起来,查询者只需要直接从视图中获取数据,不必考虑数据来源于不同表所带来的差异。

114、索引的作用?和它的优点缺点是什么?

  数据库索引,是数据库管理系统中一个排序的数据结构,以协助快速查询、更新数据库表中数据。索引的实现通常使用B树及其变种B+树。

  在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法。这种数据结构,就是索引。

  为表设置索引要付出代价的:一是增加了数据库的存储空间,二是在插入和修改数据时要花费较多的时间(因为索引也要随之变动)。

  创建索引可以大大提高系统的性能(优点):

  第一,通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。

  第二,可以大大加快数据的检索速度,这也是创建索引的最主要的原因。

  第三,可以加速表和表之间的连接,特别是在实现数据的参考完整性方面特别有意义。

  第四,在使用分组和排序子句进行数据检索时,同样可以显著减少查询中分组和排序的时间。

  第五,通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。

  增加索引也有许多不利的方面:

  第一,创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加。

  第二,索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚簇索引,那么需要的空间就会更大。

  第三,当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,这样就降低了数据的维护速度。

  索引是建立在数据库表中的某些列的上面。在创建索引的时候,应该考虑在哪些列上可以创建索引,在哪些列上不能创建索引。

  一般来说,应该在这些列上创建索引:

  (1)在经常需要搜索的列上,可以加快搜索的速度;

  (2)在作为主键的列上,强制该列的唯一性和组织表中数据的排列结构;

  (3)在经常用在连接的列上,这些列主要是一些外键,可以加快连接的速度;

  (4)在经常需要根据范围进行搜索的列上创建索引,因为索引已经排序,其指定的范围是连续的;

  (5)在经常需要排序的列上创建索引,因为索引已经排序,这样查询可以利用索引的排序,加快排序查询时间;

  (6)在经常使用在WHERE子句中的列上面创建索引,加快条件的判断速度。

  同样,对于有些列不应该创建索引:

  第一,对于那些在查询中很少使用或者参考的列不应该创建索引。这是因为,既然这些列很少使用到,因此有索引或者无索引,并不能提高查询速度。相反,由于增加了索引,反而降低了系统的维护速度和增大了空间需求。

  第二,对于那些只有很少数据值的列也不应该增加索引。这是因为,由于这些列的取值很少,例如人事表的性别列,在查询的结果中,结果集的数据行占了表中数据行的很大比例,即需要在表中搜索的数据行的比例很大。增加索引,并不能明显加快检索速度。

  第三,对于那些定义为text, image和bit数据类型的列不应该增加索引。这是因为,这些列的数据量要么相当大,要么取值很少。

  第四,当修改性能远远大于检索性能时,不应该创建索引。这是因为,修改性能和检索性能是互相矛盾的。当增加索引时,会提高检索性能,但是会降低修改性能。当减少索引时,会提高修改性能,降低检索性能。因此,当修改性能远远大于检索性能时,不应该创建索引。

115、drop,delete与truncate的区别

  • drop 直接删除表。
  • truncate 删除表中数据,再插入时自增长id又从1开始。
  • delete 删除表中数据,可以加where语句。

  (1) DELETE语句执行删除的过程是每次从表中删除一行,并且同时将该行的删除操作作为事务记录在日志中保存以便进行进行回滚操作。TRUNCATE TABLE 则一次性地从表中删除所有的数据并不把单独的删除操作记录记入日志保存,删除行是不能恢复的。并且在删除的过程中不会激活与表有关的删除触发器。执行速度快。

  (2) 表和索引所占空间。当表被TRUNCATE 后,这个表和索引所占用的空间会恢复到初始大小,而DELETE操作不会减少表或索引所占用的空间。drop语句将表所占用的空间全释放掉。

  (3) 一般而言,drop > truncate > delete

  (4) 应用范围。TRUNCATE 只能对TABLE;DELETE可以是table和view

  (5) TRUNCATE 和DELETE只删除数据,而DROP则删除整个表(结构和数据)。

  (6) truncate与不带where的delete :只删除数据,而不删除表的结构(定义)truncate语句将删除表的结构被依赖的约束(constrain),触发器(trigger)索引(index);依赖于该表的存储过程/函数将被保留,但其状态会变为:invalid(无效的)。

  (7) delete语句为DML(data maintain Language),这个操作会被放到 rollback segment中,事务提交后才生效。如果有相应的 tigger,执行的时候将被触发。

  (8) truncate、drop是DLL(data define language),操作立即生效,原数据不放到 rollback segment中,不能回滚。

  (9) 在没有备份情况下,谨慎使用 drop 与 truncate。要删除部分数据行采用delete且注意结合where来约束影响范围。回滚段要足够大。要删除表用drop;若想保留表而将表中数据删除,如果于事务无关,用truncate即可实现。如果和事务有关,或需要触发trigger,还是用delete。

  (10) Truncate table 表名 速度快,而且效率高,因为: 
  truncate table 在功能上与不带 WHERE 子句的 DELETE 语句相同:二者均删除表中的全部行。但 TRUNCATE TABLE 比 DELETE 速度快,且使用的系统和事务日志资源少。DELETE 语句每次删除一行,并在事务日志中为所删除的每行记录一项。TRUNCATE TABLE 通过释放存储表数据所用的数据页来删除数据,并且只在事务日志中记录页的释放。

  (11) TRUNCATE TABLE 删除表中的所有行,但表结构及其列、约束、索引等保持不变。新行标识所用的计数值重置为该列的种子。如果想保留标识计数值,请改用 DELETE。如果要删除表定义及其数据,请使用 DROP TABLE 语句。

  (12) 对于由 FOREIGN KEY 约束引用的表,不能使用 TRUNCATE TABLE,而应使用不带 WHERE 子句的 DELETE 语句。由于 TRUNCATE TABLE 不记录在日志中,所以它不能激活触发器。

116、谈一谈数据库事务的隔离级别

  1、Read uncommitted(读未提交)就是一个事务可以读取另一个未提交事务的数据。

  2、Read committed(读提交)就是一个事务要等另一个事务提交后才能读取数据。

  3、Repeatable read(重复读)就是在开始读取数据(事务开启)时,不再允许修改操作。

  4、Serializable(序列化)在该级别下,事务串行化顺序执行,可以避免脏读、不可重复读与幻读。是最高的事务隔离级别,但是这种事务隔离级别效率低下,比较耗数据库性能,一般不使用。

  事务的作用就是保证数据的一致性、完整性。事务隔离级别越高,在并发下会产生的问题就越少,但同时付出的性能消耗也将越大,因此很多时候必须在并发性和性能之间做一个权衡。所以设立了几种事务隔离级别,以便让不同的项目可以根据自己项目的并发情况选择合适的事务隔离级别,对于在事务隔离级别之外会产生的并发问题,在代码中做补偿。

117、MySQL主备同步的基本原理

  MySQL支持单向、异步复制,复制过程中一个服务器充当主服务器,而一个或多个其它服务器充当从服务器。

  MySQL复制是基于主服务器在二进制日志中跟踪所有对数据库的更改。因此,要进行复制,必须在服务器上启用二进制日志。每个从服务器从主服务器接收主服务器已经记录到日志的数据。

  当一个从服务器连接主服务器时,它通知主服务器从服务器在日志中读取的最后一次成功更新的位置。从服务器接收从那时起发生的任何更新;并在本机上执行相同的更新。然后封锁并等待主服务器通知新的更新。从服务器执行备份不会干扰主服务器,在备份过程中主服务器可以继续处理更新。

posted @ 2019-03-11 17:22  来兮子宁  阅读(442)  评论(0编辑  收藏  举报