当前位置 : 主页 > 编程语言 > java >

Java面试总结

来源:互联网 收集:自由互联 发布时间:2022-08-10
Java语言 八种基本数据类型及其包装类 整型:byte,short,int,long 浮点型:float,double 逻辑型:boolean 字符型:char 原始类型 原始类型所占的字节数 包装类 byte 1个字节 Byte shot 2个字节


Java语言

八种基本数据类型及其包装类

整型:byte,short,int,long
浮点型:float,double
逻辑型:boolean
字符型:char

原始类型

原始类型所占的字节数

包装类

byte

1个字节

Byte

shot

2个字节

Short

int

4个字节

Integer

long

8个字节

Long

float

4个字节

Float

double

8个字节

Double

boolean

1个字节

Boolean

char

2个字节

Character

要注意的是基本数据的包装类很多都实现了享元模式。享元模式就是运用共享技术有效地支持大量细粒度对象的复用。用一个常见的面试题来解释

1.判断如下代码的输出,并说出原因

Integer a1 = 40;
Integer a2 = 40;
System.out.println(a1 == a2);

Integer a3 = 200;
Integer a4 = 200;
System.out.println(a3 == a4);

由自动装箱和拆箱可以知道这2种写法是等价的

Integer a1 = 40;
Integer a1 = Integer.valueOf(40);

看一下Integer的valueOf方法

public static Integer valueOf(int i) {
// i的取值范围为[-128,127]
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}

IntegerCache是Ingeter的静态内部类,默认创建了[-128,127]的对象,并放到IntegerCache内部的一个cache数组中,在[-128,127]这个范围内的整数对象,不用创建。直接从IntegerCache中的cache数组中根据下标拿就可以,超出这个范围的每次去创建新的对象。其他几种包装类型的常量池和Integer思路都差不多,源码都很相似。

所以答案如下:

Integer a1 = 40;
Integer a2 = 40;

// true
System.out.println(a1 == a2);

Integer a3 = 200;
Integer a4 = 200;
// false
System.out.println(a3 == a4);

包装类缓存的范围如下

包装类

缓存范围

Byte

-128~127

Short

-128~127

Integer

-128~127

Long

-128~127

Character

0~127

2.Java一个char类型可以存储中文吗?
可以,因为Java中使用了Unicode字符,不论中文还是因为固定占用2个字节。

char a = '中';
// 中
System.out.println(a);

3.什么是自动装箱,自动拆箱?
自动装箱就是Java自动将原始类型值转换成对应的对象,比如将int的变量转换成Integer对象,这个过程叫做装箱,反之将Integer对象转换成int类型值,这个过程叫做拆箱。因为这里的装箱和拆箱是自动进行的非人为转换,所以就称作为自动装箱和拆箱

自动装箱时编译器调用valueOf将原始类型值转换成对象,同时自动拆箱时,编译器通过调用类似intValue(),doubleValue()这类的方法将对象转换成原始类型值

// jdk1.5 之前的写法
Integer tempNum1 = Integer.valueOf(5);
int num1 = tempNum1.intValue();

// jdk1.5之后的写法
Integer tempNum2 = 5;
int num2 = tempNum2;

4.为什么要需要基本数据类型的包装类?
(1)Java是面向对象的语言,很多地方需要使用的是对象而不是基本数据类型。例如,List,Map等容器类中基本 数据类型是放不进去的。
(2)包装类在原先的基本数据类型上,新增加了很多方法,如Integer.valueOf(String s)等

5.既然包装类型能完成所有功能,为啥还需要基本类型?
基本数据类型基于数值,对象类型基于引用。基本数据类型存储在栈的局部变量表中。
而对象类型的变量则存储堆中引用,实例放在堆中,因此对象类型的变量需要占用更多的内存空间。

显然,相对于基本类型的变量来说,对象类型的变量需要占用更多的内存空间。

5.写出如下代码的输出

Integer i1 = 40;
Integer i2 = 40;
Integer i3 = 0;
Integer i4 = new Integer(40);
Integer i5 = new Integer(40);
Integer i6 = new Integer(0);

System.out.println(i1 == i2);
System.out.println(i1 == i2 + i3);
System.out.println(i1 == i4);
System.out.println(i4 == i5);
System.out.println(i4 == i5 + i6);
System.out.println(40 == i5 + i6);

输入如下

Integer i1 = 40;
Integer i2 = 40;
Integer i3 = 0;
Integer i4 = new Integer(40);
Integer i5 = new Integer(40);
Integer i6 = new Integer(0);

