小米(实习一面之牛客面经)

  1. 序列化和反序列化
  2. == 与equals方法有什么区别?
  3. final在java中有什么作用?
  4. Object类有哪些方法
  5. 事务四大特性:ACID
  6. 事务的隔离级别
  7. 数据库的索引原理
  8. Java实现多线程的四种方式
  9. hashmap(concurrenthashmap)
  10. 基本数据类型、装箱拆箱
  11. Thread.sleep VS Object.wait()
  12. wait()、notify()、notifyAll()
  13. 阻塞和等待的区别
  14. 除了new还有哪些方式创建对象
  15. 单例模式
  16. 工厂模式
  17. 动态代理
  18. Spring(IOC)
  19. 编程题

牛客面经:

  1. 序列化:将java对象转化为字节序列的过程。
    反序列化:将字节序列转化为java对象的过程。

  1. == 与equals方法有什么区别?
    == 操作符专门用来比较两个变量的值是否相等,也就是用来比较变量所对应的内存中所存储的数值是否相同。
    如果要比较两个基本类型的数据或者两个变量是否相等,只能用== 操作符
    equals方法是用来比较两个独立对象的内容是否相同?
    在实际开发中,我们经常使用比较传递进来的字符串内容是否相等。
    注意:字符串的比较基本上都是使用equals方法。
    总结:是用于比较两个变量的值,equals 是用于比较两个对象内容的
    基本类型的数据比较用
    ,引用类型的数据的比较用equals

  1. final在java中有什么作用?
    final修饰的类叫最终类,该类不能被继承。
    final修饰的方法不能被重写。
    final修饰的变量叫常量,常量必须初始化,初始化之后就不能被修改。
    总结:Java关键字,终态修饰符,类不可继承,属性不可修改,方法不可重写

  1. Object类有哪些方法
    Object是所有类的父类,任何类都默认继承Object。Object类到底实现了哪些方法?
    1.clone方法
    保护方法,实现对象的浅拷贝,只有实现了Cloneable接口才可以调用该方法,否则抛出CloneNotSupportedException异常。
    在这里插入图片描述

2.getClass方法
final方法,获得运行时类型。
3.toString方法
该方法用得比较多,一般子类都有覆盖。
4.finalize方法
该方法用于释放资源。因为无法确定该方法什么时候被调用,很少使用。
5.equals方法
该方法是非常重要的一个方法。一般equals和==是不一样的,但是在Object中两者是一样的。子类一般都要重写这个方法。
6.hashCode方法
该方法用于哈希查找,重写了equals方法一般都要重写hashCode方法。这个方法在一些具有哈希功能的Collection中用到。
一般必须满足obj1.equals(obj2)==true。可以推出obj1.hash- Code()==obj2.hashCode(),但是hashCode相等不一定就满足equals。不过为了提高效率,应该尽量使上面两个条件接近等价。
7.wait方法
wait方法就是使当前线程等待该对象的锁,当前线程必须是该对象的拥有者,也就是具有该对象的锁。wait()方法一直等待,直到获得锁或者被中断。wait(long timeout)设定一个超时间隔,如果在规定时间内没有获得锁就返回。
调用该方法后当前线程进入睡眠状态,直到以下事件发生。
(1)其他线程调用了该对象的notify方法。
(2)其他线程调用了该对象的notifyAll方法。
(3)其他线程调用了interrupt中断该线程。
(4)时间间隔到了。
此时该线程就可以被调度了,如果是被中断的话就抛出一个InterruptedException异常。
8.notify方法
该方法唤醒在该对象上等待的某个线程。
9.notifyAll方法
该方法唤醒在该对象上等待的所有线程。


  1. 事务四大特性:ACID
  • 原子性(Atomicity)原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。无论是操作系统崩溃,还是计算机停止运行,这项要求都要成立。
  • 一致性(Consistency)事务作为一个原子性操作,它从一个一致性的数据库状态开始运行,事务结束时,数据库的状态必须再次是一致的。
  • 隔离性(Isolation)尽管多个事务可能并发执行,但系统保证,对于任何一对事务Ti和Tj ,在Ti看来, Tj要么在Ti开始之间已经完成,要么在Ti完成之后才开始执行。因此,每个事务都感觉不到系统中有其他事务在并发地执行。
  • 持久性(Durability)一个事务成功完成后,它对数据库的改变必须是永久的,即使出现系统故障。
    ACID 可以说是事务的四大特性,在这四个特性中,原子性是基础,隔离性是手段,一致性是约束条件,而持久性是我们的目的。

  1. 事务的隔离级别
  • 读未提交(read uncommitted)
    允许读取未提交的数据。

  • 读已提交(read committed)
    只允许读取已提交数据,但不要求可重复读。比如,在事务两次读取一个数据项期
    间,另一个事务更新了该数据项并提交。

  • 可重复读(repeatable read)
    只允许读取已提交数据,而且在一个事务两次读取一个数据项期间,其他事务不得更新该数据。但该事务不要求与其他事务可串行化。比如,在两次统计查询中,另一个事务可以插入一些记录,当这些记录中有符合查询条件的,那么就会产生幻读。

  • 可串行化(serializable)
    看起来事务就好像是串行执行的一样。一个事务结束后,另一个事务才开始执行。

