饿了么面经

1.java中的List怎么实现线程安全

1.使用synchronizedList:它可以将一个普通的lIst包装为线程安全的List
  List<String> synchronizedList = Collections.synchronizedList(new ArrayList<>());
  
2.CopyOnWriteArrayList:适合读多写少的场景,在修改操作时会创建一个新的数组副本。
                        当多个线程来执行修改操作时:每个线程都会在自己的副本数组上执行修改操作,
                        互不干扰,当修改完成后会替换原始数组,这个操作是原子的,确保了数据的
                        一致性,当其它线程发现数组已经改变了,就会重新复制副本,继续执行之前的
                        修改操作。
                        
3.使用锁:在修改操作之前加锁,在完成后释放锁。可以使用synchronized或Reentrantlock

2.使用spring AOP去打印日志和拦截器去打印日志的区别

1.AOP:具有更强的解耦能力,将日志逻辑和业务逻辑分开。
       可以通过在配置中指定切点来选择性的进行日志打印,从而在需要的地方添加日志,而不必在每个方法
       中重复添加日志代码
       
2.拦截器:拦截器适用于请求处理的全局控制,不仅限于方法调用。因此,可以在请求处理的各个阶段添加日志,
         例如在请求进入时、处理中间、响应返回时等。
         拦截器常用于web请求处理,对于方法调用等逻辑业务可能不太适合
         
3.总的来说,AOP适合在方法调用层面上进行横切关注点处理,而拦截器适用于更广泛的请求处理环境

3.适配器模式的实现方式

将已经存在的类的接口,转换成需要的类的接口

1.类适配器模式:定义一个适配器类来实现当前系统的业务接口,同时又继承现有组件库中已经存在的组件

2.对象适配器模式:适配器类不用再去继承是配置类,而是将适配者类聚合在适配器类中

4.简单工厂模式、工厂方法模式和抽象工厂模式的区别

1.简单工厂:通过一个工厂类来创建对象,客户端不需要直接实例化具体的对象,而是通过调用工厂方法来获取
           所需要的对象

2.工厂方法:定义一个用于创建对象的接口,让子类决定实例化哪个对象

3.抽象工厂:每个具体工厂负责创建一个产品族的对象

5.接口和抽象类有哪些区别。什么时候使用抽象类、什么时候使用接口

接口:1.一个类可以实现一个或多个接口,通过实现接口中定义的方法来达到多态性的目的
     2.接口中只能包含未实现的方法
     4.接口中的方法和变量之类的不需要public private等修饰符的修饰
     4.适用于描述一组相关的行为,实现类需要遵循相同的方法约定

抽象类:1.一个类只能继承一个抽象类
       2.抽象类中可以有抽象方法也可以有普通方法
       3.不能被实例化,只能被继承
       4.适用于一些具有共同实现的类,并且这些类之间可能存在一个通用的行为,但又需要保留一些方法的
         实现,留给子类进行具体实现

区分:
1.接口:当需要定义一个约定,以便多个类共同遵循,而不关心它们的实现细节时。
       多个不相关的类需要实现相同的行为时。
2.抽象类:当需要为多个相关类提供一些通用的实现代码,但又要求子类提供特定的实现时。
         抽象类可以包含实现方法,这样子类可以继承通用实现并修改。

6.spring事务的底层是怎么实现的

spring通过AOP和代理模式来实现事务的底层支持

1.代理模式:spring使用代理模式来在方法调用前后插入额外的逻辑,来实现事务管理。

2.事务管理器:spring提供了事务管理器接口,用于同意管理事务。不同的数据源和事务管理需求可以实现这个
            接口,以适应不同的数据库和事务模式。

3.事务传播机制:sprign定义了不同的事务传播机制

4.事务隔离级别:spring允许配置不同的事务隔离级别

5.事务回滚策略:spring允许根据特定的异常类型来决定是否回滚事务

6.事务的开启:当标记了@Transactional注解的方法被调用时,spring会通过AOP创建一个事务代理。这个代理
            会在方法执行前开启一个数据库连接,并将其与当前线程绑定。开启连接的过程可能涉及到连接池
            的管理,以提高性能和资源利用率。

7.数据库操作:在方法内部执行数据库操作,如SQL语句的执行,这些操作会在之前开启的数据库连接上执行,
            确保所有操作在同一个连接下完成

8.事务的提交:如果方法成功执行完成,代理会调用事务管理器的提交方法。事务管理器将确保之前的数据库
            操作都在同一个事务中提交,以确保数据的一致性。提交事务的过程可能会涉及到数据库引擎的
            处理,确保数据的持久性

9.事务的回滚:如果方法在执行过程中抛出了异常,或者事务管理器检测到回滚标志,代理会调用事务管理器的
            回滚方法。事务管理器将撤销之前的数据库操作,将数据库回滚到事务开启时的状态,从而保证
            数据的一致性和完整性

