第一阶段面试题总结:

面试题总结:

1.一个".java"源文件中是否可以包括多个类?有什么限制?

​ 一个源文件里可以有多个class声明的类,但只能有一个class被public修饰,且被public修饰的类的类名必须与源文件名相同、


2.垃圾回收器的作用?垃圾回收器可以马上回收内存吗?

​ Garbage Collection垃圾回收:将不再使用的内存空间应当进行回收。
​ 1.在C/C++等语言中,由程序员负责回收无用内存,而Java语言消除了程序员回收无用内存空间的责任:
​ 2.JVM提供了一种系统线程跟踪存储空间的分配情况。并在JVM的空闲时,检查并释放那些可以被释放的存储空间。
​ 垃圾回收器在Java程序运行过程中自动启用,程序员无法精确控制和干预,即使程序员通过代码告诉垃圾回收器执行,不会立即回收内存。


3.如何不借助第三个变量,交换两个数据?
/**
 * 不使用中间变量交换两个数据
 */
public class FileDemo {
    public static void main(String[] args) {
        changeNums1(3,2 );
        changeNums2(4,6 );
        changeNums3(5,3 );
    }

    public static void changeNums1(int num1, int num2) {
        System.out.println("转换前的两个数分别是:"+num1+" "+num2);
        num1 = num1 + num2;
        num2 = num1 - num2;
        num1 = num1 - num2;
        System.out.println("转换后的两个数分别是:"+num1+" "+num2);
    }

    public static void changeNums2(int num1, int num2){
        System.out.println("转换前的两个数分别是:"+num1+" "+num2);
        num1 = num1 * num2;
        num2 = num1 / num2;
        num1 = num1 / num2;
        System.out.println("转换后的两个数分别是:"+num1+" "+num2);
    }

    public static void changeNums3(int num1,int num2){
        System.out.println("转换前的两个数分别是:"+num1+" "+num2);
        num1 = num1 ^ num2;
        num2 = num1 ^ num2;
        num1 = num1 ^ num2;
        System.out.println("转换后的两个数分别是:"+num1+" "+num2);
    }
}

4.char型变量中能不能存储一个中文汉字?为什么?

​ char型变量是用来存储Unicode编码的字符的,unicode编码字符集中包含了汉字,所以,char型变量中可以存储汉字。不过,如果某个特殊的汉字没有被包含在unicode编码字符集中,那么,这个char型变量中就不能存储这个特殊汉字。

​ 补充说明:unicode编码占用两个字节,所以,char类型的变量也是占用两个字节。


5. String属于基本数据类型吗?

不属于,String属于引用类型,基本数据类型包括:

整数型---- byte(字节), short(短整型), int(整型), long(长整型)

浮点型---- float(单精度浮点型), double(双精度浮点型)

字符型---- char

布尔型---- boolean


6.switch是否能作用在byte上,是否能作用在long上,是否能作用在String上?

switch可以作用在byte上,在JDK1.7版本之后也可以作用在String上,但是不能作用在long上。

解释:

​ 在switch(expr)中,expr只能是一个整数表达式【byte,short,char,int】或者枚举常量(更大字体),整数表达式可以是int基本类型或Integer包装类型,由于byte,short,char都可以隐含转换为int,所以这些类型以及这些类型的包装类型也是可以的。

​ 显然,long和String类型都不符合switch的语法规定,并且不能被隐式转换成int类型,所以,它们不能作用于swtich语句中【JDK1.7以前的版本】。

​ 由于在JDK7.0中引入了新特性,所以switch语句可以接收一个String类型的值,String可以作用在switch语句上。


7.switch中default的位置是否必须出现在最后?

不一定。

​ default是缺省默认的意思,default在switch语句中不管放在哪都是从第一个case开始执行,当所有的case都不满足条件时,才执行default。

​ default在最后一行时下面的break可以省略不写,但如果没有在最后一行,default下面需要加上break,否则执行完default后会继续执行下面的代码直到遇到break跳出循环。


8.什么时候用for循环,什么时候用while循环?

1、从内存角度考虑——局部变量在栈内存中存在,当for循环语句结束,那么变量会及时被gc(垃圾回收器)及时的释放掉,不浪费空间 如果使用循环之后还想去访问循环语句中控制那个变量,使用while循环
2、从应用场景角度考虑——如果一个需求明确循环的次数,那么使用for循环(开发中使用for循环的几率大于while循环)如果一个需求,不知道循环了多少次,使用while循环


9.while循环和do-while循环的区别

while循环:先执行循环条件,如果满足条件则执行循环语句,否则不执行

do-while循环:先执行一次循环语句,然后执行循环判断条件,决定是否进行下一次的循环。

两者区别在于do-while循环至少会执行一次循环主体


合并:for循环、while循环和do-while循环的区别

1、循环结构的表达式不同
do-while循环结构表达式为:do{循环体;};

for循环的结构表达式为:for(单次表达式;条件表达式;末尾循环体){中间循环体}

while循环的结构表达式为:while(表达式){循环体}