隔离性依次增高。
read uncommitted  read committed  repeatable read  serializable


  1. 数据库的索引原理
    能够快速查找的数据结构都可以作为索引。
    哪些数据结构可以做索引?
    有序数组、哈希表(hash索引)、平衡二叉树、B树、B+树。
    有序数组:查找快、增删慢
    哈希表:增加、删除,等值查询都很快。但是区间查找,排序等操作会很慢。(因为哈希表中的元素都是无序的)
    平衡二叉树(BST):不管是,增加,删除,还是等值查找,时间复杂度都是O(logn),n 是数据页的数目。
    并且支持范围查找(BST有序)。
    但是当数据量比较大,页的数目很多时,二叉树的高度会比较高。IO 的次数会比较多。
    查找效率低。
    B树
    B+树
    B+树索引又可以分为聚簇索引和辅助索引。它们之间的不同在于,聚集索引的叶子节点存储的是一整行记录的信息,而辅助索引的叶子节点只存放部分信息 (关键字和主键)。

联合索引:联合索引是指对表的多个列进行索引
最左前缀法则。
覆盖索引:InnoDB 存储引擎支持覆盖索引 (covering index), 即从辅助索引中就可以得到要查询的信息,而不需要回表。


  1. Java实现多线程的四种方式
  • 方式一:继承Thread类的方式
    创建一个继承于Thread类的子类
    重写Thread类中的run():将此线程要执行的操作声明在run()
    创建Thread的子类的对象
    调用此对象的start():①启动线程 ②调用当前线程的run()方法

  • 方式二:实现Runnable接口的方式
    创建一个实现Runnable接口的类
    实现Runnable接口中的抽象方法:run():将创建的线程要执行的操作声明在此方法中
    创建Runnable接口实现类的对象
    将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
    调用Thread类中的start():① 启动线程 ② 调用线程的run() —>调用Runnable接口实现类的run()
    以下两种方式是jdk1.5新增的!

  • 方式三:实现Callable接口
    与使用Runnable相比, Callable功能更强大些
    实现的call()方法相比run()方法,可以返回值
    方法可以抛出异常
    支持泛型的返回值
    需要借助FutureTask类,比如获取返回结果
    Future接口可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等。
    FutureTask是Futrue接口的唯一的实现类
    FutureTask 同时实现了Runnable, Future接口。它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值

  • 方式四:使用线程池
    提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
    好处:
    提高响应速度(减少了创建新线程的时间)
    降低资源消耗(重复利用线程池中线程,不需要每次都创建)
    便于线程管理


  1. hashmap(concurrenthashmap)

Map接口概述:

将键映射到值的对象。(我们可以根据键快速地查找到值)
Map 中键是唯一的 (不能包含重复的键)
每个键最多只能映射到一个值。

如果所有的键都是小整数,我们可以用一个数组来实现的符号表,将键作为数组的索引,而数组中对应的位置存储键关联的值。

哈希表的核心算法可以分为两步。

用哈希函数将键转换为数组中的一个索引。理想情况下不同的键都能转换成不同的索引值。当然这只是理想情况下,所以我们需要处理两个或者多个键都散列到相同索引值的情况 (哈希碰撞)。

处理碰撞冲突。
a. 开放地址法
线性探测法, 平方探测法, 再散列法…
b. 拉链法

HashMap概述:
基于哈希表的Map接口实现。
允许null键和null值。
不保证映射的顺序,特别是它不保证该顺序恒久不变。
不同步。

HashMap VS Hashtable
相同点:
底层的数据结构都是哈希表
不同点:
a. HashMap是不同步的, Hashtable是同步的
b. HashMap可以允许null键和null值,Hashtable不允许null键和null值

LinkedHashMap概述:
HashMap的子类
Map 接口的哈希表和链表实现,具有可预知的迭代顺序.
链表定义了迭代顺序,该迭代顺序就是键值对的插入顺序。
不同步。

TreeMap概述:
底层的数据结构是红黑树。
如果创建对象时,没有传入 Comparator 对象,键将按自然顺序进行排序。
如果创建对象时,传入了 Comparator 对象,键将按 Comparator 进行排序。
不同步。

Java 给我们提供了哪些并发安全的 Map?我们应该选择哪一个?

  • Hashtable (看一下hashtable源码会发现hashtable有很多方法用的synchronized nchronized 方法,这样的话锁对象就是this,我们知道哈希表table的每个index下挂着一个链表,假如我在index=1的链表下进行增删查操作是不会影响index=2下的数据的,但由于hashtable的锁对象是this,所以,它不允许我们进行以上操作,尽管以上操作并不会发生线程安全,所以,hashtable的并发度是很低的 )

  • Collections.synchronizedMap(Map map) (Collections是一个工具类,并发度和hashtable一样)
    static <K,V> SortedMap<K,V> synchronizedSortedMap(SortedMap<K,V> m)
    返回指定有序映射支持的同步(线程安全的)有序映射。

  • ConcurrentHashMap(用它)
    Hashtable 锁对象是 this对象
    Collections.synchronizedMap(Map map) 锁对象是 mutex
    JDK8 ConcurrentHashMap 锁对象是链表的头结点。 大大提高并发度。这样的话,关于起那么的index=1和index=2上两条链表的操作就可以同时进行了。


  1. 基本数据类型、装箱拆箱
  • 装箱 把一个基本数据类型的值 -> 封装到了其包装类的对象
    对基本类型的数据,创建一个包装类对象,并且将其对应的基本数据类型的值,封装到该对象中
    把自动创建的这个Integer对象的引用,指向这个包装类对象
    在绝大部分场景下,基本数据类型的变量都和其对应的包装类对象,可以自动相互赋值(自动装箱和拆箱)

  1. Thread.sleep VS Object.wait()
    相同点是两者都可以使一个线程处于阻塞状态
  • 所属不同:
    a. sleep定义在Thread类,静态方法
    b. wait定义在 Object类中,非静态方法
  • 唤醒条件不同
    a. sleep: 休眠时间到
    b. wait: 在其他线程中,在同一个锁对象上,调用了notify或notifyAll方法
  • 使用条件不同:
    a. sleep 没有任何前提条件
    b. wait(), 必须当前线程,持有锁对象,锁对象上调用wait()
  • 休眠时,对锁对象的持有,不同:(最最核心的区别)
    a. 线程因为Sleep方法而处于阻塞状态的时候,在阻塞的时候不会放弃对锁的持有
    b. 但是wait()方法,会在阻塞的时候,放弃锁对象持有

  1. wait()、notify()、notifyAll()

阻塞自己

wait() (在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前)导致当前线程等待/阻塞
阻塞功能:
在哪个线程中调用了 对象a.wait(),该方法导致调用wait方法的线程处于阻塞状态
唤醒条件:
在 其他线程 调用 此对象的 notify() 方法或 notifyAll()

即因为哪个对象的wait()而处于阻塞状态,要唤醒该线程,必须在同一个对象上调用其notify() 或者 notifyAll()
a.wait() 其他线程 a.notify() 或者notifyAll方法

前提条件:
当前线程 必须 拥有此对象监视器 (当前线程必须持有这个锁对象)
即,当前线程如果要想正确运行wait(),前提条件是 必须在当前线程所持有的 锁对象.wait() 。
说白了,你想用锁对象.wait(),首先得写在synchronized (o) { }中。

wait() 方法除了使线程阻塞之外它还做了什么:
该线程 发布对此监视器(监视器即锁对象)的所有权 (当前线程一旦调用了wait()方法,当前线程释放锁) 并 等待
即阻塞当前线程

直到其他线程通过调用 notify 方法,或 notifyAll 方法通知在此对象的监视器上等待的线程醒来。
然后该线程将等到重新获得对监视器的所有权后才能继续执行。就是说唤醒你之后你得取争抢锁对象,才能继续往下执行

通知别人
notify()
唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的

notifyAll()
唤醒在此对象监视器上等待的所有线程

  1. 阻塞和等待的区别:

BLOCKED
一个线程因为等待临界区的锁被阻塞产生的状态
Lock 或者synchronize 关键字产生的状态
进入 blocked 状态是被动的。

WAITING
一个线程进入了锁,但是需要等待其他线程执行某些操作。时间不确定
当wait,join,park方法调用时,进入waiting状态。前提是这个线程已经拥有锁了。
进入 waiting 状态是线程主动的。


  1. 除了new还有哪些方式创建对象
  • Class对象的newInstance()方法
    还是上面的Test对象,首先我们通过Class.forName()动态的加载类的Class对象,然后通过newInstance()方法获得Test类的对象
public static void main(String[] args) throws Exception {
    String className = "org.b3log.solo.util.Test";
    Class clasz = Class.forName(className);
    Test t = (Test) clasz.newInstance();
}
  • 构造函数对象的newInstance()方法
public static void main(String[] args) throws Exception {
    Constructor<Test> constructor;
   try {
        constructor = Test.class.getConstructor();
       Test t = constructor.newInstance();
   } catch (InstantiationException |
        IllegalAccessException |
        IllegalArgumentException |
        InvocationTargetException |
        NoSuchMethodException |
        SecurityException e) {
        e.printStackTrace();
   }
}

  • Object对象的clone()方法
    Object对象中存在clone方法,它的作用是创建一个对象的副本。看下面的例子,这里我们依然使用第一个例子的Test类
public static void main(String[] args) throws Exception {
    Test t1 = new Test("张三");
    Test t2 = (Test) t1.clone();
    System.out.println(t2.getName());
}
  • 对象反序列化
    使用反序列化来获得类的对象,那么这里必然要用到序列化Serializable接口,所以这里我们将第一个例子中的Test作出一点改变,那就是实现序列化接口。
public class Test implements Serializable{

    private String name;
    public Test() {
    }

    public Test(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public static void main(String[] args) throws Exception {
       String filePath = "sample.txt";
     Test t1 = new Test("张三");
     try {
        FileOutputStream fileOutputStream =
               new FileOutputStream(filePath);
        ObjectOutputStream outputStream =
               new ObjectOutputStream(fileOutputStream);
        outputStream.writeObject(t1);
        outputStream.flush();
        outputStream.close();

        FileInputStream fileInputStream =
               new FileInputStream(filePath);
        ObjectInputStream inputStream =
               new ObjectInputStream(fileInputStream);
        Test t2 = (Test) inputStream.readObject();
        inputStream.close();
        System.out.println(t2.getName());
     } catch (Exception ee) {
           ee.printStackTrace();
     }
    }
}

  1. 单例模式
  • 懒加载
    在这里插入图片描述

  • 立即加载

public class Singleton {
	private Singleton();
	static Singleton st = new Singleton();
	public static Singleton getInstance(){
		return st;
	}
}

  1. 工厂模式
    在这里插入图片描述

  1. 动态代理
    jdk动态代理:
package com.cskaoyan;

import com.cskaoyan.service.HelloService;
import com.cskaoyan.service.HelloServiceImpl;
import org.junit.Test;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * 动态代理
 */
public class ProxyTest {

    /**
     * 没有使用动态代理
     */
    @Test
    public void mytest() {
        //获得委托类对象
        HelloService helloService = new HelloServiceImpl();
        System.out.println("起床");
        helloService.sayHello();
        System.out.println("编程");
    }

    /**
     * 使用jdk动态代理
     */
    @Test
    public void mytest2() {
        //在hello world之前 起床
        //hello world之后 编程

        //先获得委托类对象的实例
        HelloService helloService = new HelloServiceImpl();
        //去获得一个代理对象来完成增强 → jdk动态代理获得增强对象(代理对象)
        //classloader都是和委托类对象相关的
        //interfaces都是和委托类对象相关的
        HelloService helloServiceProxy = (HelloService) Proxy.newProxyInstance(HelloServiceImpl.class.getClassLoader(),
                helloService.getClass().getInterfaces(), new InvocationHandler() {
                    //invoke中
                    //1.是要去执行委托类的方法
                    //2.可以去做增强
                    //返回值:Object 对应委托类方法执行的返回值
                    //参数:
                    //  proxy :代理对象
                    //  method: 委托类方法的method
                    //  args: 委托类方法的参数
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("起床");
                        //invoke方法中可以使用反射来执行委托类的方法
                        //第一个参数是委托类对象,而不是代理对象
                        Object invoke = method.invoke(helloService, args);
                        System.out.println("编程");
                        return invoke;
                    }
                });

