4

小迈科技一面_延年有余的技术博客_51CTO博客

 1 year ago
source link: https://blog.51cto.com/fyphome/5723447
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

简单三分钟自我介绍

自我介绍这里一笔带过,给对面介绍自己内在 + 外在 + 校园经历 + 校园项目 + 意向岗位

1. Java基础

1.1 序列化和反序列化

首先了解一下序列化和反序列化的概念

序列化:将Java对象以二进制即字节码的形式保存在磁盘文件中,可以说是保存Java对象状态的过程,序列化可以实现对象保存的持久化;

反序列化:将保存在磁盘文件中的Java字节码重新转换为Java对象的过程;

其他特点:一般RPC框架底层协议通信就是通过序列化和反序列化在网络上传输Java对象。

序列化和反序列化的实现主要有两种,准确来说有三种方法:

采用默认的序列化方式,即通过ObjectOutPutStream类的writeObject(OutputStream out)方法来序列化到输出流中

输出流可以选择文件流、也可以选择管道流,甚至是二进制流

文件流是直接写到文件中再读取转成对象,管道流是通过管道缓存数据,然后再通过输入流连接管道读取转成对象

序列化的对象必须实现Seriablized接口,才能完成序列化和反序列化操作

class Student implements Serializable {
   private String name;
   private Integer age;
}
  • 第一种实现(采用默认)
//FileOutputStream fos = new FileOutputStream("D:\\student.txt");
      PipedOutputStream pos = new PipedOutputStream();
      PipedInputStream pis = new PipedInputStream();
      pis.connect(pos);
      ObjectOutputStream oos = new ObjectOutputStream(pos);

      Student student = new Student();
      student.setAge(21);
      student.setName("zs");
      oos.writeObject(student);

      //FileInputStream fis = new FileInputStream("D:\\student.txt");
      ObjectInputStream ois = new ObjectInputStream(pis);
      Student res = (Student) ois.readObject();
      System.out.println(res);
  • 第二种实现(自定义)
// 自定义协议 比如我可以将加入对象头 cafe babe
   private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
      System.out.println("自定义反序列化");
      int magic = in.readInt();
      this.name = (String) in.readObject();
      this.age = (Integer) in.readObject();
   }

   // 自定义协议 比如我可以将加入对象头 cafe babe
   private void writeObject(ObjectOutputStream out) throws IOException {
      System.out.println("自定义序列化");
      // 4 字节 魔数
      out.writeInt(0xCAFEBABE);
      out.writeObject(this.name);
      out.writeObject(this.age);
   }
  • 第三种(自定义)

实现接口Externalizable并重写方法,其实跟第二种差别不大,只是第二种有默认的私有方法

@Override
   public void writeExternal(ObjectOutput out) throws IOException {
      System.out.println("实现 Externalizable 接口的自定义序列化");
      out.writeObject(this.name);
      out.writeObject(this.age);
   }

   @Override
   public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
      System.out.println("实现 Externalizable 接口的自定义反序列化");
      this.name = (String) in.readObject();
      this.age = (Integer) in.readObject();
   }

解决反序列化破坏单例

  • 在单例模式中,序列化可以破坏单例,这是需要重写readResolve() 方法,将之前的单例实例对象返回即可保证单例。

解决反射破坏单例