2、执行时判断方式不同
do-while循环将先运行一次,因为经过第一次do循环后,当检查条件表达式的值时,其值为不成立时而会退出循环。保证了至少执行do{ }内的语句一次。

for循环执行的中间循环体可以为一个语句,也可以为多个语句,当中间循环体只有一个语句时,其大括号{}可以省略,执行完中间循环体后接着执行末尾循环体。

while循环执行时当满足条件时进入循环,进入循环后,当条件不满足时,执行完循环体内全部语句后再跳出(而不是立即跳出循环)

3、执行次数不同
do-while循环是先执行后判断,执行次数至少为一次。

for循环是先判断后执行,可以不执行中间循环体。

while循环也是先判断后执行,可以不执行中间循环体。

4、执行末尾循环体的顺序不同
do-while循环是在中间循环体中加入末尾循环体,并在执行中间循环体时执行末尾循环体。

for循环的中间循环体在条件判断语句里,执行末尾循环体后自动执行中间循环体。

while循环的末尾循环体也是在中间循环体里,并在中间循环体中执行。


10.break、continue、return的区别

​ break:应用在switch和循环中,作用跳出(终止)语句块
​ continue:应用在循环中,作用结束本次循环,继续下一次循环。
​ return :用在方法中作用返回结果,结束方法。


11.基本数据类型和引用数据类型之间的区别?
基本数据类型引用数据类型
在栈中进行分配在堆中进行分配,堆的读写速度远不及栈
变量名指向具体的数值变量名指向存储数据对象的内存地址,即变量名指向哈希值
变量在声明之后Java立刻分配内存空间以特殊的方式(类似指针)指向对象实体(具体的值),这类变量声明时不会分配内存,只是存储了一个内存地址
基本类型之间的赋值是创建新的拷贝对象之间的赋值只是传递引用
“==”和“!=”是在比较值“==”和“!=”是在比较两个引用是否相同,需要自己实现equals()方法
基本类型变量创建和销毁速度很块类对象需要JVM销毁
使用时需要赋具体值s使用时可以赋值为null

12.在java中,声明一个数组过程中,是如何分配内存的

当声明数组类型变量时,为其分配了(32位)引用空间,由于未赋值,因此并不指向任何对象;

当创建了一个数组对象(也就是new出来的)并将其地址赋值给了变量,其中创建出来的那几个数组元素相当于引用类型变量,因此各自占用(32位的)引用空间并按其默 认初始化规则被赋值为null

程序继续运行,当创建新的对象并(将其地址)赋值给各数组元素,此时堆内存就会有值了


13.数组的静态和动态初始化有什么不同

静态初始化:在定义数组的同时对数组元素进行初始化,

动态初始化:使用运算符new为数组分配空间,对于基本类型的数组,其格式如下:
type arrayName[ ]=new type[arraySize];
type[ ] arrayName=new type[arraySize];
对于对象数组,使用运算符new只是为数组本身分配空间,并没有对数组的元素进行初始化。即数组元素都为空,此时不能访问数组的任何元素,必须对数组元素进行初始化后,才能访问。因此,对于对象数组,需 要经过两步空间分配。首先给数组分配空间,然后给每一个数组元素分配空间:


14.分别使用冒泡和选择对已知数组进行排序
/**
 * 冒泡排序
 */
public class Demo1 {
    public static void main(String[] args) {
        int[] arr = {12, 9, 13, 7, 5};
        System.out.println("排序之前:");
        for (int i : arr) {
            System.out.print(i + " ");
        }
        System.out.println();

        for (int i = 0; i < arr.length - 1; i++) {
            for (int j = 0; j < arr.length - 1 - i; j++) {
                if (arr[j] > arr[j + 1]) {
                    int temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                }
            }
        }
        System.out.println("排序之后:");
        for (int i : arr) {
            System.out.print(i + " ");
        }
    }
}
/**
 * 简单选择排序
 */
public class Demo2 {
    public static void main(String[] args) {
        int [] arr = {12,9,13,7,5};
        System.out.println("排序之前:");
        for (int i : arr) {
            System.out.print(i + " ");
        }
        System.out.println();
        for (int i = 0; i < arr.length -1; i++) {
            for (int j = i + 1; j < arr.length; j++) {
                if(arr[i] > arr[j]){
                    int temp = arr[i];
                    arr[i] = arr[j];
                    arr[j] = temp;
                }
            }
        }
        
        System.out.println("排序之后:");
        for (int i : arr) {
            System.out.print(i + " ");
        }
    }
}

/**
 * 优化的选择排序
 */
public class Demo2 {
    public static void main(String[] args) {
        int [] arr = {12,9,13,7,5};
        System.out.println("排序之前:");
        for (int i : arr) {
            System.out.print(i + " ");
        }
        System.out.println();

        for (int i = 0; i < arr.length - 1; i++) {
            int pos = i;
            for (int j = i + 1; j < arr.length; j++) {
                if (arr[j] < arr[pos]) {
                    pos = j;
                }
            }
            if (pos != i) {
                int temp = arr[i];
                arr[i] = arr[pos];
                arr[pos] = temp;
            }
        }

        System.out.println("排序之后:");
        for (int i : arr) {
            System.out.print(i + " ");
        }
    }
}