// true
// Integer.valueOf()用了常量池,看上面的源码
System.out.println(i1 == i2);

// true
// + 操作会导致左右2边都转成基本数据类型
// 具体原因看下面
System.out.println(i1 == i2 + i3);

// false
// Integer.valueOf()使用常量池中的对象
// new Integer每次会创建新对象,
System.out.println(i1 == i4);

// false
// 2个不同的对象
System.out.println(i4 == i5);

// true、
// 解释在最下面
System.out.println(i4 == i5 + i6);

// true
// 解释在最下面
System.out.println(40 == i5 + i6);

语句i4 == i5 + i6,因为+这个操作符不适用于Integer对象,首先i5和i6进行自动拆箱操作,进行数值相加,即i4 == 40。然后Integer对象无法与数值进行直接比较,所以i4自动拆箱转为int值40,最终这条语句转为40 == 40进行数值比较

抽象类和接口

接口和抽象类的相似性

  • 都不能被实例化
  • 接口和抽象类都可以包含抽象方法,实现接口或继承抽象类的普通子类都必须实现这些抽象方法
  • 接口和抽象类的区别

  • 接口只能包含抽象方法,静态方法和默认方法,抽象类则可以包含普通方法
  • 接口里只能定义静态常量,不能定义普通成员变量,抽象类里既可以定义普通成员变量,也可以定义静态常量
  • 接口不能包含构造器,抽象类可以包含构造器。抽象类的构造器并不是用于创建对象,而是让其子类调用这些构造器来完成属于抽象类的初始化操作
  • 接口里不能包含初始化快,抽象类里可以包含初始化块
  • 一个类最多只能有一个直接父类,包括抽象类。但一个类可以实现多个接口
  • StringBuffer和StringBuilder的区别

    先来看String类的实现

    public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];
    }

    先来说一下final关键字的作用

    1.final修饰类时,表明这个类不能被继承
    2.final修饰方法,表明方法不能被重写
    3.final修饰变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象

    可以看到String类和保存变量的value数组都被final修饰,表明String类是不可变的。
    StringBuffer和StringBuilder都继承自AbstractStringBuilder类,看一下AbstractStringBuilder类的定义

    abstract class AbstractStringBuilder implements Appendable, CharSequence {
    /**
    * The value is used for character storage.
    */
    char[] value;
    }

    看到区别了吗?value数组没有用private和final修饰,说明了StringBuffer和StringBuilder是可变的。

    而StringBuilder和StringBuffer的方法是差不多的,只不过StringBuffer在方法上添加了
    synchronized关键字,所以在多线程环境下我们要用StringBuffer来保证线程安全,单线程环境下用StringBuilder来获得更高的效率。

    看2个类中同一个方法的定义

    // StringBuffer

    @Override
    public synchronized StringBuffer append(char[] str) {
    toStringCache = null;
    super.append(str);
    return this;
    }// StringBuilder

    @Override
    public StringBuilder append(char[] str) {
    super.append(str);
    return this;
    }

    因为StringBuffer和StringBuilder的实现类似,所以性能比较就落在String和StringBuilder之间了。

    1.String是不可变对象,每次操作都会生成新的String对象,然后将指针指向新的对象。
    2.抽象类AbstractStringBuilder内部提供了一个自动扩容机制,当发现长度不够的时候,会自动进行扩容工作(具体扩容可以看源码,很容易理解),会创建一个新的数组,并将原来数组的数据复制到新数组,不会创建新对象,拼接字符串的效率高。

    用源码证实一下

    // String

    public String substring(int beginIndex) {
    if (beginIndex < 0) {
    throw new StringIndexOutOfBoundsException(beginIndex);
    }
    int subLen = value.length - beginIndex;
    if (subLen < 0) {
    throw new StringIndexOutOfBoundsException(subLen);
    }
    return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
    }// StringBuilder

    @Override
    public StringBuilder append(String str) {
    super.append(str);
    return this;
    }

    介绍完毕,所以你应该知道这道题应该怎么答了

    常见面试题

    1.说一下String StringBuffer StringBuilder的区别?

  • 都是final类,不允许被继承
  • String长度是不可变的,StringBuffer,StringBuilder长度是可变的
  • StringBuffer是线程安全的,StringBuilder不是线程安全的。但它们方法实现类似,StringBuffer在方法之上添加了synchronized修饰,保证线程安全
  • StringBuilder比StringBuffer拥有更好的性能
  • 如果一个String类型的字符串,在编译时可以确定是一个字符串常量,则编译完成之后,字符串会自动拼接成一个常量,此时String的速度比StringBuffer和StringBuilder的性能好的多
  • 字符串拼接速度StringBuilder>StringBuffer>String
  • 我用例子解释一下第五条

    public static void main(String[] args) {
    String a = "a";
    String b = "b";
    String c = a + b;
    String d = "a" + "b" + "c";
    }

    反编译class文件后是这样的

    public static void main(String[] args) {
    String a = "a";
    String b = "b";
    (new StringBuilder()).append(a).append(b).toString();
    String d = "abc";
    }

    看string d,理解了吗?
    同时看string c的拼接过程,先生成一个StringBuilder对象,再调用2次append方法,最后再返回一个String对象,知道String比StringBuilder慢的原因了吧

    算法和数据结构

    二叉树

    定义

    二叉树是一种树形结构,它的特点是每个节点至多只有两颗子树(即二叉树中不存在度大于2的几点),并且,二叉树的子树有左右之分,其次序不能任意颠倒

    性质

  • 在二叉树的第i层上至多有个节点(i>=1)
  • 深度为k的二叉树至多有个节点(k>=1)
  • 对任何一颗二叉树T,如果其终端节点数为,度为2的节点数为,则
  • 具有n个节点的完全二叉树的深度为
  • 遍历二叉树

    先序遍历:根左右
    中序遍历:左根右
    后序遍历:左右根
    记忆方法:左右的位置不变,先序遍历根在最前面,中序遍历根在中间,同理,后序遍历根就在最后面了

    排序

    冒泡排序

    public class BubbleSort {

    //交换元素顺序
    public static void swap(int[] a, int i, int j) {
    int temp = a[i];
    a[i] = a[j];
    a[j] = temp;
    }

    //冒泡排序
    public static void bubbleSort(int[] a) {
    for (int i=0; i<a.length - 1; i++) {
    for (int j=0; j<a.length - 1 - i; j++) {
    if (a[i] > a[i + 1]) {
    swap(a, i, i + 1);
    }
    }
    }
    }

    public static void main(String[] args) {

    int[] a = {1, 5, 2, 4, 7, 6};
    bubbleSort(a);
    //[1, 2, 4, 5, 6, 7]
    System.out.println(Arrays.toString(a));
    }
    }

    选择排序

    public class SelectSort {

    public static void swap(int[] a, int i, int j) {
    int temp = a[i];
    a[i] = a[j];
    a[j] = temp;
    }

    public static void selectSort(int[] a) {
    for (int i=0; i<a.length; i++) {
    int min = a[i];
    int index = i;
    for (int j=i; j<a.length; j++) {
    if (a[j] < min) {
    min = a[j];
    index = j;
    }
    }
    if (index != i) {
    swap(a, i, index);
    }
    }
    }
    public static void main(String[] args) {
    int[] a = {1, 5, 2, 4, 7, 6};
    selectSort(a);
    //[1, 2, 4, 5, 6, 7]
    System.out.println(Arrays.toString(a));
    }
    }

    快速排序

  • 从数列中取出一个数作为基准数
  • 分区过程,将比它大的数全放到它的右边,小于或等于它的数全放到它的左边
  • 再对左右区间重复第二步,直到各区间只有一个数
  • public class QuickSort {


    public static int sort(int[] a, int low, int high) {
    int key = a[low];
    while (low < high) {
    //从high所指位置向前搜索找到第一个关键字小于key的记录和key互相交换
    while (low < high && a[high] >= key) {
    high--;
    }
    a[low] = a[high];
    //从low所指位置向后搜索,找到第一个关键字大于key的记录和key互相交换
    while (low < high && a[low] <= key) {
    low++;
    }
    a[high] = a[low];
    }
    //此时low和key相等
    a[low] = key;
    return low;
    }

    public static void quickSort(int[] a, int low, int high) {
    if (low < high) {
    int key = sort(a, low, high);
    quickSort(a, low, key - 1);
    quickSort(a, key + 1, high);
    }
    }

    public static void main(String[] args) {
    int[] a = {1, 5, 2, 4, 7, 6};
    quickSort(a, 0, a.length - 1);
    //[1, 2, 4, 5, 6, 7]
    System.out.println(Arrays.toString(a));
    }

    }

    归并排序

    假设初始序列含有n个记录,则可看成是n个有序的子序列,每个子序列的长度为1,然后两两归并,得到$ \lceil \frac{x}{2} \rceil $个长度为2或1的有序子序列,在两两归并,…,如此重复,直至得到一个长度为n的有序序列为止,这种排序方法称为2-路归并排序

    Java面试总结_包装类_07

    public class MergeSort {

    public static void sort(int[] src) {
    int[] temp = new int[src.length];
    msort(src,temp,0,src.length-1);
    }

    public static void msort(int[] src,int[] dest,int left,int right) {

    if (left < right) {
    int mid = (left + right) / 2;
    msort(src,dest,0,mid);
    msort(src,dest,mid+1,right);
    merge(src,dest,0,mid,right);
    }
    }

    public static void merge(int[] src,int[] dest,int left,int mid,int right) {
    int i = left;//左边数组的游标
    int j = mid + 1;//右边数组的游标
    int index = 0;//dest起一个中途存储的作用,这个是dest数组的游标
    while (i <= mid && j <= right) {
    if (src[i] <= src[j]) {
    dest[index++] = src[i++];
    } else {
    dest[index++] = src[j++];
    }
    }

    //复制左边剩余的数组
    while (i <= mid) {
    dest[index++] = src[i++];
    }
    //复制右边剩余的数组
    while (j <= right) {
    dest[index++] = src[j++];
    }
    index = 0;
    while (left <= right) {
    src[left++] = dest[index++];
    }
    }

    public static void main(String[] args) {

    int[] arr = {7,5,3,4,2,1,6,2,9,8};
    sort(arr);
    //[1, 2, 2, 3, 4, 5, 6, 7, 8, 9]
    System.out.println(Arrays.toString(arr));
    }
    }

    二分查找

    public class Search {


    public static void main(String[] args) {
    int test1[] = {3,4,5,1,2,7,9};
    //false
    System.out.println(binarySearch(test1, 10));
    //true
    System.out.println(binarySearch(test1, 9));
    }

    //从array数组中查找target的值,找到返回true,否则返回false
    public static boolean binarySearch(int[] array, int target) {

    //进行二分查找先进行排序
    Arrays.sort(array);
    int left = 0;
    int right = array.length -1;
    //注意是小于等于,如从123456中查找6,没等于不行
    while (left <= right) {
    int mid = (left + right) >> 1;
    if (target == array[mid]) {
    return true;
    } else if (target > array[mid]) {
    left = mid + 1;
    } else {
    right = mid - 1;
    }
    }
    return false;
    }
    }

    深度优先搜索

    给定图G的初始状态是所有顶点均未曾访问过,在G中任选一顶点v为初始出发点(源点或根节点),则深度优先遍历可定义如下:首先访问出发点v,并将其标记为已访问过,然后从v出发搜索v的每个未曾访问的的邻节点w,并以w为新的出发点继续进行深度优先遍历,直至图中所有和源点v有路径相通的顶点均被访问为止。若此时图中仍有未访问的顶点,则另选一个尚未访问的顶点作为新的源点重复上述过程,直至图中所有顶点均已被访问为止
    ##广度优先搜索
    设图G的初始状态是所有顶点均未访问过。以G中任选一顶点v为起点,则广度优先搜索定义为:首先访问出发点v,接着依次访问v的所有邻接点,…,,然后再依次访问与,…,邻接的所有未曾访问过的顶点。以此类推,直至图中所有和起点v有路径相通的顶点都已访问到为止。此时从v开始的搜索过程结束。
    若G是连通图,则一次就能搜索完所有节点;否则,在图G中另选一个尚未访问的顶点作为新源点继续上述搜索过程,直至G中所有顶点均已被访问为止

    用户点击页面到收到结果中间发生了什么,从servlet的角度

  • 用户点击页面发送查询请求->Web服务器应用(如Apache)->Web容器应用(如tomcat)
  • 容器创建两个对象HttpServletRequest和HttpServletResponse
  • 根据URL找到servlet,并为请求创建或分配一个线程,将请求和响应对象传递给这个servlet线程
  • 容器调用Servlet的service()方法,根据请求的不同类型,service()方法会调用doGet()和doPost()方法,假如请求是HTTP POST请求
  • doPost()查询数据库获得数据,并把数据增加到请求对象
  • servlet把请求转发给jsp,jsp为容器生成页面
  • 线程结束,容器把响应对象装换为一个HTTP请求,把它发回给客户,然后删除请求和响应对象
  • MySQL索引优化策略

    数据库事务的四个特性

    MySQL为什么要用B+树实现

    voliate

    线程池

    HashTable和ConcurrentHashMap的区别

    Lock

    CountDownLatch

    Spring MVC执行流程

    单例模式的5种写法

    Spring AOP和IOC

    spring ioc的用处

    nio

    netty

    jvm

    try catch

    求出数据库中重复的记录



    网友评论