10.数据库事务的底层机制:
开启事务
执行SQL
记录日志:在执行每个修改操作之后,数据库会将相应的日志记录下来,包括操作类型、表名、行ID和修改前后的           数据
事务的提交或回滚:如果没有出现问题就提交,如果出现问题,数据库会回滚事务,将之前的操作全部撤销
持久性:事务一旦提交,数据库会确保将修改后的数据持久化的存储在磁盘上

7.ACID

原子性:指的是一个事务中的所有操作要么全部成功,要么全部失败回滚。
       数据库通过记录事务的日志来实现原子性。当事务开始时,DBMS将开始记录日志,记录每个操作的变更。
       如果事务失败,DBMS可以使用日志来撤销之前的操作,将数据库回滚到事务开始前的状态

一致性:确保事务在完成后,数据库从一个一致的状态转换到另一个一致的状态。事务的操作应该遵循事先定义
       好的业务规则和约束,以保证数据的合法性和完整性

隔离性:指多个并发事务之间的操作是相互隔离的,一个事务的操作不应该影响其它事务的操作。数据库通过
       多版本并发控制来实现隔离性。MVCC允许事务在读取数据时不被其他事务的写操作干扰,从而避免了
       脏读、不可重复读和幻读问题

持久性:确保一旦提交事务,其结果将永久保存在数据库中,即使发生系统崩溃或其他规章也不会丢失。
       数据库通过将事务的变更写入磁盘来实现持久性。通常,数据库在事务提交后,会将相关数据库写入
       到事务日志中,以确保数据的持久化

8.存储int类型的LRU

public class LRUCache {

    private final int capacity;
    private final Map<Integer,Node> map;
    private Node head;//头结点
    private Node tail;//尾节点

    public LRUCache(int capacity) {
        this.capacity = capacity;
        map = new HashMap<>();
        head = new Node(-1,-1);
        tail = new Node(-1,-1);
        head.next = tail;
        tail.prev = head;
    }

    //从头部插入节点 表示最新的节点
    private void addNode(Node node) {
        Node next = head.next;
        head.next = node;
        node.prev = head;
        node.next = next;
        next.prev = node;
    }

    //删除节点
    private void removeNode(Node node) {
        Node prev = node.prev;
        Node next = node.next;
        prev.next = next;
        next.prev = prev;
    }

    private int get(int key){
        if (map.containsKey(key)){
            Node node = map.get(key);
            //删除当前节点
            removeNode(node);
            //在链表头部重新插入它
            addNode(node);
            return node.value;
        }
        return -1;
    }

    private void put(int key,int value){
        if (map.containsKey(key)){
            Node node = map.get(key);
            removeNode(node);//删除旧的节点
            node.value = value;
            //插入新的节点
            addNode(node);
        } else {
            //如果容量到了 就删除最近未使用的 也就是链表尾的节点
            if (map.size() >= capacity){
                Node remove = tail.prev;
                removeNode(remove);
                map.remove(remove.key);
            }
            Node node = new Node(key, value);
            map.put(key,node);
            addNode(node);
        }
    }

}
class Node {

    public int key;

    public int value;

    public Node prev;

    public Node next;

    public Node(int key,int value){
        this.key = key;
        this.value = value;
    }

}

9.存储任意类型的LRU

public class LRUCache<K,V> {

    private final int capacity;

    private final Map<K,Node<K,V>> map;

    private Node<K,V> head;

    private Node<K,V> tail;

    public LRUCache(int getCapacity){
        this.capacity = getCapacity;
        map = new HashMap<>();
        head = new Node<>(null,null);
        tail = new Node<>(null,null);
        head.next = tail;
        tail.prev = head;
    }

    //增加节点都是加到头部节点
    private void addNode(Node<K,V> node){
        Node<K,V> next = head.next;
        head.next = node;
        node.prev = head;
        node.next = next;
        next.prev = head;
    }

    //删除节点
    private void removeNode(Node<K,V> node){
        Node<K,V> prev = node.prev;
        Node<K,V> next = node.next;
        prev.next = next;
        next.prev = prev;
    }

    public V get(K key){
        if (map.containsKey(key)){
            Node<K,V> node = map.get(key);
            removeNode(node);
            addNode(node);
            return node.value;
        }
        return null;
    }

    public void put(K key,V value){
        if (map.containsKey(key)){
            Node<K,V> node = map.get(key);
            node.value = value;
            removeNode(node);
            addNode(node);
        } else {
            if (map.size() >= capacity){
                Node<K,V> delete = tail.prev;
                removeNode(delete);
                map.remove(delete.key);
            } else {
                Node<K,V> node = new Node<>(key,value);
                map.put(key,node);
                addNode(node);
            }
        }
    }
}

class Node<K,V> {

    public K key;

    public V value;

    public Node<K,V> prev;

    public Node<K,V> next;
    
    public Node(K key,V value){
        this.key = key;
        this.value = value;
    }

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

cw旧巷

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

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

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

打赏作者

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

抵扣说明:

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

余额充值