补充:插入排序和二分查找
/**
 * 插入排序:从第二个元素开始,向前插入,保证插入的位置是有序的
 */
public class Demo3 {
    public static void main(String[] args) {
        int[] arr = {12, 9, 13, 7, 5};
        System.out.println("排序之前:");
        for (int i : arr) {
            System.out.print(i + " ");
        }
        System.out.println();

        //插入排序
        for (int i = 1; i < arr.length; i++) {
            int pos = i;//pos插入的位置
            int curr = arr[i];//指向当前元素
            for (int j = i - 1; j >= 0; j--) {
                if (arr[j] > curr) {
                    arr[j + 1] = arr[j];
                    pos--;
                } else {
                    break;//跳出循环
                }
            }
            if (pos != i) {
                arr[pos] = curr;
            }
        }

        System.out.println("排序之后:");
        for (int i : arr) {
            System.out.print(i + " ");
        }
    }
}
/**
 * 二分查找算法
 */
public class Demo4 {
    public static void main(String[] args) {
        //定义一个有序数组
        int [] arr ={1,5,8,10,15,20,26,30,36,50};

        Scanner input = new Scanner(System.in);
        System.out.println("请输入要查找的数:");
        int key = input.nextInt();
        int pos = -1;

        int low = 0;
        int high = arr.length - 1;
        while (low <= high) {
            int mid = (low + high)/2;
            if (key>arr[mid]) {
                low = mid + 1;
            } else if (key < arr[mid]) {
                high = mid -1;
            }else{
                pos = mid;
                break;
            }
        }
        if (pos!=-1) {
            System.out.println("找到了,是第"+(pos + 1)+"个数");
        }else{
            System.out.println("没有找到");
        }
    }
}

15.二维数数组在内存中的存储方式是怎样的?

​ 二维数组可以理解成,在数组的每个成员空间存储了一个新的数组。所以在我们理解二维数组的内存分配时可以这样理解:

​ 首先在栈中开辟一个空间存储二维数组的地址,并同时在堆中开辟了一个连续的空间,按具体的数据类型进行空间分配,每一个成员空间存储着一个新的数组的址,该地址指向堆中成员数组的具体成员。


16.什么是面向对象?面向对象和面向过程的区别是什么

​ 面向对象是一种看待问题的思维方式,着眼于找到一个具有特殊功能的具体个体,然后委托这个个体去做某件事情,我们把这个个体就叫做对象。面向对象是一种更符合人类思考习惯的思想【懒人思想】,可以将复杂的事情简单化,将程序员从执行者转换成了指挥者。使用面向对象进行开发,先要去找具有所需功能的对象来用,如果该对象不存在,那么创建一个具有所需功能的对象。

面向对象和面向过程都是看待问题的一种思维方式,都能解决问题。面向过程着眼于所有的事情按照步骤来实现;面向对象着眼于找到一个具有特殊功能的对象,委托这个对象去做某件事情。


17.构造方法与普通方法之间的区别

<1>构造方法是在创建对象的过程中自动调用的,普通方法只能手动进行调用
<2>构造方法没有返回值类型【注意区别返回值void】,普通方法的返回值类型要么是确定的类型,要么为void
<3>系统会默认为我们提供一个无参的构造方法,普通方法只能手动添加
<4>构造方法的方法名称必须和对应的类名保持一致
<5>构造方法在创建对象的过程中就会执行,而且每个对象只执行一次,对于普通方法而言,只有在需要使用的时候才被执行,并且一个对象可以调用多次


18.this关键字的作用以及使用

this——表示当前对象的引用。

this.属性——访问本类的成员变量

​ 作用:为了区分成员变量和形参变量名一样的情况

this.方法——访问本类的其他方法

this()——访问本类中的其他构造方法

​ (1) this(参数)只能用在构造方法中,必须是第一条语句

​ (2) this(参数)只能调用一次


19. 方法重载与方法重写的区别

​ 方法的重载:Overload,在同一个类中,方法名相同,参数列表不同(参数个数不同,类型不同,顺序不同),和返回值修饰符无关,互为重载方法。

​ 方法的重写:Override,在继承过程中,在子类中重写父类中继承来的方法,方法名、参数列表、返回值必须相同,访问权限不能比父类严格。

补充:构造方法的重载

​ 同一个类中,构造方法名相同,参数列表不同

​ 参数列表不同:个数不同,类型不同,顺序不同

​ 方法名和类名相同。


20.问题:以下输出结果是什么?
class A{  
 public String show(D obj){  
        return ("A and D");  
 }   
 public String show(A obj){  
        return ("A and A");  
 }   
}     
class B extends A{  
 public String show(B obj){  
        return ("B and B");  
 }  
 public String show(A obj){  
        return ("B and A");  
 }   
}    
class C extends B{}   
class D extends B{} 

​ A a1 = new A();
​ A a2 = new B();
​ B b = new B();
​ C c = new C();
​ D d = new D();

​ System.out.println(a1.show(b)); A and A
​ System.out.println(a1.show©); A and A
​ System.out.println(a1.show(d)); A and D