反射创建对象根本也是要调用构造方法,而且可以无视构造方法的访问修饰符(publicprivate

  • Java规定反射不能破坏枚举类型,采用枚举构造单例
  • 构造方法执行前,单例实例逻辑上不应该创建而且只执行一次,可判断是否已创建来避免反射创建多个实例,前提单例已先于反射

1.2 说一说数据类型有哪些

Java有八种基本数据类型 + String

1个位的bit,布尔类型的boolean,2个字节的short,4个字节的intchar,8个字节的longdoublefloat

其中基本类型中的包装类型常用的有Integer、Long

其中Integer会自动进行拆封装处理,也就是可以直接跟int类型比较数值上的大小

底层在-128-127之间会做缓存,在这之间通过Integer.valueOf(int)创建的对象都是同一个,使用了享元设计模式

System.out.println(new Integer(1) == 1);
System.out.println(Integer.valueOf(1) == Integer.valueOf(1));
System.out.println(Integer.valueOf(128) == Integer.valueOf(128));
true
true
false

1.3 数据结构

给你你个浏览器,要求设计前进和后退的数据结构,优先考虑性能

那么由于浏览器前进后退访问,是一种FIFO的结构,比如你连续点击前进几个页面,最先进的页面最后返回,即LIFO

那么可以考虑栈的结构设计,栈结构设计要考虑性能,首先我们可以分析到,浏览器页面跳转没有涉及到页面的修改,即用于查询

那么优先考虑数组而不考虑链表

我们可以设计两个栈数组,一个用于入栈已前进的页面,一个用于入栈已后退的页面,取其中一个栈顶元素作为当前页面

2. MySQL

2.1 索引

在MySQL中,直接影响索引类型的是数据库的存储引擎

使用MyIsam存储引擎,数据文件和索引是分开的,索引会另外存储在另一个文件中

使用InnoDB存储引擎,数据和索引都存储在同一个文件中,而且是以B+数的数据结构存储,非叶子节点存储索引,叶子节点存储索引和索引对应的数据

就MyIsam引擎来说,索引中最主要的是聚簇索引,也就是主键的默认索引

单值索引和多值索引,这里是指组合索引,好的组合索引可以达到覆盖索引,可以做到避免回表,这里是因为叶子节点通过覆盖索引带了数据,因为B+树只有叶子节点带有数据,非叶子节点都是索引

而多个组合索引下,有效索引要做到左匹配,也就是必须从左顺序匹配到右查询,否则索引将失效

2.2 口述 sql

给你一张表,有三个字段,id、产品id、备注信息,现在需要你查询相同产品id的记录,然后id值相同的记录数大于等于5的产品id

首先呢,我们可以先定义这张表为S

因为涉及相同字段的记录,可以考虑直接分组,使用聚合函数

select pro_id from S
group by pro_id
having count(pro_id) >= 5

3. JVM

3.1 线程的死锁了解过吗?

比如有两个线程t1和t2,t1线程有资源r1,t2有资源r2,t1线程执行代码中需要资源r2,不过这段代码需要t2线程把资源r2释放才能执行,此时t2线程也执行代码中,释放资源前需要获取资源r1,但t2又需要r2,此时处于相互等待的状态,就导致了死锁。

从Java角度来说,资源相当于Java的锁对象,是互斥的,一个线程获取锁对象后,另一个线程只能等待,t1线程获取锁对象r1,也就是占有了锁r1,这时线程t2也是占有了锁对象r2,而线程t1需要获取锁对象r2才能往下执行代码,使得线程t1阻塞,而线程t2处于阻塞等待锁对象r1释放,才能释放锁对象r2,这样就导致死锁。(互相等待)

4. JUC

4.1 线程池有了解过吗?

线程池主要有核心线程数、救急线程和队列,队列分为阻塞队列和非阻塞队列。

目前线程池主要可以分为几类:

只有核心线程、无救急线程的线程池,此时等待队列中有要执行的任务,而核心线程在轮询地执行等待队列中的任务,如果队列满或者队列是无界队列,可能导致内存溢出问题。

一般这种线程池的做法就是使用了拒绝策略,拒绝策略可分为直接丢弃新任务、异常抛出(主动逻辑处理)、丢弃等待队列头结点、提交任务线程执行。

最后一个是推荐使用的

  • 不会造成数据丢失,也就不会出现业务损失;
  • 提交任务的线程被占用,新的任务不可提交,减缓任务提交的速度,相当于负反馈,能给到线程池一定的缓冲期;

只有救急线程的线程池,这种的话有线程池newCachedThreadPool,它能无限创建救急线程,队列采用SynchronousQueue ,是一个没有容量的队列,只有线程取任务时才能提交任务;

最后是一种核心线程数只有1的线程池,没有救急线程,任务队列无界,一般作为单线程任务,这样就不会有CPU的轮询切换,任务的执行效率最高,不过请求数太多的情况下,也是容易导致内存溢出。

非阻塞队列实现的有CurrentLinkedQueue,它是通过CAS无锁化机制的线程池队列,每个线程通过for(;;)执行,是一个单向且通过GC自动回收出队节点的,利用可达性算法分析,将next指向自己,即可触发延迟回收,利用元素的不可重用性,规避ABA问题

5. Linux

给你一个日志文件,要求你查询最近一天内中,匹配到关键字的所有记录数据

一般日志实时查询可以使用该命令,它会实时更新,而且会处于fg模式,tail打印文件末尾记录,也就是最近

tail -100f catalina.log | grep "关键字" 

而使用cat是查询历史,只能打印日志到屏幕

比如按照tailhead来查询关键字为20:的后5条数据并显示行号

cat -n log.log4j | grep "20:" | tail -n 5

时间段查询

grep '2022-08-21 20:1[1-9]' log.log4j
cat -n log.log4j | grep -E "关键字|2022-08-21 20:1[0-9]" | tail -n 10

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK