面试集合 java基础的集合
Collection接口的常用方法
1 2 3 4 5 增加:add(E e) addAll(Collection<? extends E> c) 删除:clear() remove(Object o) 修改: 查看:iterator() size() 判断:contains(Object o) equals(Object o) isEmpty()
总结一下:首先是接口不能创建对象,利用实现类创建对象,
集合有一个特点:只能存放引用数据类型的数据,不能是基本数据类型
基本数据类型放入到集合里面会自动装箱。
特别问题 String、StringBuilder、StringBuffer 区别和联系 1、String 类是不可变类、即一旦一个 String 对象被创建后,包含在这个对象中的字符序列是不可改变的,制止这个对象销毁。
2、StringBuffer 类则代表一个字符序列可变的字符串,可以通过 append、insert、reverse、setCharAt、setLength 等方法改变其内容。一旦生成了最终的字符串,调用 toString 方法将其转变为 String
3、JDK1.5 新增了一个 StringBuilder 类和 StringBuffer 相似,构造方法和方法基本相同。不同的是 StrtingBuffer 是线程安全的,而 StringBuilder 是线程不安全的,所以性能略高,通常情况下,创建一个内容可变的字符串,应该优先考虑使用 StringBuilder。
StringBuilder:JDK1.5 开始 效率高 线程不安全
StringBuffer:JDK1.0 开始 效率低 线程安全
并发问题 问题1:购票系统的读写一致性问题 假如你设计一个购票系统,如何设计车票,让车票的读和写是一致的,比如一共有15个车票,买了5张,还剩10张,如何保证准确性
数据库事务 + 悲观锁 通过数据库的事务和悲观锁(如 SELECT FOR UPDATE
)来确保同一时间只有一个线程可以修改票数。
实现思路 :
在购票时,使用事务锁定票数记录,确保其他线程无法同时修改。
读操作可以直接读取数据库中的剩余票数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 public class TicketService { private static final String URL = "jdbc:mysql://localhost:3306/ticket_db" ; private static final String USER = "root" ; private static final String PASSWORD = "password" ; public boolean purchaseTicket (int ticketId, int userId) { Connection conn = null ; try { conn = DriverManager.getConnection(URL, USER, PASSWORD); conn.setAutoCommit(false ); String selectSql = "SELECT remaining FROM tickets WHERE id = ? FOR UPDATE" ; PreparedStatement selectStmt = conn.prepareStatement(selectSql); selectStmt.setInt(1 , ticketId); ResultSet rs = selectStmt.executeQuery(); if (rs.next()) { int remaining = rs.getInt("remaining" ); if (remaining > 0 ) { String updateSql = "UPDATE tickets SET remaining = remaining - 1 WHERE id = ?" ; PreparedStatement updateStmt = conn.prepareStatement(updateSql); updateStmt.setInt(1 , ticketId); updateStmt.executeUpdate(); String insertSql = "INSERT INTO purchases (user_id, ticket_id) VALUES (?, ?)" ; PreparedStatement insertStmt = conn.prepareStatement(insertSql); insertStmt.setInt(1 , userId); insertStmt.setInt(2 , ticketId); insertStmt.executeUpdate(); conn.commit(); return true ; } } conn.rollback(); return false ; } catch (SQLException e) { if (conn != null ) { try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } e.printStackTrace(); return false ; } finally { if (conn != null ) { try { conn.setAutoCommit(true ); conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } } public int getRemainingTickets (int ticketId) { try (Connection conn = DriverManager.getConnection(URL, USER, PASSWORD); PreparedStatement stmt = conn.prepareStatement("SELECT remaining FROM tickets WHERE id = ?" )) { stmt.setInt(1 , ticketId); ResultSet rs = stmt.executeQuery(); if (rs.next()) { return rs.getInt("remaining" ); } } catch (SQLException e) { e.printStackTrace(); } return -1 ; } }
优点 :
简单直接,利用数据库的事务和锁机制保证一致性。
适合中小型系统。
缺点 :
乐观锁 通过版本号或时间戳来检测冲突,避免直接加锁。
实现思路 :
在票数表中增加一个 version
字段。
每次更新票数时,检查版本号是否一致,如果一致则更新,否则重试。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 public class TicketService { private static final String URL = "jdbc:mysql://localhost:3306/ticket_db" ; private static final String USER = "root" ; private static final String PASSWORD = "password" ; public boolean purchaseTicket (int ticketId, int userId) { Connection conn = null ; try { conn = DriverManager.getConnection(URL, USER, PASSWORD); conn.setAutoCommit(false ); String selectSql = "SELECT remaining, version FROM tickets WHERE id = ?" ; PreparedStatement selectStmt = conn.prepareStatement(selectSql); selectStmt.setInt(1 , ticketId); ResultSet rs = selectStmt.executeQuery(); if (rs.next()) { int remaining = rs.getInt("remaining" ); int version = rs.getInt("version" ); if (remaining > 0 ) { String updateSql = "UPDATE tickets SET remaining = remaining - 1, version = version + 1 WHERE id = ? AND version = ?" ; PreparedStatement updateStmt = conn.prepareStatement(updateSql); updateStmt.setInt(1 , ticketId); updateStmt.setInt(2 , version); int rowsUpdated = updateStmt.executeUpdate(); if (rowsUpdated > 0 ) { String insertSql = "INSERT INTO purchases (user_id, ticket_id) VALUES (?, ?)" ; PreparedStatement insertStmt = conn.prepareStatement(insertSql); insertStmt.setInt(1 , userId); insertStmt.setInt(2 , ticketId); insertStmt.executeUpdate(); conn.commit(); return true ; } } } conn.rollback(); return false ; } catch (SQLException e) { if (conn != null ) { try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } e.printStackTrace(); return false ; } finally { if (conn != null ) { try { conn.setAutoCommit(true ); conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } } }
优点 :
缺点 :
分布式锁 + 缓存 在分布式系统中,使用分布式锁(如Redis的RedLock)来保证同一时间只有一个线程可以修改票数,同时使用缓存(如Redis)来加速读操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 import redis.clients.jedis.Jedis;public class TicketService { private static final String REDIS_HOST = "localhost" ; private static final int REDIS_PORT = 6379 ; public boolean purchaseTicket (int ticketId, int userId) { Jedis jedis = new Jedis (REDIS_HOST, REDIS_PORT); String lockKey = "ticket_lock_" + ticketId; String lockValue = String.valueOf(System.currentTimeMillis()); try { String result = jedis.set(lockKey, lockValue, "NX" , "PX" , 10000 ); if ("OK" .equals(result)) { boolean success = doPurchaseTicket(ticketId, userId); if (success) { jedis.decr("ticket_remaining_" + ticketId); return true ; } } return false ; } finally { if (lockValue.equals(jedis.get(lockKey))) { jedis.del(lockKey); } jedis.close(); } } private boolean doPurchaseTicket (int ticketId, int userId) { return true ; } public int getRemainingTickets (int ticketId) { try (Jedis jedis = new Jedis (REDIS_HOST, REDIS_PORT)) { String remaining = jedis.get("ticket_remaining_" + ticketId); if (remaining != null ) { return Integer.parseInt(remaining); } int remainingTickets = fetchRemainingTicketsFromDB(ticketId); jedis.set("ticket_remaining_" + ticketId, String.valueOf(remainingTickets)); return remainingTickets; } } private int fetchRemainingTicketsFromDB (int ticketId) { return 10 ; } }
总结
悲观锁 :适合中小型系统,实现简单,但性能较差。
乐观锁 :适合高并发场景,但需要处理重试逻辑。
分布式锁 + 缓存 :适合分布式系统,性能较好,但实现复杂度较高。
根据系统的规模和需求,可以选择合适的方案来保证车票的读和写操作的一致性。
问题2:如何看是否有命中,如何知道读取数据是最新的数据 通过查询结果判断 在执行 SELECT ... FOR UPDATE
后,可以通过检查查询结果集来判断是否命中。
缓存中的数据可能会过期或失效,因此需要确保读取的是最新的数据。以下是几种常见的方法:
缓存更新策略
spring原理 问题1:讲一下spring ioc的实现原理 Spring 的 IOC 实现原理 1. 什么是 IOC(控制反转)? IOC(Inversion of Control)是一种设计原则,它将对象的创建、依赖注入和生命周期管理交给框架(如 Spring)来处理,而不是由程序员手动控制。IOC 的核心思想是 将控制权从应用程序代码转移到框架 。
传统方式 :程序员手动创建对象并管理依赖关系。
IOC 方式 :Spring 容器负责创建对象并注入依赖。
2. IOC 的核心组件 Spring 的 IOC 容器主要由以下几个核心组件实现:
BeanFactory :IOC 容器的基础接口,提供了最基本的依赖注入功能。
ApplicationContext :BeanFactory
的子接口,提供了更多高级功能(如事件发布、国际化支持等)。
BeanDefinition :用于描述一个 Bean 的元数据(如类名、作用域、依赖关系等)。
BeanPostProcessor :用于在 Bean 初始化前后执行自定义逻辑。
BeanFactoryPostProcessor :用于在 BeanFactory 初始化后修改 Bean 的定义。
3. IOC 的实现原理 Spring 的 IOC 实现原理可以分为以下几个步骤:
3.1 加载配置文件或注解 Spring 容器会加载配置文件(如 applicationContext.xml
)或扫描注解(如 @Component
、@Service
等),获取 Bean 的定义信息。
XML 配置 :
1 2 3 <bean id ="userService" class ="com.example.UserService" > <property name ="userDao" ref ="userDao" /> </bean >
注解配置 :
1 2 3 4 5 @Service public class UserService { @Autowired private UserDao userDao; }
3.2 解析 Bean 定义 Spring 容器会解析配置文件或注解,将每个 Bean 的定义信息封装为 BeanDefinition
对象,并存储在一个 BeanDefinitionRegistry
中。
3.3 实例化 Bean Spring 容器根据 BeanDefinition
中的信息,通过反射机制实例化 Bean。
单例 Bean :容器启动时就会创建并缓存单例 Bean。
原型 Bean :每次请求时都会创建一个新的 Bean。
3.4 依赖注入 Spring 容器会根据 Bean 的依赖关系,自动将依赖的 Bean 注入到目标 Bean 中。
Setter 注入 :
1 2 3 4 5 6 public class UserService { private UserDao userDao; public void setUserDao (UserDao userDao) { this .userDao = userDao; } }
构造器注入 :
1 2 3 4 5 6 public class UserService { private UserDao userDao; public UserService (UserDao userDao) { this .userDao = userDao; } }
字段注入(通过注解) :
1 2 @Autowired private UserDao userDao;
3.5 初始化 Bean 在 Bean 初始化前后,Spring 容器会调用 BeanPostProcessor
的 postProcessBeforeInitialization
和 postProcessAfterInitialization
方法,执行自定义逻辑。
初始化方法 :可以通过 @PostConstruct
注解或 init-method
配置指定初始化方法。
销毁方法 :可以通过 @PreDestroy
注解或 destroy-method
配置指定销毁方法。
3.6 使用 Bean 应用程序可以通过 ApplicationContext
或 BeanFactory
获取 Bean 并使用。
1 2 3 ApplicationContext context = new ClassPathXmlApplicationContext ("applicationContext.xml" );UserService userService = context.getBean(UserService.class);userService.doSomething();
4. IOC 的核心机制 4.1 反射机制 Spring 通过反射机制动态创建 Bean 实例并注入依赖。
4.2 工厂模式 Spring 使用工厂模式(BeanFactory
)来管理 Bean 的创建和依赖注入。
4.3 依赖查找 vs 依赖注入
依赖查找 :应用程序主动从容器中查找依赖(如 context.getBean()
)。
依赖注入 :容器自动将依赖注入到目标 Bean 中。
4.4 生命周期管理 Spring 容器负责管理 Bean 的整个生命周期,包括实例化、初始化、使用和销毁。