说明:父类调用子类对象时,子类会自动向上转型

​ System.out.println(a2.show(b)); B and A
​ System.out.println(a2.show©); B and A
​ System.out.println(a2.show(d)); A and D

说明:子类向上转型后,父类应用变量调用的方法是子类重写或父类的方法(子类重写的方法会覆盖父类原有的方法)

补充:子类向上转型后,通过父类引用变量无法调用子类特有的属性和方法

​ System.out.println(b.show(b)); B and B
​ System.out.println(b.show©); B and B
​ System.out.println(b.show(d)); A and D

说明:子类会完全继承父类的非私有成员(父类的构造方法不能被继承,但可以被子类调用)


21.接口和抽象类有什么区别?

<1>语法:

​ (1)抽象类使用abstract,接口使用interface

(2)抽象类中可以包含抽象方法,也可以包含非抽象方法,接口中只能包含抽象方法和静态常量,jdk1.8之后接口可以包含静态方法和默认方法。

(3)抽象类和接口都不能实例化。

(4)抽象类可以包含构造方法,接口中没有构造方法。

<2>功能:

​ (1)抽象类一般用来表示同类事物,接口可以表示不同类事物。

​ (2)抽象类可以实现代码的重用,也可以约束子类的功能。接口就是约束实现类的功能,降低代码之间的耦合性。

<3>使用场景:

​ (1)程序或模块内部使用抽象类

​ (2)程序架构或模块之间使用接口


22.final和abstract是否可以连用?

1)修饰方法时,final修饰的方法特点:可以被继承不能被重写;abstract修饰的方法特点:必须被重写;所以这两个关键字不能同时修饰同一个方法

2)修饰类时:final修饰的类特点:不能被继承;abstract修饰的类特点:必须被继承;所以这两个关键字不能同时修饰同一个类

所以不能连用


23.使用已知的变量,在控制台输出30,20,10。
class InnerClassTest {
    public static void main(String[] args) {
        Outer.Inner oi = new Outer().new Inner();
        oi.show();
    }
}

class Outer {
    public int num = 10;

    class Inner {
        public int num = 20;

        public void show() {
            int num = 30;
            System.out.println(num);
            System.out.println(this.num);
            System.out.println(Outer.this.num);
        }
    }
}

24.String str = new String(“abc”);创建出了几个字符串对象;

​ 创建了两个字符串对象

​ 说明:String str = new String(“abc”)创建实例的过程

​ <1>首先在堆中(不是常量池)创建一个指定的对象"abc",并让str引用指向该对象

​ <2>在字符串常量池中查看,是否存在内容为"abc"字符串对象

​ <3>若存在,则将new出来的字符串对象与字符串常量池中的对象联系起来

​ <4>若不存在,则在字符串常量池中创建一个内容为"abc"的字符串对象,并将堆中的对象与之联系起来

补充:String str = "abc"创建对象的过程

​ <1>首先在常量池中查找是否存在内容为"abc"字符串对象

​ <2>如果不存在则在常量池中创建"abc",并让str引用该对象

​ <3>如果存在则直接让str引用该对象


25.String str1 = “abc”; String str2 = “ab” + “c”; str1==str2是true吗?

​ 是。因为String str2 = “ab” + "c"会查找常量池中时候存在内容为"abc"字符串对象,如存在则直接让str2引用该对象,显然String str1 = "abc"的时候,上面说了,会在常量池中创建"abc"对象,所以str1引用该对象,str2也引用该对象,所以str1==str2


26.String str1 = “abc”; String str2 = “ab”; String str3 = str2 + “c”; str1==str3是false吗?

​ 是。因为String str3 = str2 + "c"涉及到变量(不全是常量)的相加,所以会生成新的对象,其内部实现是先new一个StringBuilder,然后 append(str2),append(“c”);然后让str3引用toString()返回的对象


27.b1,b2,b3,b4的值分别是多少
	String s1 = "abc";
	String s2 = new String("abc");
	String s3 = "a";
	String s4 = "bc";
	String s5 = s3 + s4;
	boolean b1 = s1==s2;
	boolean b2 = s1.equals(s2);
	boolean b3 = s1==s5;
	boolean b4 = s1.equals(s5);

​ b1 = false
​ b2 = true
​ b3 = false
​ b4 = true

分析:首先明确一个观点,在String类中重写了equals方法,所以在String类型的数据比较时,“==”比较的是对象的地址,而equals方法比较的是两个对象的值。其次,我们应该了解如果对象是使用new关键字创建的,那么不管他的数据内容是什么,他都会创建一个新的地址。


补充:“==”和equals的区别

​ ==:

​ 基本类型:比较的就是值是否相同

​ 引用类型:比较的就是地址值是否相同

​ equals:

​ 引用类型:默认情况下,比较的是地址值。(但是String等一些JDK提供的类已经重写了父类Object的equals方法,比较的是内容的值)

​ 不过,我们

​ 可以根据情况自己重写该方法。一般重写都是自动生成,比较对象的成员变量值是否相同