        //helloServiceProxy.sayHello(); //使用代理对象去执行才会增强

        //helloService.sayHello(); //使用委托对象只会输出hello world

        helloServiceProxy.method2();

    }
}

  1. Spring(IOC)

IOC用于实例化类对象,管理类对象
IOC:Inverse of Controll 控制反转
控制:实例的生成权
反转:由应用程序反转给spring
容器:容器是放置实例对象的地方 → Spring容器、IOC容器

原先实例我们想用的时候自己new出来(主动的过程);到了Spring阶段,把实例的生成权交给了Spring容器,由Spring容器进行生成并管理,当我们想用的时候就从容器中取出来。

DI:Dependency Injection 依赖注入
依赖:
谁依赖谁? 应用程序依赖Spring容器
为什么依赖? Spring容器包含了应用程序必须的内容
注入:
谁注入谁? Spring容器注入给应用程序
注入了什么?应用程序运行所必须的资源

AOP:面向切面编程
作用:给容器中的组件做增强
之前做增强是通过oop(面向对象)编程,继承父类(静态代理)或传入委托类对象(动态代理)。

之前是使用动态代理给一个类生成代理对象
aop是给容器中的组件批量生成代理对象 → 把要增强的方法放到一起 → 这个范围称为切面。aop是一个范围更广的增强


  1. synchorized和volatile区别

  1. 四道编程题
  • 快速排序实现(手写代码)
public class 快速排序 {

    public static void main(String[] args) {
        int[] nums = new int[]{1, 9, 5, 4, 2, 7, 0, 2, 6, 8};
        QuickSort(nums, 0, nums.length - 1);
        System.out.println(Arrays.toString(nums));
    }

    public static void QuickSort(int nums[], int low, int high) {
        int temp;
        int i = low, j = high;
        if (low < high) {
            temp = nums[low];
            while (i < j) {
                while (i < j && nums[j] >= temp) j--;
                if (i < j) {
                    nums[i++] = nums[j];
                }
                while (i < j && nums[i] < temp) i++;
                if (i < j) {
                    nums[j--] = nums[i];
                }
            }
            nums[i] = temp;
            QuickSort(nums, low, i - 1);
            QuickSort(nums, i + 1, high);
        }
    }
}


  • 合并两个链表(手写代码)
public ListNode mergeTwoLists(ListNode h1, ListNode h2) {
	ListNode head = new ListNode(-1);
	ListNode h = head;
	while(h1 != null && h2 != null){
		if(h1.val < h2.val){
			head.next = h1;
			h1 = h1.next;
		}else{
			head.next = h2;
			h2 = h2.next;
		}
		head = head.next;
	}
	if(h1 != null) head.next = h1;
	if(h2 != null) head.next = h2;
	return h.next;
}
  • 二路归并排序
package com.leetcode;

import java.util.Arrays;

/**
 * @author liushihao <liushihao@kuaishou.com>
 * Created on 2021/2/25 10:32 下午
 */
public class MergeSort {

    public static void main(String[] args) {
        int[] nums = new int[]{1, 9, 5, 4, 2, 7, 0, 2, 6, 8};
        mergeSort(nums, 0, nums.length - 1);
        System.out.println(Arrays.toString(nums));
    }

    //二路归并排序
    public static void mergeSort(int nums[], int low, int high) {
        if (low < high) {
            int mid = (low + high) / 2;
            mergeSort(nums, low, mid);
            mergeSort(nums, mid + 1, high);
            merge(nums, low, mid, high);
        }
    }

    public static void merge(int[] a, int low, int mid, int high) {
        int[] temp = new int[high - low + 1];
        int i = low;
        int j = mid + 1;
        int k = 0;
        // 把较小的数先移到新数组中
        while (i <= mid && j <= high) {
            if (a[i] < a[j]) {
                temp[k++] = a[i++];
            } else {
                temp[k++] = a[j++];
            }
        }
        // 把左边剩余的数移入数组
        while (i <= mid) {
            temp[k++] = a[i++];
        }
        // 把右边边剩余的数移入数组
        while (j <= high) {
            temp[k++] = a[j++];
        }
        // 把新数组中的数覆盖nums数组
        for (int x = 0; x < temp.length; x++) {
            a[x + low] = temp[x];
        }
    }
}
  • 树的最大距离

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

-玫瑰少年-

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值