28.什么是异常?常见的异常有哪些?

​ 在程序执行过程中由于设计或设备原因导致的程序中断的异常现象叫做异常。

​ 异常又可以分为运行时异常和编译时异常:

​ 运行时异常:(RuntimeException)在编译过程不会发现(没有语法错误),但是在执行程序过程中,由于重大的逻辑错误导致的程序中断。所有的RuntimeException的子类包括RuntimeException都属于运行时异常。常见的异常包括NullPointerException(空指针异常)、ArrayIndexOutOfBoundsException(下标越界异常)、ClassCastException(类型转换异常)、NumberFormatException(数字格式异常)、ArithmeticException(算术异常)

​ 编译异常或检查异常,在程序设计过程中,编译时就会被发现,但是执行时可能发生也可能不发生的异常,为了程序不报错可以执行,那么这一类异常必须进行相应的处理。Exception的子类包括Exception,除了RuntimeExcption之外都属于编译时异常。

​ 除此之外还有:Error类:错误,错误比较严重的问题,不属于异常,程序员无法处理。包括StackOverflowError (栈溢出错误)、OutOfMemoryError(内存溢出错误)


29.异常有哪些处理方式,分别需要注意什么问题?

<1> try-catch块

​ 使用try-catch块捕获异常,分为三种情况

​ 第一种情况:正常情况,程序正常执行,不执行catch语句块中的内容

​ 第二种情况:出现异常,程序执行catch语句块中的内容,然后继续顺序执行程序

​ 第三种情况:异常类型不匹配,程序不能正常执行,报错

<2> try-catch-finally

​ 在try-catch块后加入finally块,finally块的主要作用释放资源。

​ (1)finally块是否发生异常都执行

​ (2)finally块不执行的唯一情况,退出java虚拟机,System.exit(); 0正常退出,非0非正常退出

<3>多重catch块(try…catch变形)

​ 引发多种类型的异常

​ (1)排列catch 语句的顺序:先子类后父类

​ (2)发生异常时按顺序逐个匹配

​ (3)只执行第一个与异常类型匹配的catch语句

<4>try…finally

​ try…finally不能捕获异常 ,仅仅用来当发生异常时,用来释放资源。

​ 一般用在底层代码,只释放资源不做异常处理,把异常向上抛出。

<5>使用throws声明异常

​ 在一个方法体中抛出了异常,如何通知调用者?

​ throws关键字:声明异常

​ 使用原则:底层代码向上声明或者抛出异常,最上层一定要处理异常,否则程序中断。

<6>使用throw抛出异常

​ 除了系统自动抛出异常外,有些问题需要程序员自行抛出异常。

​ throw关键字:抛出异常


30.try-catch块中存在return语句,是否还执行finally块,如果执行,说出执行顺序

​ 会执行。finally块不执行的唯一情况,退出java虚拟机,System.exit(); 0正常退出,非0非正常退出。

执行顺序:

<1>如果try-catch-finally语句块中都没有return,则正常执行

<2>如果try和catch中有return,finally中没有return时先执行try-catch语句块中的内容,后执行finally,最后执行返回语句。返回值不会受finally的改变

<3>如果try-catch-finally中都有return语句,先执行try-catch语句块中的内容,后执行finally中的内容,最后执行return语句,但是最终的返回值是finally语句块中return语句的返回值,即finally中的return语句将之前的返回值进行了覆盖,所以finally中最好不要包含return,否则会导致程序提前退出。


31.ArrayList与LinkedList的区别

​ ArrayList存储结构是数组,LinkedList存储结构是双向链表。

​ ArrayList集合适用在对元素查询、遍历操作,不适合插入和删除。

​ LinkedList集合适用在对元素插入和删除操作,不适合遍历和查找

32.Stack的存储特点是什么

Stack类表示后进先出(LIFO)的对象栈


33.简述HashSet和LinkedHashSet之间的区别

<1>此类实现Set接口,由哈希表(实际上是一个HashMap实例)支持。它不保证set的迭代顺序;特别是它不保证该顺序恒久不变。此类允许使用null元素。

​ 存储特点:相对无序存储,不可以存储相同元素(排重),通过哈希表实现的集合

​ 存储结构:哈希表:数组加链表,既有数组的优点也有链表的优点。

<2>LinkedHashSet类是具有可预知迭代顺序(相对有序)的Set接口的哈希表和链接列表实现。是HashSet的子类。

​ 存储特点:有序存储,不可以存储相同元素(排重),通过链表实现的集合的有序。

​ 存储结构:哈希表和链表


34.BufferedReader属于哪种流,它主要是用来做什么的,它里面有那些经典的方法

按照流向分类属于输入流

按照处理数据单位属于字符流

按照功能分类属于处理流中的缓冲流

主要用来将读取的内容以字符的形式存在内存里面

mark()方法,标记当前位置

reset()方法,返回之前标记的位置


35.怎么样把输出字节流转换成输出字符流,说出它的步骤

​ 首先创建一个输出字节流对象,然后创建一个字符字节转换输出流,并传入输出字节流对象,就可以得到输出字符流

例如:

		ByteArrayOutputStream baos = new ByteArrayOutputStream();
        OutputStreamWriter osw =  new OutputStreamWriter(baos);

36.流一般需要不需要关闭,如果关闭的话在用什么方法,一般要在那个代码块里面关闭比较好,处理流是怎么关闭的,如果有多个流互相调用传入是怎么关闭的?

​ 流一旦打开就必须关闭,使用close方法
​ 放入finally语句块中(finally 语句一定会执行)
​ 调用的处理流就关闭处理流
​ 多个流互相调用只关闭最外层的流


37.什么叫对象序列化,什么是反序列化,实现对象序列化需要做哪些工作

​ 对象序列化,将对象以二进制的形式保存在硬盘上
​ 反序列化;将二进制的文件转化为对象读取
​ 实现serializable接口
​ 不想让字段放在硬盘上就加transient或者static静态处理


38.在实现序列化接口是时候一般要生成一个serialVersionUID字段,它叫做什么,一般有什么用

​ 是版本号,要保持版本号的一致来进行序列化
​ 为了防止序列化出错


39.什么是内存流?有什么作用

​ 将输出输入的位置设置在内存上,此时的操作应该以内存为操作点,在网络信息的传输上会有作用,可以将传入的信息保存到内存然后进行读写,速度快


40.什么是IO流,有什么作用

​ 在工作中,经常会操作磁盘上的资源,这个过程中实现了数据的输入和输出操作,磁盘上的文件和内存之间进行交互,数据的交互需要有一个媒介或者管道,把这个媒介或者管道就称为IO流,也被称为输入输出流【I:Input O:Output】

​ 流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。


41.IO流都有哪些分类?依据是什么?

​ 按照方向分:输入流和输出流
​ 按照读取的字节个数:字节流和字符流
​ 按照功能分:节点流 (负责读写数据)、处理流 (封装)


42.简述进程和线程各自的特点以及二者之间的区别与联系

​ <1>进程的特点:
​ (1)独立性:不同的进程之间是独立的,相互之间资源不共享
​ (2)动态性:进程在系统中不是静止不动的,而是在系统中一直活动的
​ (3)并发性:多个进程可以在单个处理器上同时进行,且互不影响

​ <2>线程的特点:
​ 线程的执行是抢占式的,多个线程在同一个进程中可以并发执行,其实就是CPU快速的在不同的线程之间切换,也就是说,当前运行的线程在任何时候都有可能被挂起,以便另外一个线程可以运行。

​ <3>进程与线程的区别

​ (1)定义方面:进程是程序在某个数据集合上的一次运行活动;线程是进程中的一个执行路径。

​ (2)角色方面:在支持线程机制的系统中,进程是系统资源分配的单位,线程是系统调度的单位。

​ (3)资源共享方面:进程之间不能共享资源,而线程共享所在进程的地址空间和其它资源。同时线程还有自己的栈和栈指针,程序计数器等寄存器。

​ (4)独立性方面:进程有自己独立的地址空间,而线程没有,线程必须依赖于进程而存在。

​ <4>进程与线程的联系

​ 进程中有多个不同的执行路径,是多个线程的集合。线程是进程中正在独立运行的一条执行路径。


43.简述线程的生命周期

​ 线程五状态

​ New(新生):线程被实例化,但是还没有开始执行
​ Runnable(就绪):没有抢到时间片
​ Running(运行):抢到了时间片,CPU开始处理这个线程中的任务
​ Blocked(阻塞):线程在执行过程中遇到特殊情况,使得其他线程就可以获得执行的机会,被阻塞的线程会等待合适的时机重新进入就绪状态(还有一种线程七状态,是将阻塞状态细分为,阻塞状态,等待状态和锁定状态)
​ Dead(死亡):线程终止
​ a.run方法执行完成,线程正常结束【正常的死亡】
​ b.直接调用该线程的stop方法强制终止这个线程


44.线程的创建方式以及之间的区别和联系

​ 线程的创建方式有三种:

​ 继承Thread类,实现Runnable接口,实现Callable接口

​ 实现Callable接口的方法只做了解,不进行比较。

​ <1> 继承Thread类的方式
​ a.没有资源共享,编写简单
​ b.弊端:因为线程类已经继承了Thread类,则不能再继承其他类

​ <2> 实现Runnable接口的方式
​ a.可以多个线程共享同一个资源,所以非常适合多个线程来处理同一份资源的情况
​ b.资源类实现了Runnable接口。如果资源类有多个操作,需要把功能提出来,单独实现Runnable接口。
​ c.弊端:编程稍微复杂,不直观,如果要访问当前线程,必须使用Thread.currentThread()

总结:实际上大多数的多线程应用都可以采用实现Runnable接口的方式来实现【推荐使用匿名内部类】


45.sleep()方法和yield()方法之间的区别

​ sleep()方法使得当前正在执行的线程休眠一段时间,释放时间片,导致线程进入阻塞状态.设置了sleep就相当于将当前线程挂起,这个操作跟线程的优先级无关,当对应的时间到了之后,还会再继续执行

​ yield()方法可以让当前正在执行的线程暂停,但它不会阻塞该线程,他只是将该线程转入就绪状态,完全可能出现的情况是:当某个线程调用了yield方法暂停之后,线程调度器又将其调度出来重新执行。实际上,当某个线程调用了yield方法暂停之后,只有优先级与当前线程相同,或者优先级比当前线程更高的就绪状态的线程才会获得执行的机会。


46.多线程临界资源问题是如何产生的,该怎么解决

​ 有多个线程在同时访问一个资源,如果一个线程在取值的过程中,时间片又被其他线程抢走了,临界资源问题就产生了

​ 一个线程在访问临界资源的时候,如果给这个资源“上一把锁”,这个时候如果其他线程也要访问这个资源,就得在“锁”外面等待。即使用同步代码块、同步方法或ReentrrantLock


47.简述生产者与消费者设计模式的实现原理

​ <1>线程同步是实现该设计模式的前提

​ <2>实现线程间的通信,实现生产者与消费者之间的同步,保证统一资源被多个线程并发访问时的完整性

​ wait():当缓冲区已满或空时,生产者/消费者线程停止自己的执行,放弃锁,使自己处于等待状态,让其他线程执行

​ notify():当生产者/消费者向缓冲区放入/取出一个产品时,向其他等待的线程发出可执行的通知,同时放弃锁,使自己处于等待状态


48.sleep()和wait()的区别?

​ <1>sleep()方法进入休眠后,系统自动唤醒,wait()方法进入休眠后,需要别的线程进行唤醒,也可以设置等待事件,但仍须获得锁之后才能运行

​ <2>sleep()方法进入休眠,只释放CPU不释放锁,wait()方法进入休眠后,CPU和锁一同释放


49.三个线程交替输出A B C ,输出20遍。
public class Alternative {
    private int index = 1;
    private int count = 1;

    private Lock lock = new ReentrantLock();
    Condition conditionA = lock.newCondition();
    Condition conditionB = lock.newCondition();
    Condition conditionC = lock.newCondition();

    public void printA(){
        lock.lock();
        try {
            if (index!=1) {
                try {
                    conditionA.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("A");
            index = 2;
            conditionB.signal();
        } finally {
            lock.unlock();
        }
    }

    public void printB(){
        lock.lock();
        try {
            if (index!=2) {
                try {
                    conditionB.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("B");
            index = 3;
            conditionC.signal();
        } finally {
            lock.unlock();
        }
    }

    public void printC(){
        lock.lock();
        try {
            if (index!=3) {
                try {
                    conditionC.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("C");
            index = 1;
            System.out.println("------"+count+"------");
            count++;
            conditionA.signal();

        } finally {
            lock.unlock();
        }
    }
}


public class Demo {
    public static void main(String[] args) {
        Alternative alternative = new Alternative();
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 20; i++) {
                    alternative.printA();
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 20; i++) {
                    alternative.printB();
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 20; i++) {
                    alternative.printC();
                }
            }
        }).start();
    }
}

50.volitale关键字的作用?

volatile可以实现内存可见性,禁止指令重排序


51.i++操作是原子操作吗?

​ 不是。i++操作其实是分为了“读、改、写”三个不可分割步骤的,并非原子操作。

​ i++可拆分为:
​ int temp1=i;
​ int temp2=temp+1;
​ i=temp2;


52.简述Socket和ServerSocket的区别。

​ Socket:
​ (1)客户端创建套接字
​ (2)向服务器发送连接请求
​ (3)和服务器端进行通信
​ ServerSocket:
​ (1)服务器端创建套接字
​ (2)用于绑定本地地址和端口
​ (3)监听作用,接收客户端的请求
​ (4)当请求到来后,接收连接请求,启动线程为当前连接服务


53.简述UDP和TCP之间的区别

UDP(面向事务的、无连接的、简单不可靠信息传送服务协议):

  1. 将数据封装成一个数据包,面向无连接. 如同广播站 和 收音机的关系
  2. 每一个数据包大小限制在64kb以内
  3. 因为面向无连接,不可靠, 会丢包
  4. 因为面向无连接,所以传输 速度特别快!!!
  5. UDP不区分客户端和服务器,只有发送端和接收端
  6. 网络游戏/网络直播均采用UDP协议

TCP(面向连接的、可靠的、基于字节流的通信协议):

  1. TCP协议是完全依赖IO流的,面向连接的
  2. 数据传输没有大小限制
  3. 因为面向连接,所以数据安全
  4. 因为面向连接,所以速度较慢
  5. TCP协议严格区别客户端和服务器

54.简述常见的NIO类及作用

​ <1>Buffer,用于和NIO通道进行交互。数据是从通道读入缓冲区,从缓冲区写入到通道中的。缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存。这块内存被包装成NIO Buffer对象,并提供了一组方法,用来方便的访问该块内存。

​ <2>Channel,所有的 IO 在NIO 中都从一个Channel 开始。Channel 有点象流。 数据可以从Channel读到Buffer中,也可以从Buffer 写到Channel中。 ServerSocketChannel、SocketChannel实现阻塞式网络编程

​ <3>Selector,Selector选择器类管理着一个被注册的通道集合的信息和它们的就绪状态。通道是和选择器一起被注册的,并且使用选择器来更新通道的就绪状态。选择器提供选择执行已经就绪的任务的能力.从底层来看,Selector提供了询问通道是否已经准备好执行每个I/O操作的能力。Selector 允许单线程处理多个Channel。仅用单个线程来处理多个Channels的好处是,只需要更少的线程来处理通道。事实上,可以只用一个线程处理所有的通道,这样会大量的减少线程之间上下文切换的开销。


55.BIO、NIO、AIO的区别

​ BIO 就是传统的 java.io 包,它是基于流模型实现的,交互的方式是同步、阻塞方式,也就是说在读入输入流或者输出流时,在读写动作完成之前,线程会一直阻塞在那里,它们之间的调用时可靠的线性顺序。它的有点就是代码比较简单、直观;缺点就是 IO 的效率和扩展性很低,容易成为应用性能瓶颈。

​ NIO 是 Java 1.4 引入的 java.nio 包,提供了 Channel、Selector、Buffer 等新的抽象,可以构建多路复用的、同步非阻塞 IO 程序,同时提供了更接近操作系统底层高性能的数据操作方式。

​ AIO 是 Java 1.7 之后引入的包,是 NIO 的升级版本,提供了异步非堵塞的 IO 操作方式,所以人们叫它 AIO(Asynchronous IO),异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。


57.HashMap和ConcurrentHashMap的区别

ConcurrentHashMap是线程安全的哈希表(相当于线程安全的HashMap)


58.同/异、阻/非堵塞四种类型组合的新能分析
组合方式性能分析
同步阻塞最常用的一种用法,使用也是最简单的,但是 I/O 性能一般很差,CPU 大部分在空闲状态。
同步非阻塞提升 I/O 性能的常用手段,就是将 I/O 的阻塞改成非阻塞方式,尤其在网络 I/O 是长连接,同时传输数据也不是很多的情况下,提升性能非常有效。 这种方式通常能提升 I/O 性能,但是会增加CPU 消耗,要考虑增加的 I/O 性能能不能补偿 CPU 的消耗,也就是系统的瓶颈是在 I/O 还是在 CPU 上。
异步阻塞这种方式在分布式数据库中经常用到,例如在网一个分布式数据库中写一条记录,通常会有一份是同步阻塞的记录,而还有两至三份是备份记录会写到其它机器上,这些备份记录通常都是采用异步阻塞的方式写 I/O。异步阻塞对网络 I/O 能够提升效率,尤其像上面这种同时写多份相同数据的情况。
异步非阻塞这种组合方式用起来比较复杂,只有在一些非常复杂的分布式情况下使用,像集群之间的消息同步机制一般用这种 I/O 组合方式。如 Cassandra 的 Gossip 通信机制就是采用异步非阻塞的方式。它适合同时要传多份相同的数据到集群中不同的机器,同时数据的传输量虽然不大,但是却非常频繁。这种网络 I/O 用这个方式性能能达到最高。

59.简述Java中的反射使用

​ <1>导入java.lang.reflect.*

​ <2>获得需要操作的类的Java.lang.Class对象

​ <3>调用Class的方法获取Field、Method等对象

​ <4>使用反射API进行操作(设置属性﹑调用方法)


60. JAVA常用反射API

​ 使用反射API的第一步便是获取Class对象。在Java中常见的有这么三种。

  • 使用静态方法Class.forName来获取

  • 调用对象的getClass()方法

  • 直接用类名 + ".class"来访问。对于基本类型来说,它们的包装类型(wrapper classes)拥有一个名为“TYPE”的final静态字段,指向该基本类型对应的Class对象。

    例如,Integer.TYPE指向int.class。对于数组类型来说,可以使用类名+“[].class”来访问,如int[],class。

    除此之外,Class类和java.lang.reflect包中还提供了许多返回Class对象的方法。例如,对于数组类的Class对象,调用Class.getComponentType()方法可以获得数组元素的类型。

一旦得到了Class对象,我们便可以正式地使用反射功能了。下面列举了较为常用的几项。

  • 使用*newInstance()*来生成一个该类的实例,它要求该类中拥有一个无参数构造器。
  • 使用*isInstance(Object)*来判断一个对象是否该类的实例,语法上等同于instanceof关键字(JIT优化时会有差别)
  • 使用*Array.newInstance(Class,int)*来构造该类型的数组。
  • 使用getFields()/getConstructors()/*getMethods()*来访问该类的成员。除了这三个之外,Class还提供了许多其他方法。需要注意的是,方法名中带Declared的不会返回父类的成员,但是会返回私有成员;而不带Declared的则相反。

当获得类成员之后,我们可以进行进一步做如下操作

  • 使用Constructor/Field/*Method.setAccessible(true)*来绕开Java语言的访问限制
  • 使用*Constructor.newInstance(Object[])*来生成类的实例
  • 使用*Field.get/set(Object)*来访问字段的值。
  • 使用*Method.invoke(Object, Object[])*来调用方法
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值