首页 文章 精选 留言 我的

精选列表

搜索[面试],共4913篇文章
优秀的个人博客,低调大师

面试大杂烩

Java 基础 JVM调优 为什么要进行 JVM 调优? 更低的延迟 (Lower Latency):减少 GC 停顿时间(Stop-The-World),提高系统响应速度,尤其是对实时性要求高的应用。 更高的吞吐量 (Higher Throughput):降低 GC 开销,让 CPU 更多时间处理业务逻辑,提高单位时间内的处理能力。 更高的可用性 (Higher Availability):避免因内存溢出(OOM)导致的程序崩溃,保证系统稳定运行。 更小的内存占用 (Smaller Memory Footprint):在满足需求的前提下,合理使用内存,节省硬件成本。 调优的“铁三角”:核心参数与选择 JVM 调优绝大多数工作围绕着内存和垃圾回收器展开 1.堆内存 (Heap Memory) 相关 这是最核心的调整区域。 -Xms 和 -Xmx -Xms:堆内存的初始大小。建议设置为和 -Xmx 相同,以避免运行时动态调整堆大小带来的额外性能开销。 -Xmx:堆内存的最大大小。这是最重要的参数之一。设置太小会导致频繁 GC 甚至 OOM;设置太大会延长 Full GC 的停顿时间,并可能影响本机其他进程。 示例:-Xms4g -Xmx4g (设置堆初始和最大大小为 4GB) -XX:NewRatio 和 -XX:SurvivorRatio -XX:NewRatio:设置年轻代(Young Generation)和老年代(Old Generation)的比例。例如,-XX:NewRatio=2 表示老年代是年轻代的 2 倍(即堆的 1/3 是年轻代,2/3 > 是老年代)。对于大量产生临时对象的应用(如Web应用),可以适当增大年轻代(即减小这个比值)。 -XX:SurvivorRatio:设置 Eden 区与一个 Survivor 区的比例。例如,-XX:SurvivorRatio=8 表示 Eden 区是一个 Survivor 区的 8 倍(即年轻代的 8/10 是 Eden,两个 Survivor 各占 1/> 10)。通常不需要调整,除非有非常明确的诊断数据。 -Xmn 直接设置年轻代的大小(例如 -Xmn1g)。优先级高于 -XX:NewRatio。通常更直观。 2.垃圾回收器 (Garbage Collector) 选择与参数 选择正确的 GC 比调优其参数更重要。JDK 8 以后,G1GC 已成为默认选择。 串行回收器 (Serial GC) -XX:+UseSerialGC 适用于单核小型应用,Client 模式默认。 并行回收器 (Parallel GC / Throughput GC) -XX:+UseParallelGC / -XX:+UseParallelOldGC 目标:最大化吞吐量。适合后台运算、科学计算等,对停顿不敏感的应用。 相关参数:-XX:ParallelGCThreads(设置并行 GC 线程数) 并发标记清除回收器 (CMS GC) - 已废弃 (Deprecated) -XX:+UseConcMarkSweepGC 目标:降低停顿时间。JDK 9 开始被标记为废弃,JDK 14 中移除。不推荐在新项目中使用。 G1 回收器 (Garbage-First GC) - JDK 9+ 默认 -XX:+UseG1GC 目标:在延迟和吞吐量之间取得平衡。适用于大堆(>4G)、低停顿要求的应用。 核心参数: -XX:MaxGCPauseMillis:设置期望的最大停顿时间目标(例如 -XX:MaxGCPauseMillis=200)。G1 会尽力但不保证达到这个目标。这是 G1 最重要的调优参数。 -XX:InitiatingHeapOccupancyPercent(IHOP):触发并发标记周期的堆占用阈值(默认 45%)。如果老年代增长过快,可以适当调低此值。 ZGC / Shenandoah - 下一代低延迟回收器 -XX:+UseZGC (JDK 15+ 可投入生产) / -XX:+UseShenandoahGC (非Oracle JDK) 目标:亚毫秒级(<1ms)的超低停顿,适用于超大堆(TB 级别)和极致延迟要求的场景。 目前兼容性和稳定性仍在持续提升中,是未来的方向。 3.其他重要参数 -XX:+HeapDumpOnOutOfMemoryError 和 -XX:HeapDumpPath 发生 OOM 时自动生成堆转储(Heap Dump)文件,用于事后分析,强烈建议开启。 示例:-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/heapdump.hprof -XX:MetaspaceSize 和 -XX:MaxMetaspaceSize 设置元空间(取代永久代 PermGen)的初始和最大大小。如果类加载很多,需要适当调大(默认较小,且只受本机内存限制)。 示例:-XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=256m -Xss 设置每个线程的栈大小。默认通常为 1M。如果系统线程数非常多,可以适当减小(如 -Xss256k)来节省内存,但可能增加栈溢出风险。 JDK1.8新特性 特性 主要目的 核心优点 Lambda 表达式 实现函数式编程 代码简洁,避免匿名内部类模板代码 函数式接口 为 Lambda 提供类型 定义明确的目标类型,内置常用接口 Stream API 处理集合数据 声明式编程,内部迭代,易于并行 方法引用 简化 Lambda 语法更紧凑,可读性更强 接口默认/静态方法 扩展接口而不破坏现有代码 增强接口能力,支持库的平滑演进 新日期时间 API 替代老旧的 Date/Calendar 清晰、不可变、线程安全 Optional 优雅处理 null 减少 NPE,明确表达可能缺失的值 线程同步的主要机制 synchronized 关键字 synchronized 是 Java 中最基本、最常用的同步机制,它提供了一种内置的锁机制来保证原子性。 a) 同步实例方法 锁是当前实例对象 (this)。 java 复制代码 public class SynchronizedExample { private int count = 0; // 同步实例方法 public synchronized void increment() { count++; } public synchronized int getCount() { return count; } } b) 同步静态方法 锁是当前类的 Class 对象(如 SynchronizedExample.class)。 java 复制代码 public class SynchronizedExample { private static int count = 0; // 同步静态方法 public static synchronized void increment() { count++; } } c) 同步代码块 可以更细粒度地控制锁的范围,并可以指定任意对象作为锁。 java 复制代码 public class SynchronizedExample { private int count = 0; // 专门创建一个对象来作为锁是一种好习惯,尤其是不想用this做锁时 private final Object lock = new Object(); public void increment() { // 同步代码块,使用lock对象作为锁 synchronized (lock) { count++; } // 其他不需要同步的代码可以放在外面,提高性能 } // 也可以用this作为锁,效果和同步实例方法一样 public void decrement() { synchronized (this) { count--; } } } 使用修改后的例子: java 复制代码 public class SafeExample implements Runnable { private static int count = 0; // 静态变量需要静态锁 private static final Object staticLock = new Object(); @Override public void run() { for (int i = 0; i < 100000; i++) { // 使用同步代码块保护count++ synchronized (staticLock) { count++; } } } public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(new SafeExample()); Thread t2 = new Thread(new SafeExample()); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println("count的最终值是: " + count); // 现在结果总是200000 } } Lock 接口 (java.util.concurrent.locks.Lock) java.util.concurrent.locks 包提供了更灵活的锁操作。最常用的是 ReentrantLock(可重入锁)。 优点: 尝试非阻塞获取锁:tryLock() 方法可以尝试获取锁,如果获取失败不会一直阻塞。 可中断的获取锁:lockInterruptibly() 方法可以在等待锁时响应中断。 超时获取锁:tryLock(long time, TimeUnit unit) 可以设置超时时间。 可以创建多个条件变量(Condition):比 wait()/notify() 更精细的线程通信。 java 复制代码 import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class LockExample { private static int count = 0; // 创建ReentrantLock实例 private static final Lock lock = new ReentrantLock(); public static void main(String[] args) throws InterruptedException { Runnable task = () -> { for (int i = 0; i < 100000; i++) { lock.lock(); // 获取锁 try { count++; } finally { lock.unlock(); // 释放锁必须放在finally块中,确保一定会执行 } } }; Thread t1 = new Thread(task); Thread t2 = new Thread(task); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println("count的最终值是: " + count); } } synchronized 与 Lock 的对比 特性 synchronized Lock (ReentrantLock) 实现层次 JVM 层面的关键字,内置 JDK 层面的接口,通过代码实现 锁的释放 自动释放(代码块/方法执行完毕或发生异常) 必须手动调用 unlock()(通常在finally中) 灵活性 较差,只能以块结构的方式获取和释放锁 非常灵活,可以非阻塞、可中断、超时获取锁 性能 在早期版本较差,JDK 6 后进行了大量优化,现在性能很好 在高竞争环境下可能表现更好 读写分离 不支持 支持(ReadWriteLock) 条件变量 单一,通过 wait(), notify(), notifyAll() 多个,通过 Condition await(), signal(), signalAll() 公平性 非公平锁 可选(构造函数可指定创建公平或非公平锁) Java集合 ArrayList 特性 描述 优点 缺点 随机访问 通过索引获取元素极快 (get(int index)) 查询性能高 (O(1)) - 尾部添加 在列表末尾添加元素平均很快 平均摊销时间复杂度为 O(1) 偶尔触发扩容,会导致性能抖动 中间操作 在指定位置插入或删除元素 - 性能差 (O(n)),需要移动元素 内存占用 底层是数组 相比 LinkedList,每个元素占用的内存更少(无需存储节点指针) 使用建议: 首选场景:当你的主要操作是随机访问或遍历,而很少在列表中间进行插入和删除操作时。 初始化:如果能够预估数据量的大小,最好在构造时指定初始容量(new ArrayList<>(1000)),这样可以避免多次扩容,提升性能。 避免在循环中中间操作:尽量避免在 ArrayList 的前端或中间进行大量的添加或删除操作,否则性能会急剧下降。这种情况下,LinkedList 可能更合适。 HashMap 底层实现 JDK 1.7 及之前:数组 + 链表 JDK 1.8 及之后:数组 + 链表 + 红黑树 详细步骤 计算哈希值 (hash(Object key)) 并非直接使用 key.hashCode()。JDK 8 进行了优化:将哈希码的高16位与低16位进行异或操作:(h = key.hashCode()) ^ (h >>> 16)。 目的:掺入高位特征,减少哈希冲突。因为计算下标时 (n-1) & hash 只用到低位的比特,如果高位变化很大而低位不变,容易冲突。 计算数组下标 通过 i = (table.length - 1) & hash 计算键值对应该存放在数组的哪个位置(哪个“桶”里)。 为什么用 & 而不是 %? 因为数组长度 n 总是 2 的幂,(n-1) & hash 等价于 hash % n,但位运算 & 的效率远高于取模 %。 处理哈希冲突 如果计算出的桶位置是空的,直接放入新节点。 如果不为空(发生哈希冲突),则遍历该桶中的链表或树: 如果 key 已存在(hash 值相等且 (key == e.key || key.equals(e.key))):用新 value 覆盖旧 value。 如果 key 不存在:将新节点插入到链表末尾(JDK 1.7是头插法,JDK 1.8改为尾插法,避免了多线程下扩容时可能引起的死循环)。 判断是否树化 插入新节点后,如果链表的长度大于等于 8 (TREEIFY_THRESHOLD),则会触发树化检查。 如果此时整个哈希桶数组的容量大于等于 64 (MIN_TREEIFY_CAPACITY),则将该链表转换为红黑树。 如果容量小于 64,则优先进行扩容(resize()),因为扩容本身也能缩短链表长度。 CurrentHashMap 底层实现:数组 + 链表 + 红黑树 如何保证线程安全 CAS (Compare-And-Swap):无锁算法,用于实现乐观锁。在无竞争的情况下非常高效。常用于初始化数组、插入新节点(当桶为空时)等场景。 synchronized:用于锁定当前要操作的桶的第一个节点(头节点)。锁粒度非常小,只锁住单个桶。只要多个线程不操作同一个桶,它们就可以并发执行。 volatile:用于修饰 Node 的 value 和 next 指针,以及核心的 table 数组引用。保证了内存的可见性,确保一个线程的修改能立即被其他线程看到。 具体步骤 计算哈希:使用 spread 方法计算 key 的 hash,保证为正数。 循环尝试:整个 put 操作在一个无限循环 for (Node<K,V>\[] tab = table;;) 中进行,直到成功插入才 break。 初始化 table (Lazy-load):如果 table 为空,先调用 initTable() 初始化。这里使用 CAS 操作来保证只有一个线程能执行初始化。 定位桶:通过 (n - 1) & hash 找到 key 对应的桶,获取头节点 f。 CAS 插入(无锁):如果桶 f 为 null,说明是第一次插入。使用 CAS 操作 tabAt(tab, i) 将新节点放入桶中。如果 CAS 成功则插入完成;如果失败(被其他线程抢先),则循环重试。 同步块插入(加锁):如果桶 f 不为 null,则用 synchronized 锁住这个头节点 f。 这里检查是否正在扩容(f.hash == MOVED),如果是,则当前线程会先协助扩容 (helpTransfer),扩容完再重试。 遍历链表或树: 链表:遍历,如果 key 存在则覆盖 value;否则插入到链表尾部。 红黑树:调用红黑树的插入方法。 判断是否需要树化:插入链表后,如果链表长度 >= 8,则调用 treeifyBin 尝试树化。树化前会检查当前数组容量是否 >= 64,如果不够,会优先选择扩容。 框架 Spring 常用注解 核心注解 (IoC & DI) 注解 说明 @Component 通用注解,用于标记任何一个类为 Spring 组件(Bean)。Spring 会自动扫描并创建其实例。 @Repository 标注在 DAO 层(数据访问层) 的类上。是 @Component 的特化,同时会将平台特定的持久化异常转换为 Spring 的统一异常。 @Service 标注在 业务服务层(Service层) 的类上。是 @Component 的特化,表示一个业务逻辑组件。 @Controller 标注在 Web 控制层(MVC) 的类上。负责处理请求,通常与 @RequestMapping 结合使用。 @RestController @Controller 和 @ResponseBody 的组合注解。用于 RESTful Web 服务,返回的数据直接写入 HTTP 响应体(如 JSON/XML),而不是视图页面。 @Configuration 标记一个类为配置类,相当于一个 XML 配置文件,内部会包含多个 @Bean 方法的定义。 @Bean 在 @Configuration 或 @Component 类的方法上使用。定义了一个由 Spring IoC 容器管理的 Bean,方法名默认为 Bean 的名称。 @Autowired 自动依赖注入。可以用于字段、Setter 方法、构造方法上。Spring 会按类型(byType)自动装配合适的 Bean。 @Qualifier 与 @Autowired 配合使用,当有多个相同类型的 Bean 时,通过名称(byName)来指定要注入的具体 Bean。 @Value 注入属性值。可以注入外部属性(如来自 .properties 文件)或表达式(SpEL)的结果。例如:@Value("${database.url}")。 @Scope 指定 Bean 的作用域,如 singleton(默认,单例)、prototype(原型,每次注入都新建)、request、session、application 等。 @Lazy 延迟初始化,表示这个 Bean 在第一次被请求时才会被创建,而不是在容器启动时。 @Primary 当有多个相同类型的 Bean 时,被标注 @Primary 的 Bean 将作为自动装配时的首选候选者。 Web MVC 注解 注解 说明 @RequestMapping 通用请求映射。可以标注在类或方法上,将 HTTP 请求映射到 MVC 控制器的方法。可通过 method、path 等属性细化。 @GetMapping @RequestMapping(method = RequestMethod.GET) 的快捷方式。 @PostMapping @RequestMapping(method = RequestMethod.POST) 的快捷方式。 @PutMapping @RequestMapping(method = RequestMethod.PUT) 的快捷方式。 @DeleteMapping @RequestMapping(method = RequestMethod.DELETE) 的快捷方式。 @PatchMapping @RequestMapping(method = RequestMethod.PATCH) 的快捷方式。 @RequestParam 用于获取 URL 查询参数或表单字段的值并绑定到方法参数。例如:@RequestParam("id") Long userId。 @PathVariable 用于获取 RESTful 风格 URL 中的模板变量。例如:/user/{id} 对应 @PathVariable("id") Long id。 @RequestBody 将 HTTP 请求体(如 JSON 数据) 反序列化并绑定到方法参数对象上。 @ResponseBody 将方法返回值直接写入 HTTP 响应体,而不是渲染一个视图。通常用于返回 JSON/XML 数据。 @ModelAttribute 1. 用于方法参数:从模型中获取属性。2. 用于方法:在 @RequestMapping 方法执行前,将返回值添加到模型。 @CookieValue 将 HTTP 请求中的 Cookie 值绑定到方法参数。 @RequestHeader 将 HTTP 请求头信息绑定到方法参数。 @ResponseStatus 标注在方法或异常类上,指定 HTTP 响应的状态码。例如:@ResponseStatus(HttpStatus.NOT_FOUND)。 @ControllerAdvice 全局异常处理。定义一个类,其中的 @ExceptionHandler、@InitBinder、@ModelAttribute 方法会应用到所有控制器。 @ExceptionHandler 标注在方法上,用于处理特定类型的异常,通常在一个 @Controller 或 @ControllerAdvice 类中。 @CrossOrigin 启用跨域请求。可以标注在控制器类或方法上,为请求处理方式启用跨源资源共享(CORS)。 数据访问/事务注解 注解 说明 @Transactional 声明式事务管理。标注在类或方法上,定义方法的事务属性(如传播行为、隔离级别、回滚规则等)。 切面编程 (AOP) 注解 注解 说明 @Aspect 声明一个类是切面,包含通知(Advice)和切点(Pointcut)。 @Before 前置通知:在目标方法执行之前执行。 @After 后置通知:在目标方法执行之后(无论是否发生异常)执行。 @AfterReturning 返回通知:在目标方法成功执行并返回后执行。 @AfterThrowing 异常通知:在目标方法抛出异常后执行。 @Around 环绕通知:最强大的通知类型,可以在目标方法执行前后自定义行为,甚至可以决定是否执行目标方法。 @Pointcut 定义切点表达式,声明一个可重用的切点。 Spring Boot 注解 说明 @SpringBootApplication 核心注解,是 @SpringBootConfiguration, @EnableAutoConfiguration, @ComponentScan 三个注解的组合。通常放在主启动类上。 @EnableAutoConfiguration 启用自动配置。Spring Boot 会根据类路径中的 Jar 包,自动配置应用程序。 @SpringBootConfiguration 标记类为配置类,是 @Configuration 的另一种形式。 @ConfigurationProperties 将外部配置文件(如 application.properties)中的属性批量绑定到一个 Java Bean 上。 @ConditionalOnClass 条件注解,当类路径下存在指定的类时,配置才生效。 @ConditionalOnProperty 条件注解,当指定的配置属性具有特定值时,配置才生效。 事务的传播机制 7种传播机制 传播行为类型 说明 外部不存在事务 外部存在事务 适用场景 REQUIRED (默认) 支持当前事务。如果不存在,则新建一个。 新建一个事务 加入当前事务 最常用的场景。例如,新增用户和新增日志应该在同一事务中。 REQUIRES_NEW 新建一个事务。如果当前存在事务,则挂起当前事务。 新建一个事务 挂起外部事务,新建内部事务。两个事务互不干扰。 内外事务完全独立,内层事务成功与否不应影响外层。例如,日志记录(即使失败也不应回滚主业务)。 SUPPORTS 支持当前事务。如果不存在,则以非事务方式执行。 以非事务方式运行 加入当前事务 方法可以“随大流”,有事务就用,没有也行。查询操作有时会使用。 NOT_SUPPORTED 以非事务方式执行。如果当前存在事务,则挂起当前事务。 以非事务方式运行 挂起外部事务,以非事务方式运行内部方法。 强制非事务执行。例如,执行一些不需要事务的统计计算。 MANDATORY 强制要求存在当前事务。如果不存在,则抛出异常。 抛出异常 IllegalTransactionStateException 加入当前事务 方法必须在一个已存在的事务中被调用,否则就是编程错误。 NEVER 以非事务方式执行。如果当前存在事务,则抛出异常。 以非事务方式运行 抛出异常 IllegalTransactionStateException 方法绝对不能在任何事务中运行,用于检查事务泄露。 NESTED 嵌套事务。如果当前存在事务,则在当前事务的嵌套事务中执行。如果不存在,则行为同 REQUIRED。 新建一个事务 在外部事务中创建一个保存点(Savepoint)。内部事务的回滚只影响保存点之后的操作,不影响外部事务之前的操作。但外部事务的回滚会回滚整个嵌套事务。 复杂的业务场景,允许部分操作回滚而不影响全局。例如,电商下单时,扣库存和扣款是主事务,为每个商品创建订单项可以作为嵌套事务,某个商品失败只回滚该商品的操作。 重点机制示例 REQUIRED (默认) 这是最常用的模式。 场景:methodB 有事务,调用 methodA(REQUIRED)。 行为:methodA 会加入到 methodB 的事务中。它们属于同一个事务。任何一个方法发生异常,整个事务都会回滚。 java 复制代码 @Transactional(propagation = Propagation.REQUIRED) // 默认值,可省略 public void methodB() { // 一些数据库操作... methodA(); // 调用内层方法 // 更多数据库操作... } @Transactional(propagation = Propagation.REQUIRED) public void methodA() { // 数据库操作... } REQUIRES_NEW 场景:methodB 有事务,调用 methodA(REQUIRES_NEW)。 行为:Spring 会挂起 methodB 的事务,为 methodA 创建一个全新、独立的事务。两个事务拥有独立的连接和隔离级别。 如果 methodA 完成提交,然后 methodB 之后发生异常并回滚,methodA 的操作不会回滚。 如果 methodA 发生异常并回滚,默认情况下会抛出异常并导致 methodB 的事务也回滚(除非 methodB 捕获并处理了这个异常)。 java 复制代码 @Transactional public void methodB() { // 操作1 (属于事务B) try { methodA(); // 调用内层方法 } catch (Exception e) { // 捕获异常,防止事务B因A的异常而回滚 } // 操作2 (属于事务B) } @Transactional(propagation = Propagation.REQUIRES_NEW) public void methodA() { // 操作A (属于独立的新事务A) // 如果这里异常,事务A回滚,并且异常会抛给methodB } NESTED (基于保存点) 场景:methodB 有事务,调用 methodA(NESTED)。 行为:Spring 会在外部事务(methodB 的事务)中创建一个保存点(Savepoint)。 如果 methodA 成功执行,则其操作会随着外部事务一起提交。 如果 methodA 执行失败并回滚,则只会回滚到保存点的状态,不会影响保存点之前 methodB 所做的操作。 如果 methodB 在 methodA 之后发生异常,则整个事务(包括 methodA 的操作)都会回滚。 NESTED 和 REQUIRES_NEW 的区别: REQUIRES_NEW:完全独立的新事务,互不干扰。 NESTED:是外部事务的子集,其提交依赖于外部事务的最终提交。 注意:NESTED 需要底层数据库支持保存点功能(如 MySQL, PostgreSQL),否则会降级为 REQUIRED。 常见问题 Q1: Spring事务失效的常见情况 数据库引擎不支持事务(最根本的前提) 场景:如果你使用的是 MySQL,并且表使用的是 MyISAM 引擎,那么事务会完全失效,因为 MyISAM 本身就不支持事务。 解决方案:将表引擎改为 InnoDB。 方法非 public 修饰 场景:@Transactional 注解标注在了一个 protected、private 或包权限的方法上。 原因:Spring AOP 代理默认是基于 CGLIB 的,而 CGLIB 无法代理非 public 方法。基于接口的 JDK 动态代理也同样如此。 解决方案:确保被 @Transactional 注解的方法都是 public 的。 自调用(同类调用)★ 最常见 & 最隐蔽 ★ 场景:在同一个类中,一个非事务方法 A() 调用了本类的事务方法 B()。 java 复制代码 @Service public class UserService { public void A() { // ... 一些操作 this.B(); // 自调用,事务失效! // ... } @Transactional public void B() { // 数据库操作 } } 原因:A() 方法中的 this.B() 中的 this 是目标对象本身,而不是 Spring 注入的代理对象。因此调用不会经过代理,事务逻辑自然不会生效。 解决方案: (推荐)将方法 B() 抽取到另一个 Service 中,然后通过 @Autowired 注入这个新的 Service 再调用。 (不推荐)通过 AopContext 获取当前代理对象(需要开启 expose-proxy)。 复制代码 @Service public class UserService { public void A() { // 获取当前代理对象并调用其方法 ((UserService) AopContext.currentProxy()).B(); } @Transactional public void B() { ... } } 配置中需要开启:@EnableAspectJAutoProxy(exposeProxy = true) 异常类型不对或被捕获 场景1:抛出的是非 RuntimeException 或 Error。 原因:@Transactional 默认只回滚 RuntimeException 和 Error。如果抛出的是 IOException、SQLException 等受检异常(Checked Exception),事务不会回滚。 解决方案:使用 @Transactional(rollbackFor = Exception.class) 指定需要回滚的异常类型。 场景2:异常被方法内部 catch 吞掉了。 原因:代理对象只有在接收到异常时才会触发回滚逻辑。如果你在方法内 try-catch 了异常却没有重新抛出,代理对象就感知不到异常,自然会提交事务。 解决方案:如果希望事务回滚,必须在 catch 块中重新抛出一个运行时异常,或者通过 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly() 手动回滚。 复制代码 @Transactional public void method() { try { // 数据库操作 } catch (Exception e) { // 只打印日志,没有抛出 e.printStackTrace(); // 事务失效! // throw new RuntimeException(e); // 必须重新抛出 } } 未被 Spring 容器管理 场景:你给一个类加了 @Transactional,但这个类没有被 Spring 扫描到(即不是 @Component, @Service, @Repository 等),或者你直接 new 了一个对象来调用方法。 原因:只有 Spring 容器管理的 Bean 才会被代理。 解决方案:确保类已被 Spring 扫描并管理。 propagation 传播属性设置不当 场景:内层方法设置了 Propagation.NOT_SUPPORTED, Propagation.NEVER 等传播行为。 原因:这是由传播行为的定义决定的,并非“失效”,而是符合预期的行为。 解决方案:根据业务需求正确配置传播行为。 复制代码 @Transactional public void A() { // 有事务 B(); // B方法不支持事务,会挂起A的事务,以非事务运行 } @Transactional(propagation = Propagation.NOT_SUPPORTED) public void B() { // 非事务执行,操作立即提交,无法回滚 } 多线程调用 场景:在方法内开启新线程进行数据库操作。 原因:事务信息(如数据库连接)是存储在 ThreadLocal 中的,不同线程拥有不同的 ThreadLocal,因此新线程无法共享原线程的事务上下文。 解决方案:避免在多线程中处理同一事务,或将多线程操作合并到主线程中。 错误配置(如切面顺序) 场景:如果同时使用了自定义的 AOP 切面,并且切面的优先级比事务切面高,且在切面中吞掉了异常。 原因:自定义切面先执行,如果它捕获了异常,事务切面就接收不到异常信号。 解决方案:调整切面顺序,使用 @Order 注解确保事务切面有更高的优先级(更小的 order 值)。 总结 数据库:用的是 InnoDB 引擎吗? 注解:方法是否是 public 的? 调用:是自调用吗?(检查调用链,是否通过代理对象调用) 异常:抛出的异常类型对吗?异常被捕获了吗? 管理:这个类被 Spring 管理了吗?(@Service 等注解加了没?扫描路径对了吗?) 配置:@EnableTransactionManagement 开启了吗?(Spring Boot 项目默认已开启) 传播行为:检查 propagation 设置是否符合预期。 SpringBoot 自动装配 启动: Spring Boot 应用启动,执行 main 方法中的 SpringApplication.run(...)。 触发入口注解: 扫描到被 @SpringBootApplication 注解的入口类。 启用自动装配: @SpringBootApplication 中的 @EnableAutoConfiguration 注解生效。 加载候选配置: @EnableAutoConfiguration 通过 @Import 导入了 AutoConfigurationImportSelector。该类从 META-INF/spring.factories 文件中读取所有自动配置类的全类名。 过滤与条件判断: 遍历这些候选配置类,根据其上的 @ConditionalOnXxx 条件注解进行筛选,最终确定哪些配置类需要被加载。 执行配置类: 生效的自动配置类 (XXXAutoConfiguration) 被加载,它们内部的 @Bean 方法开始执行,向 IoC 容器中添加组件。 自定义配置优先: 这些配置类中的 @Bean 方法通常带有 @ConditionalOnMissingBean 条件,如果用户已经在自己的配置中定义了相同类型的 Bean,则自动配置将不会生效,确保了用户自定义的配置优先于自动配置。 常见问题 Q1: 自动装配 vs 传统配置 (spring.factories vs @Import) 你可能会有疑问,为什么不用传统的 @Import 直接导入所有配置类,而要用 spring.factories 这种看似繁琐的方式? 答案:解耦和可扩展性。 @Import 是写死在代码里的,需要编译时就知道要导入哪些类。 spring.factories 是一种SPI (Service Provider Interface) 机制。它允许第三方 Jar 包(例如 mybatis-spring-boot-starter)通过在自己的 Jar 包中创建一个 META-INF/spring.factories 文件,并将自己的自动配置类写在里面,从而被 Spring Boot 发现和加载。这使得自动装配对整个生态体系都是可扩展的,而不需要修改 Spring Boot 本身的源代码。 SpringCloud 常用组件 核心与必备组件 组件名 功能描述 对比 Spring Cloud Netflix 官方文档 Nacos 服务注册发现 和 配置管理 的二合一平台。是整个体系的基石。 Eureka + Config https://nacos.io Spring Cloud Alibaba Nacos Discovery 服务发现组件,用于将微服务注册到 Nacos Server,并能从 Nacos 发现其他服务。 Spring Cloud Netflix Eureka https://github.com/alibaba/spring-cloud-alibaba/wiki/Nacos-discovery Spring Cloud Alibaba Nacos Config 配置管理组件,用于从 Nacos Server 拉取应用的外部配置,并支持配置动态刷新。 Spring Cloud Config https://github.com/alibaba/spring-cloud-alibaba/wiki/Nacos-config 服务治理与容错组件 组件名 功能描述 对比 Spring Cloud Netflix 官方文档 Spring Cloud Alibaba Sentinel 流量控制、熔断降级、系统负载保护 的轻量级组件。提供可视化控制台。 Hystrix https://github.com/alibaba/spring-cloud-alibaba/wiki/Sentinel Spring Cloud LoadBalancer 客户端负载均衡器。通常与 Nacos Discovery 结合使用,实现从服务列表中选择实例进行调用。 Ribbon (Spring Cloud 官方组件) Spring Cloud OpenFeign 声明式的 REST 客户端。只需定义一个接口并添加注解,就能实现对其他服务的 HTTP 调用。集成了负载均衡和熔断器。 Feign (Spring Cloud 官方组件) 分布式事务组件 组件名 功能描述 对比 Spring Cloud Netflix 官方文档 Seata 分布式事务解决方案。提供了 AT、TCC、Saga 和 XA 多种事务模式,其中 AT 模式无侵入,使用简单。 Apache ShardingSphere, LCN https://seata.io Spring Cloud Alibaba Seata 对 Seata 的集成,让你在 Spring Cloud 项目中方便地使用 Seata。 https://github.com/alibaba/spring-cloud-alibaba/wiki/Seata 其他常用组件 组件名 功能描述 Spring Cloud Gateway API 网关,是所有流量的入口,负责路由转发、权限校验、限流、熔断、日志监控等。它是 Spring Cloud 官方二代网关,替代了 Netflix Zuul。 Spring Cloud Stream 用于构建消息驱动微服务的框架。可以很方便地与 RocketMQ(阿里系)、Kafka 等消息中间件集成。 RocketMQ 阿里开源的分布式消息中间件,常用于异步解耦、流量削峰、数据同步等场景。Spring Cloud Alibaba 对其有良好集成。 中间件 Redis 数据类型 主数据类型 String: 字符串 List: 列表 Hash: 哈希表 Set: 集合 Sorted Set: 有序集合 扩展类型 Bitmaps: 位图(基于 String) HyperLogLog: 基数统计(基于 String) Geospatial: 地理空间(基于 Sorted Set) 新类型 Stream: 流(5.0版本引入) 总结 数据类型 特性 适用场景 String 二进制安全,可计数 缓存,计数器,分布式锁 List 有序,可重复,双向操作 消息队列,最新列表 Hash 键值对集合,适合存储对象 存储对象信息,购物车 Set 无序,唯一,集合运算 共同好友,抽奖,去重 Sorted Set 唯一,有序(按分数排序) 排行榜,延迟任务,带权重队列 Bitmaps 位操作,极省空间 签到统计,用户活跃度 HyperLogLog 基数估算,固定大小 大规模UV统计 Geospatial 地理位置计算 附近的人,地点 Stream 持久化消息流,消费者组 可靠的消息队列,事件溯源 常见问题 Q1: Sorted Set实现原理 核心结构:哈希表 + 跳跃表 一个 Sorted Set 在 Redis 内部的完整表示包含两个部分: 一个字典 (Hash Table) 一个跳跃表 (Skip List) 字典 (Dict) 作用:实现 O(1) 复杂度的根据成员 (member) 查找分数 (score) 的操作。 结构: Key:存储的是集合的成员 (member)。 Value:存储的是该成员对应的分数 (score)。 要它? 如果只用跳跃表,根据成员查分数需要 O(log N) 的时间(因为要在跳跃表中遍历查找成员)。字典的引入将这个操作优化到了 O(1),对于像 ZSCORE 这样的命令至关重要。 跳跃表 (Skip List) 作用:维护一个按分数 (score) 排序的成员列表,支持高效的范围查询(如 ZRANGE, ZRANK)和插入删除操作。 为什么需要它? 字典本身是无序的,无法直接支持基于分数的排序和范围查询。而跳跃表在这种场景下性能非常好,平均时间复杂度为 O(log N)。 Rabbitmq 常见问题 Q1: 脑裂 1.什么是脑裂? 脑裂 是指在高可用集群中,由于网络分区或节点暂时性故障,导致集群中的成员无法正常通信。此时,原本的一个集群分裂成了两个或多个独立的部分,这些部分都认为自己是唯一存活的集群,并且可能同时对外提供服务(如写入操作),从而导致数据不一致、冲突和混乱的现象。 在 RabbitMQ 的语境下,脑裂特指镜像队列 同时存在于两个无法通信的节点分区中,并且两个分区都可能有生产者继续向队列写入消息,导致同一队列的数据出现分叉。 2.RabbitMQ 如何处理和防止脑裂? RabbitMQ 提供了三种主要的策略来处理网络分区(从而避免脑裂),核心在于优先保证数据一致性,而非可用性。 策略一:pause-minority 模式(默认推荐) 原理: 节点会根据自己对集群状态的认知,判断自己是否属于“少数派”(分区中的节点数少于或等于总节点数的一半)。如果判断自己是少数派,它会主动暂停(pause)自己。 一个被暂停的节点会: 断开所有客户端的连接。 停止处理消息(既不能生产也不能消费)。 实际上停止服务,不再认为自己是一个主节点。 效果: 在上面的例子中: 分区 A (node2, node3): 有 2 个节点,是多数派(超过 3/2=1.5),所以继续运行。 分区 B (node1): 只有 1 个节点,是少数派,它会主动暂停自己。 结果: 最终只有多数派分区(分区 A)能提供服务,完全避免了脑裂。网络恢复后,暂停的节点(node1)会重新加入集群,并从新的主节点同步所有错过的数据。 优点: 强一致性,保证数据不会出错0。 缺点: 牺牲了部分可用性(少数派分区完全不可用)。对于偶数节点的集群,可能会出现两个分区节点数相同(都是少数派)而导致整个集群都暂停的情况,因此建议集群使用奇数个节点(3, 5, 7...)。 策略二:pause-if-all-down 模式 原理: 只有当节点无法连接到所有指定的节点时,它才会暂停。你需要配置一个节点列表(nodes),该模式只关心是否能连接到这些特定节点,而不是计算集群多数派。 适用场景: 适用于跨机房部署等场景,你希望只有当整个机房都失联时才暂停,而不是根据简单的数量多数来判断。 风险: 如果配置不当,比 pause-minority 更容易引发脑裂。 策略三:autoheal 模式 原理: 这种模式不那么保守。当网络分区恢复后,RabbitMQ 会选择一个连接客户端最多的分区继续运行,并重启其他分区中的节点,让它们以从节点身份重新加入。 过程: 在分区期间,多个分区可能都在运行(存在脑裂风险)。但一旦网络恢复,系统会通过“牺牲”一个分区的方式来修复(heal)数据不一致。 优点: 在分区期间提供了更好的可用性。 缺点: 分区期间可能发生脑裂,导致数据不一致。恢复时,被放弃的分区中的所有数据将会丢失。这是一个优先保证可用性(AP) 的策略。 3.如何配置和选择策略 策略是通过 cluster_partition_handling 配置项设置的。 配置文件示例 (rabbitmq.conf): shell 复制代码 # 使用 pause-minority 模式(推荐) cluster_partition_handling = pause_minority # 使用 pause-if-all-down 模式 cluster_partition_handling = pause_if_all_down cluster_partition_handling.pause_if_all_down.recover = heal cluster_partition_handling.pause_if_all_down.nodes.1 = rabbit@node1 cluster_partition_handling.pause_if_all_down.nodes.2 = rabbit@node2 # 使用 autoheal 模式 cluster_partition_handling = autoheal 4.如何监控和恢复 监控: 使用 RabbitMQ 管理界面或 rabbitmqctl cluster_status 命令监控集群状态。网络分区是严重事件,应该触发警报。 检测分区: 管理界面的 Overview 页面上方会有明显的红色警告提示 “Partition detected”。 恢复: 当网络问题修复后,被暂停的节点会自动尝试重新加入集群。通常不需要手动干预。你可以使用 rabbitmqctl forget_cluster_node 命令手动移除故障节点(如果需要)。 Q2: 消息的幂等性 为什么会产生重复消息 1.生产者重复发送: 生产者发送消息后,Broker 可能因网络抖动等原因未及时返回 ack 确认。 生产者会触发重试机制,从而可能发出两条一模一样的消息。 2.Broker 重复投递: 消费者处理完消息后,在给 Broker 返回 ack 之前突然断开连接(如进程崩溃、网络中断)。 Broker 因未收到 ack,会将消息重新置为 Ready 状态,并投递给其他消费者(或等待当前消费者重连后再次投递)。 3.消费者重复处理: 消费者处理成功,但在输出结果(如修改数据库、调用下游接口)后,自身崩溃,未能发送 ack。 消费者重启后,会再次收到同一条消息。 如何保证消息幂等性 方案一:唯一业务ID(最推荐、最通用) 这是最常用且有效的方案。 消息体设计:在生产者端,为每一条业务消息生成一个全局唯一的ID(例如 UUID、雪花算法ID、业务字段组合如“订单ID+操作类型”)。这个ID需要代表唯一的一笔业务。 json 复制代码 { "msg_id": "202409020001", // 唯一消息ID "order_id": "123456", // 业务ID "amount": 100, "action": "add_points" } 消费者处理流程: 第一步:SELECT:在执行业务逻辑之前,先拿着这个唯一消息ID msg_id 去数据库中查询(例如一张 message_processed 表)。 第二步:判断: 如果查到了记录:说明这条消息已经被成功处理过了,直接返回成功(ack消息),不做任何业务操作。 如果没查到记录:说明这是条新消息,继续执行业务操作。 第三步:INSERT + 业务操作:将业务操作和插入 message_processed 记录放在同一个数据库事务中。这是关键! 事务成功:业务做了,记录也插了,消息被消费。 事务失败:业务回滚,记录也没插入,消息会重试。 message_processed 表设计: 字段名 类型 说明 id bigint 自增主键 msg_id varchar(128) 唯一消息ID,需要加唯一索引 create_time datetime 创建时间 优点: 通用性强,适用于所有业务场景。 可靠性高,依靠数据库的事务和唯一索引保证。 缺点: 增加了数据库的写入压力(每次消费都要写一条记录)。 需要创建一张额外的表。 方案二:利用数据库唯一键约束(方案一的特化版) 如果业务逻辑本身就是在向数据库插入一条记录,而这条记录的某个字段可以天然作为唯一标识(例如订单ID),那么可以利用数据库的唯一索引来避免重复插入。 例子:订单创建消息 消息内容:{“order_id”: "123456", "user_id": "1001", ...} 消费者逻辑:INSERT INTO orders (order_id, user_id, ...) VALUES ("123456", "1001", ...); 如果消息重复,第二次插入时,因为 order_id 字段有唯一索引,插入会失败,从而避免了创建两个相同的订单。 优点: 无需额外表,利用现有业务表即可。 实现简单。 缺点: 适用范围窄,只适用于“新增”操作,不适用于更新、删除或复杂逻辑。 插入失败可能意味着其他错误,需要做好异常区分。 方案三:乐观锁(适用于更新操作) 如果业务是更新操作(如扣减库存、更新状态),可以使用乐观锁。 例子:更新订单状态 消息内容:{“order_id”: "123456", "new_status": "PAID", "version": 1} 消费者逻辑:执行SQL: sql 复制代码 UPDATE orders SET status = 'PAID', version = version + 1 WHERE order_id = '123456' AND version = 1; 检查 UPDATE 操作影响的行数 (affected rows): 如果为 1:成功,是第一次处理。 如果为 0:失败,要么版本号不对(消息是旧的),要么已经处理过了(当前版本号已经不是1了)。 优点: 不需要额外表。 是处理并发更新的标准模式。 缺点: 需要改造业务表,增加版本号字段。 消息体中需要携带版本号信息。 方案四:分布式锁(复杂场景) 在非常复杂的业务场景下,可以先尝试获取一个分布式锁(基于 Redis 或 Zookeeper),锁的Key就是消息的唯一ID。获取到锁的线程才能处理业务,处理完后释放锁。后续的重复消息由于获取不到锁,会处理失败或直接放弃。 优点: 概念清晰。 缺点: 性能开销大,引入新的中间件,增加了系统复杂性。 通常不推荐作为首选,除非业务逻辑本身就需要强互斥。 Sentinel 数据库 MySQL MySQL存储引擎 InnoDB(默认存储引擎) 从 MySQL 5.5 版本开始,InnoDB 成为默认的存储引擎。 核心特性: 支持事务(ACID):这是它最重要的特性。支持 COMMIT、ROLLBACK 和崩溃恢复能力,确保数据完整性。 行级锁:只在需要时锁定特定的行,而不是整个表。这极大地提高了在高并发读写负载下的性能和可扩展性。 外键约束:支持 FOREIGN KEY,保证数据的一致性和参照完整性。 MVCC(多版本并发控制):通过保存数据的快照来提高并发性能,允许非阻塞读操作。 适用场景: 绝大多数场景:除非有特殊需求,否则 InnoDB 是最安全、最通用的选择。 需要事务的应用(如银行交易、订单系统)。 高并发读写、需要行级锁的应用。 需要外键来保证数据完整性的应用。 不适用场景: 对全文索引有极高要求(虽然 MySQL 5.6+ 的 InnoDB 也支持全文索引,但可能不如专业的搜索引擎)。 只读或读多写极少,且对查询速度有极致要求,可以牺牲一些功能来换取极致的空间和性能(这时可考虑 MyISAM 或列式存储引擎)。 MyISAM(MySQL 5.5 之前的默认引擎) 注意:MySQL 8.0 已不再支持 MyISAM 的数据字典元数据校验,意味着它正逐渐被淘汰。 核心特性: 表级锁:对表进行写操作时(如 UPDATE、INSERT),会锁定整个表。这导致它在高并发写操作下性能很差。 不支持事务:发生故障后无法安全恢复,可能丢失数据或需要修复表。 全文索引:在早期版本中,MyISAM 的全文索引比 InnoDB 的更成熟(但现在差距已缩小)。 高速读:如果表主要是用于读(如 SELECT COUNT()),且并发写很少,它的速度可能非常快。 支持 AUTO_INCREMENT on secondary columns. 适用场景: 旧的、不再更新的应用程序。 只读或读远多于写的表,且对并发性要求不高。 数据仓库、报表类应用,进行大量的 COUNT() 查询(因为它将行数存储在元数据中,查询极快)。 不适用场景: 现代 Web 应用:因为需要事务和高并发写入。 任何需要数据可靠性和崩溃恢复的场景。 Memory 核心特性: 将所有数据存储在RAM 中,速度极快。 表结构在服务器重启后保留,但所有数据都会丢失。 默认使用哈希索引,也支持 B-tree 索引。 不支持 TEXT 或 BLOB 等变长数据类型。 适用场景: 用于存储临时、非关键的会话数据。 用于缓存中间结果集的查找表。 需要极快访问速度,且数据丢失也无所谓的场景。 不适用场景: 任何需要持久化存储的数据。 Archive 核心特性: 为大量很少被引用的历史、归档数据的存储和检索而优化。 插入速度非常快,并很好地压缩数据(磁盘 I/O 更少)。 只支持 INSERT 和 SELECT 操作,不支持 DELETE、UPDATE 和索引(在行上)。 支持行级锁。 适用场景: 日志记录、审计数据等需要大量存储但很少更新的归档数据。 CSV 核心特性: 以逗号分隔值的格式将数据存储在文本文件中。 不支持索引。 可以直接用文本编辑器打开查看。 适用场景: 快速导出数据到 CSV 格式,或从 CSV 文件导入数据。 与其他需要读写 CSV 文件的应用程序交换数据。 其他引擎 Blackhole:接受数据但不存储,就像“黑洞”。常用于复制架构或记录日志。 Federated:访问远程 MySQL 服务器上的表,而不是本地存储数据。注意:该引擎默认禁用,且有诸多限制,不建议使用。 Merge(MRG_MyISAM):将多个结构相同的 MyISAM 表逻辑上组合成一个表,适用于数据仓库。 列式存储引擎: MyRocks:由 Facebook 开发,基于 RocksDB,为高压缩率和写入密集型负载而优化。 ColumnStore:为大规模数据仓库和分析查询(OLAP)设计。 总结 特性 InnoDB MyISAM Memory Archive 存储限制 64TB 256TB RAM 无 事务 支持 不支持 不支持 不支持 锁粒度 行级锁 表级锁 表级锁 行级锁 外键 支持 不支持 不支持 不支持 全文索引 支持 不支持 不支持 不支持 MVCC 支持 不支持 不支持 不支持 压缩 支持 是(只读) 否 极高压缩 适用场景 绝大多数事务型应用 只读报表、Web(旧) 临时表、缓存 日志归档 如何选择? 默认选择 InnoDB:对于 99% 的新项目,直接使用 InnoDB。它提供了事务安全性和高并发能力,这是现代应用的基础。 需要全文搜索:优先使用 InnoDB 的全文索引。如果不能满足,可以考虑集成专业的搜索引擎(如 Elasticsearch 或 Solr),而不是用 MyISAM。 临时数据/缓存:考虑使用 Memory 引擎。 日志/归档:考虑使用 Archive 引擎。 数据仓库/只读报表:如果表完全是静态的(只读),且需要频繁地 COUNT(*),可以测试一下 MyISAM 的性能是否仍有优势,但需谨慎评估风险。更好的选择是使用列式存储引擎或专门的 OLAP 数据库。 高并发下MySQL优化 层面一:SQL语句与索引优化(成本最低,效果最显著) 这是优化的第一步,也是最关键的一步。80%的性能问题源于糟糕的SQL和索引。 避免低效SQL: 避免使用 SELECT *:只获取需要的字段,减少网络传输和内存消耗。 避免大事务:尽量将大事务拆分为小事务,及时提交,减少锁的持有时间。 避免函数操作 on 索引列:如 WHERE YEAR(create_time) = 2023 会导致索引失效。应改为 WHERE create_time BETWEEN '2023-01-01' AND '2023-12-31'。 避免类型转换:WHERE user_id = '123'(user_id 是整数)会导致类型转换,索引失效。 高效使用索引: 确保查询都使用到索引:使用 EXPLAIN 分析每一条核心SQL的执行计划,关注 type 字段(至少达到 range 级别,理想是 ref 或 const)。 创建覆盖索引:索引包含了查询所需的所有字段,无需回表,极大提升性能。例如 SELECT id, name FROM users WHERE status = 'active',可以建立索引 (status, name, id)。 注意联合索引的最左前缀原则:索引 (a, b, c) 可以用于查询 a=?,a=? AND b=?, a=? AND b=? AND c=?,但不能用于 b=? 或 c=?。 使用前缀索引:对于 TEXT/VARCHAR 等长字段,不必对整个字段建索引,可以使用 column_name(prefix_length)。 索引不是越多越好:索引会降低写操作(INSERT/UPDATE/DELETE)的速度,并占用额外空间。需要平衡读写比例。 利用批量操作: 批量插入:使用 INSERT INTO table VALUES (v1), (v2), (v3)... 代替多条单行插入。 批量更新:尽量减少循环逐条更新。 层面二:数据库设计优化 良好的设计是应对高并发的基础。 选择合适的存储引擎: InnoDB:绝对的主流选择。支持行级锁、外键、事务(ACID),写并发性能远优于MyISAM。除非有特殊理由,否则一律使用 InnoDB。 范式与反范式的权衡: 范式化:减少数据冗余,更新操作更快更简单。 反范式化:适当的数据冗余(如将常用字段冗余到主表),用空间换时间,减少表关联查询,适合读多写少的场景。例如,在订单列表里直接冗余“用户名”,而不是每次都要 JOIN 用户表。 数据类型优化: 使用最简单、最小的数据类型。例如,用 INT 而不是 VARCHAR 存储IP地址(使用 INET_ATON() 和 INET_NTOA() 转换)。 避免使用 NULL,尽量用默认值(如空字符串、0)。NULL 会使索引和值比较变得更复杂。 分库分表(sharding): 当单表数据量过大(如千万级)时,B+树深度增加,查询性能下降。 水平分表:将一张表的数据按某种规则(如用户ID哈希、时间范围)拆分到多个结构相同的表中。这是解决大数据量和高并发的主要手段。 垂直分表:将一张宽表(有很多列)按访问频率拆分成多个小表(如“热点字段表”和“冷数据表”)。 分库:在分表的基础上,将表分布到不同的物理数据库实例上,进一步分散压力。 工具:可以借助 ShardingSphere、MyCat 等中间件,但对应用侵入性较强,增加了系统复杂度。 层面三:架构优化(从单机到分布式) 这是应对极高并发的主要手段。 引入缓存(Cache) - 重中之重! 原则:缓存是数据库的前置屏障,绝大部分的读请求不应该到达数据库。 策略: 客户端缓存:浏览器缓存。 应用层缓存:本地缓存(如 Caffeine/Ehcache)或分布式缓存(如 Redis/Memcached)。将热点数据(如用户信息、商品信息、秒杀库存)放在缓存中。 数据库缓存:MySQL 自身的 query_cache(8.0已移除),效果一般,不推荐过多依赖。 注意缓存一致性和缓存穿透、击穿、雪崩问题。 读写分离(Read/Write Splitting): 基于主从复制(Master-Slave Replication)实现。 一主多从:主库(Master)负责处理写操作(事务),从库(Slave)负责处理读操作。 优点:分摊读压力,提升读性能。从库还可以用于备份和故障转移。 挑战:主从同步有延迟,需要业务能容忍短暂的数据不一致(如评论、点赞等场景适合,金融余额等场景不适合)。 消息队列(MQ)削峰填谷: 对于高并发的写操作(如秒杀下单、日志记录),可以将请求先写入消息队列(如 Kafka/RabbitMQ/RocketMQ)。 后端服务以自己能处理的速度从队列中消费消息,再写入数据库。 优点:避免流量洪峰直接冲垮数据库,将异步的、非核心的操作与主流程解耦。 层面四:MySQL配置与硬件优化 硬件优化: SSD硬盘:必须的。将机械硬盘(HDD)换成固态硬盘(SSD),IOPS提升几个数量级,是性价比最高的硬件升级。 内存:扩大内存,使 innodb_buffer_pool_size 足够大,能容纳整个工作数据集(working set),让查询尽可能在内存中完成,避免磁盘IO。 关键配置调优 (my.cnf): innodb_buffer_pool_size:最重要的参数。通常设置为可用物理内存的 50%-70%。 innodb_log_file_size:redo log 大小。更大的 log file 可以减少磁盘刷写。通常设置为 1G-4G。 max_connections:最大连接数。设置过高可能导致内存耗尽,需要配合连接池使用。 innodb_flush_log_at_trx_commit: =1(默认):完全ACID,每次事务提交都刷盘,最安全,但性能最差。 =2:每次事务提交只写 OS 缓存,每秒刷一次盘。性能更好,但宕机可能丢失1秒数据。 =0:每秒写日志和刷盘。性能最好,但最不安全。 根据业务对一致性和性能的要求权衡。很多金融业务用1,而一些能容忍少量数据丢失的场景(如日志、评论)可以用2。 sync_binlog:控制二进制日志(binlog)刷盘策略。类似上一条,=1 最安全,=0 或 >1 性能更好。 连接池: 无论是应用端(如 HikariCP, Druid)还是数据库端(如 ProxySQL),使用连接池可以避免频繁创建和销毁连接的开销。

优秀的个人博客,低调大师

SSM面试

Mybatis 简介 mybatis支持普通sql查询,存储过程和高级映射的优秀持久层框架,Mybatis消除了几乎所有的JDBC代码和参数的手工设置以及结果集的检查,Mybatis使用简单的xml或注解用于配置和原始映射,将接口和Java的POJO(plan old Java Object)普通的Java对象映射成数据库的记录 每个mybatis应用程序主要都是使用SqlSessionFaction实例的,一个sqlSessionFaction实例可以通过sqlSessionFactionBuilder获得,SqlSessionFactionBuilder可以从一个xml配置文件或者一个预定义的配置类的实例获得 mybatis有一个实用类(Resources)它由很多方法,可以很方便地从类路径及其他位置加载资源 流程 加载配置并初始化 触发条件:加载配置文件 将sql的配置信息加载成为一个个mappedStatment对象,包括了传入参数映射 配置执行的sql语句,结果集映射配置,存储在内存中 接受调用请求 触发条件:调用mybatis提供的API 传入参数:为sql的id和传入参数对象 处理过程:将请求传递给下层的请求处理层进行处理 处理操作请求 触发条件:API接口层传递请求过来 传入参数:为sql的id和传入参数对象 处理过程: 根据sql的id查找对应的MappedStatment对象 根据传入参数对象解析MappedStatment对象,得到最终要执行的sql和执行传入的参数 获取数据连接,根据得到最终的sql语句和执行传入参数到数据库执行,并得到执行结果 根据MappedStatment对象中的结果映射配置对得到的执行结果进行转换处理,并得到最终的处理结果 释放连接资源 返回处理结果将最终的处理结果返回 功能架构(三层) API接口层: 提供给外部使用的接口API,开发人员通过本地API操作数据库,接口层接到调用请求,就会调用数据处理来完成具体的数据处理 数据处理层: 负责具体的sql查找、解析、执行和执行结果映射处理等,主要的目的是根据调用的请求完成一次数据库的操作 基础支持层 负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共用的东西,将他们抽取出来做最基础的组件,为上层的数据处理层提供最基础的支撑 框架结构 加载配置 配置来源于两个地方: 1).配置文件中 2).Java代码的注解,将sql的配置信息加载为一个MappedStatment对象(包括传入参数映射配置,执行sql语句,结果映射配置)存储在内存中 sql解析 当API接口层接收到调用请求时,会接收到传入sql的id和传入对象(可以是map、JavaBean或基本类型),mybatis会根据sql的id找到对应的MappedStatment,然后传入参数对象的MappedStatment进行解析,解析后可以得到最终的要执行的sql语句和参数 sql执行 将最终得到的sql和参数拿到数据库进行执行,得到操作数据库的结果 结果映射 将操作数据库的结果按照映射的配置进行转换,可以转换成HashMap、JavaBean或者基本类型,并将最终结果返回 mybatis中#{}与${}的区别 ${} 是properties文件中的占位符,可用于标签属性值和sql内部,属于静态文本替换,比如${driver}会被静态替换为com.mysql.jdbc.Driver #{} 是sql的参数占位符,Mybatis会将sql中的#{}替换为?,在sql执行前会使用preparedStatement的参数设置方法,按序给sql的?占位符设置参数值,比如ps.setInt(0,ParameterValue),#{item.name}的取值方式为使用反射从参数对象中获取item对象的name属性值,相当于param.getItem().getName()。 XML映射文件中,除了常见的select、insert、update、delete标签之外,还有哪些标签 <resultMap>返回值类型、<sql>、<include>、<selectKey>加上动态sql的9个标签(trim、where、set、foreach、if、choose、when、otherwise、bind)其中<sql>为sql片段标签,通过<include>标签引入sql片段,<selectKey>为不支持自增的主键生产策略标签 一个XML文件都会写一个Dao接口与之对应,工作原理 Dao接口,常说的mapper接口,接口的权限名就是映射文件中的namespace的值,接口的方法名,就是映射文件MappedStatment的id值,接口方法的参数就是传递给slq的参数,mapper接口是没有实现类的,当调用接口方法时,接口权限名+方法名拼接字符串作为key值,可唯一定位一个mappedStatmentDao接口里的方法是不能重载的,因为是权限名+方法名的保存和寻找策略 Mybatis是如何分页的 mybatis是使用RowBounds对象进行分页的,针对ResultSet结果集进行的内存分页而非物理分页,可以在sql内直接书写带有物理分页的参数来完成物理分页的功能 springMVC 是一个mvc框架,在web模型中,mvc是一种很流行的框架,通过把model、view、controller分离,把较为复杂的web应用分成逻辑清晰的几部分,是为了简化开发,减少出错,或是为了组内开发人员之间的配,总之就是一种分层工作的办法 是spring的一个子框架,当然拥有spring的特性,如依赖注入,注解:RequestMapping,专门负责映射的url spring是什么 spring是开源的轻量级的开发应用框架,目的是用于简化企业级应用程序开发 原理 通过配置方式来创建对象,管理对象之间依赖关系,我们不需要通过工厂和生成器来创建及管理对象之间的依赖关系,这样我们是不是减少了许多工作,加速了开发 spring框架除了帮助我们管理对象及其依赖关系,还提供了日志记录、性能统计、安全控制、异常处理等面向切面的能力,还能帮我们管理最头疼的数据库事务,本身提供了一套简单的JDBC访问实现,提供与第三方数据访问框架集成(如Hibernate、JPA)与各种javaEE技术整合,提供一套自己的web层框架springMVC,而且还能非常简单的与第三方web框架集成 作用 能帮助我们根据配置文件创建及组装对象之间的依赖关系(无需重新编译,只需要修改配置文件) spring提供了与第三方数据访问框架无缝集成,而且自己提供了一套JDBC/springMVC框架,方便数据库/web层搭建(第三方web【如struts、JSF】) 为什么需要spring 应用程序 是能完成我们所需的功能的成品,比如:购物网站、OA系统 框架 是能够完成功能的半产品,框架规定了开发过程中的整体架构,提供了基础功能 非侵入式设计 从框架角度可以理解为,无需继承框架提供的类 POJO(plan old java Object)简单的Java对象 它可以包含业务逻辑或持久化逻辑,但不担当任何特殊角色且不继承或不实现任何其他java框架的类或接口 轻量级及重量级 轻量级是相对于重量级而言的,轻量级一般是非入侵性的,所依赖的东西非常少,资源占用非常少,部署简单,极易使用,重量级相反 容器 装对象的对象,因为存在放入、拿出等操作,所以容器还要管理对象的生命周期 控制反转 即Inversion of Control(IOC)/ 依赖注入(Dependency Injection),即由容器控制程序之间的关系,而非由程序直接操控 Bean 一般指容器管理对象,在spring中指springIOC容器管理对象 为什么使用spring 能帮助我们简化应用程序开发,帮助我们创建和组装对象,为我们管理事务,简单的mvc框架,可以把spring看作是一个超级粘合平台,能够把很多技术整合在一起,形成一个整体,使系统结构更出众,更优秀,从而加速我们程序开发 Spring的BeanFactory和ApplicationContext的区别 spring使用BeanFactory来实例化配置和管理对象,但是它只是一个接口,里面有一个getBean()方法,我们一般都不直接用BeanFactory,而是用它的实现类ApplicationContext,这个类会自动解析我们配置的applicationContext.xml,然后根据我们配置的bean来new对象,将new好的对象放进一个Map中,键就是我们bean的id,值就是new的对象BeanFactory是spring中比较原始的Factory,如XMLBeanFactory就是一种典型的BeanFactory,原始的BeanFactory无法支持spring的许多插件,例如:AOP、web应用等ApplicationContext接口,它由BeanFactory接口派生而来,因而提供BeanFactory所有的功能 容器是spring的核心 BeanFactory 是spring中比较原始的Factory,如XMLBeanFactory就是一种典型的BeanFactory,原始的BeanFactory无法支持spring的许多插件,例如:AOP、web应用等 ApplicationContext 三个实现类 classPathXMLApplication:把上下文文件当成类路径资源 FileSystemXMLApplication:从文件系统中的XML文件载入上下文定义信息 XMLWebApplicationContext:从web系统中的XML文件载入上下文定义信息 作用 BeanFactory负责读取Bean配置文档,管理Bean的加载,实例化,维护Bean之间的依赖关系,负责Bean的生命周期 ApplicationContext除了提供上述BeanFactory所能提供的功能之外,还提供了更完整的框架功能 a. 国际化支持 b. 资源访问 c. 时间传递(通过实现ApplicationContextAware接口) 常用的获取ApplicationContext的方法 FileSystemXMLApplicationConrext 从文件系统或者url指定的xml配置文件创建参数为配置文件名或文件名数组。 classPathXMLApplicationContext 从classPath的xml配置文件创建,可以从jar包中读取配置文件

优秀的个人博客,低调大师

Java面试

1、java事件机制包括哪三个部分?分别介绍。 2、为什么要使用线程池? 3、线程池有什么作用? 4、说说几种常见的线程池及使用场景。 5、线程池都有哪几种工作队列? 6、怎么理解无界队列和有界队列? 7、线程池中的几种重要的参数及流程说明。 8、什么是反射机制? 9、说说反射机制的作用。 10、反射机制会不会有性能问题? 11、你怎么理解http协议? 12、说说http协议的工作流程。 13、http有哪些请求提交方式? 14、http中的200,302,403,404,500,503都代表什么状态? 15、http get和post有什么区别? 16、你怎么理解cookie和session,有哪些不同点? 17、什么是web缓存?有什么优点? 18、什么是https,说说https的工作原理? 19、什么是http代理服务器,有什么用? 20、什么是虚拟主机及实现原理? 21、什么是Java虚拟机,为什么要使用? 22、说说Java虚拟机的生命周期及体系结构。 23、说一说Java内存区域。 24、什么是分布式系统? 25、分布式系统你会考虑哪些方面? 26、讲一讲TCP协议的三次握手和四次挥手流程。 27、为什么TCP建立连接协议是三次握手,而关闭连接却是四次握手呢?为什么不能用两次握手进行连接? 28、为什么TCP TIME_WAIT状态还需要等2MSL后才能返回到CLOSED状态? 29、什么是DoS、DDoS、DRDoS攻击?如何防御? 30、描述一下Java异常层次结构。 31、什么是检查异常,不受检查异常,运行时异常?并分别举例说明。 32、finally块一定会执行吗? 33、正常情况下,当在try块或catch块中遇到return语句时,finally语句块在方法返回之前还是之后被执行? 34、try、catch、finally语句块的执行顺序。 35、Java虚拟机中,数据类型可以分为哪几类? 36、怎么理解栈、堆?堆中存什么?栈中存什么? 37、为什么要把堆和栈区分出来呢?栈中不是也可以存储数据吗? 38、在Java中,什么是是栈的起始点,同是也是程序的起始点? 39、为什么不把基本类型放堆中呢? 40、Java中的参数传递时传值呢?还是传引用? 41、Java中有没有指针的概念? 42、Java中,栈的大小通过什么参数来设置? 43、一个空Object对象的占多大空间? 44、对象引用类型分为哪几类? 45、讲一讲垃圾回收算法。 46、如何解决内存碎片的问题? 47、如何解决同时存在的对象创建和对象回收问题? 48、讲一讲内存分代及生命周期。 49、什么情况下触发垃圾回收? 50、如何选择合适的垃圾收集算法? 51、JVM中最大堆大小有没有限制? 52、堆大小通过什么参数设置? 53、JVM有哪三种垃圾回收器? 54、吞吐量优先选择什么垃圾回收器?响应时间优先呢? 55、如何进行JVM调优?有哪些方法? 56、如何理解内存泄漏问题?有哪些情况会导致内存泄露?如何解决? 57、从分布式系统部署角度考虑,分哪几层? 58、如何解决业务层的数据访问问题? 59、为了解决数据库服务器的负担,如何做数据库的分布? 60、什么是著名的拜占庭将军问题? 61、为什么说TCP/IP协议是不可靠的? 62、讲讲CAP理念。 63、怎么理解强一致性、单调一致性和最终一致性? 64、分布式系统设计你会考虑哪些策略? 65、最常见的数据分布方式是什么? 66、谈一谈一致性哈希算法。 67、paxos是什么? 68、什么是Lease机制? 69、如何理解选主算法? 70、OSI有哪七层模型?TCP/IP是哪四层模型。

优秀的个人博客,低调大师

面试准备

想去名企锻炼自己的技术本领,梳理一下技术的要求,做好准备,希望能达成自己的目标。 Java基础扎实,理解IO,多线程,集合等基础框架,对JVM有一定的了解。 前天一位Java大牛告诉我,刚毕业一年最好还是准备基础,刚毕业一年没有大型的项目经验,基础就要足够的耐问。 对使用的开源框架,能了解到他的原理和机制,对Spring,Mybatis等开源框架熟悉。 Spring,Mybatis系列的开源框架,研读源码...一定要能说自己精通Spring,Mybatis。做到如此才有希望。 熟悉分布式系统的设计和应用,熟悉分布式,缓存,消息等机制,能对分布式常用技术进行合理应用解决问题。 分布式的毕竟也做过一些,但要有自己的思考... 熟悉Linux操作系统,和大型数据库(Oracle,MySQL),对常见的NoSQL(Redis,MongoDB,HBase,Memcached)有一点研究。 追求并尝试最新的技术,追求编码的优雅,从技术趋势和思路上能影响团队,学习能力好,适应能力强,具备耐心,细心的品质。 刷名企Offer这本书,熟悉Tcp/ip协议,数据结构和算法。 最后 我能准备的差不多就以上的内容了,干!

优秀的个人博客,低调大师

Spark面试

1、简答说一下hadoop的map-reduce编程模型 首先map task会从本地文件系统读取数据,转换成key-value形式的键值对集合 使用的是hadoop内置的数据类型,比如longwritable、text等 将键值对集合输入mapper进行业务处理过程,将其转换成需要的key-value在输出 之后会进行一个partition分区操作,默认使用的是hashpartitioner,可以通过重写hashpartitioner的getpartition方法来自定义分区规则 之后会对key进行进行sort排序,grouping分组操作将相同key的value合并分组输出,在这里可以使用自定义的数据类型,重写WritableComparator的Comparator方法来自定义排序规则,重写RawComparator的compara方法来自定义分组规则 之后进行一个combiner归约操作,其实就是一个本地段的reduce预处理,以减小后面shufle和reducer的工作量 reduce task会通过网络将各个数据收集进行reduce处理,最后将数据保存或者显示,结束整个job 2、hadoop的TextInputFormat作用是什么,如何自定义实现 InputFormat会在map操作之前对数据进行两方面的预处理 1是getSplits,返回的是InputSplit数组,对数据进行split分片,每片交给map操作一次 2是getRecordReader,返回的是RecordReader对象,对每个split分片进行转换为key-value键值对格式传递给map 常用的InputFormat是TextInputFormat,使用的是LineRecordReader对每个分片进行键值对的转换,以行偏移量作为键,行内容作为值 自定义类继承InputFormat接口,重写createRecordReader和isSplitable方法 在createRecordReader中可以自定义分隔符 3、hadoop和spark的都是并行计算,那么他们有什么相同和区别 两者都是用mr模型来进行并行计算,hadoop的一个作业称为job,job里面分为map task和reduce task,每个task都是在自己的进程中运行的,当task结束时,进程也会结束 spark用户提交的任务成为application,一个application对应一个sparkcontext,app中存在多个job,每触发一次action操作就会产生一个job 这些job可以并行或串行执行,每个job中有多个stage,stage是shuffle过程中DAGSchaduler通过RDD之间的依赖关系划分job而来的,每个stage里面有多个task,组成taskset有TaskSchaduler分发到各个executor中执行,executor的生命周期是和app一样的,即使没有job运行也是存在的,所以task可以快速启动读取内存进行计算 hadoop的job只有map和reduce操作,表达能力比较欠缺而且在mr过程中会重复的读写hdfs,造成大量的io操作,多个job需要自己管理关系 spark的迭代计算都是在内存中进行的,API中提供了大量的RDD操作如join,groupby等,而且通过DAG图可以实现良好的容错 4、为什么要用flume导入hdfs,hdfs的构架是怎样的 flume可以实时的导入数据到hdfs中,当hdfs上的文件达到一个指定大小的时候会形成一个文件,或者超过指定时间的话也形成一个文件 文件都是存储在datanode上面的,namenode记录着datanode的元数据信息,而namenode的元数据信息是存在内存中的,所以当文件切片很小或者很多的时候会卡死 5、map-reduce程序运行的时候会有什么比较常见的问题 比如说作业中大部分都完成了,但是总有几个reduce一直在运行 这是因为这几个reduce中的处理的数据要远远大于其他的reduce,可能是因为对键值对任务划分的不均匀造成的数据倾斜 解决的方法可以在分区的时候重新定义分区规则对于value数据很多的key可以进行拆分、均匀打散等处理,或者是在map端的combiner中进行数据预处理的操作 6、简单说一下hadoop和spark的shuffle过程 hadoop:map端保存分片数据,通过网络收集到reduce端 spark:spark的shuffle是在DAGSchedular划分Stage的时候产生的,TaskSchedule要分发Stage到各个worker的executor 减少shuffle可以提高性能 7、Hive中存放是什么? 表。 存的是和hdfs的映射关系,hive是逻辑上的数据仓库,实际操作的都是hdfs上的文件,HQL就是用sql语法来写的mr程序。 8、Hive与关系型数据库的关系? 没有关系,hive是数据仓库,不能和数据库一样进行实时的CURD操作。 是一次写入多次读取的操作,可以看成是ETL工具。 9、Flume工作机制是什么? 核心概念是agent,里面包括source、chanel和sink三个组件。 source运行在日志收集节点进行日志采集,之后临时存储在chanel中,sink负责将chanel中的数据发送到目的地。 只有成功发送之后chanel中的数据才会被删除。 首先书写flume配置文件,定义agent、source、chanel和sink然后将其组装,执行flume-ng命令。 10、Sqoop工作原理是什么? hadoop生态圈上的数据传输工具。 可以将关系型数据库的数据导入非结构化的hdfs、hive或者bbase中,也可以将hdfs中的数据导出到关系型数据库或者文本文件中。 使用的是mr程序来执行任务,使用jdbc和关系型数据库进行交互。 import原理:通过指定的分隔符进行数据切分,将分片传入各个map中,在map任务中在每行数据进行写入处理没有reduce。 export原理:根据要操作的表名生成一个java类,并读取其元数据信息和分隔符对非结构化的数据进行匹配,多个map作业同时执行写入关系型数据库 11、Hbase行健列族的概念,物理模型,表的设计原则? 行健:是hbase表自带的,每个行健对应一条数据。 列族:是创建表时指定的,为列的集合,每个列族作为一个文件单独存储,存储的数据都是字节数组,其中的数据可以有很多,通过时间戳来区分。 物理模型:整个hbase表会拆分为多个region,每个region记录着行健的起始点保存在不同的节点上,查询时就是对各个节点的并行查询,当region很大时使用.META表存储各个region的起始点,-ROOT又可以存储.META的起始点。 rowkey的设计原则:各个列簇数据平衡,长度原则、相邻原则,创建表的时候设置表放入regionserver缓存中,避免自动增长和时间,使用字节数组代替string,最大长度64kb,最好16字节以内,按天分表,两个字节散列,四个字节存储时分毫秒。 列族的设计原则:尽可能少(按照列族进行存储,按照region进行读取,不必要的io操作),经常和不经常使用的两类数据放入不同列族中,列族名字尽可能短。 12、Spark Streaming和Storm有何区别? 一个实时毫秒一个准实时亚秒,不过storm的吞吐率比较低。 13、mllib支持的算法? 大体分为四大类,分类、聚类、回归、协同过滤。 14、简答说一下hadoop的map-reduce编程模型? 首先map task会从本地文件系统读取数据,转换成key-value形式的键值对集合。 将键值对集合输入mapper进行业务处理过程,将其转换成需要的key-value在输出。 之后会进行一个partition分区操作,默认使用的是hashpartitioner,可以通过重写hashpartitioner的getpartition方法来自定义分区规则。 之后会对key进行进行sort排序,grouping分组操作将相同key的value合并分组输出。 在这里可以使用自定义的数据类型,重写WritableComparator的Comparator方法来自定义排序规则,重写RawComparator的compara方法来自定义分组规则。 之后进行一个combiner归约操作,其实就是一个本地段的reduce预处理,以减小后面shufle和reducer的工作量。 reduce task会通过网络将各个数据收集进行reduce处理,最后将数据保存或者显示,结束整个job。 15、Hadoop平台集群配置、环境变量设置? zookeeper:修改zoo.cfg文件,配置dataDir,和各个zk节点的server地址端口,tickTime心跳时间默认是2000ms,其他超时的时间都是以这个为基础的整数倍,之后再dataDir对应目录下写入myid文件和zoo.cfg中的server相对应。 hadoop:修改 hadoop-env.sh配置java环境变量 core-site.xml配置zk地址,临时目录等 hdfs-site.xml配置nn信息,rpc和http通信地址,nn自动切换、zk连接超时时间等 yarn-site.xml配置resourcemanager地址 mapred-site.xml配置使用yarn slaves配置节点信息 格式化nn和zk。 hbase:修改 hbase-env.sh配置java环境变量和是否使用自带的zk hbase-site.xml配置hdfs上数据存放路径,zk地址和通讯超时时间、master节点 regionservers配置各个region节点 zoo.cfg拷贝到conf目录下 spark: 安装Scala 修改spark-env.sh配置环境变量和master和worker节点配置信息 环境变量的设置:直接在/etc/profile中配置安装的路径即可,或者在当前用户的宿主目录下,配置在.bashrc文件中,该文件不用source重新打开shell窗口即可,配置在.bash_profile的话只对当前用户有效。 16、Hadoop性能调优? 调优可以通过系统配置、程序编写和作业调度算法来进行。 hdfs的block.size可以调到128/256(网络很好的情况下,默认为64) 调优的大头:mapred.map.tasks、mapred.reduce.tasks设置mr任务数(默认都是1) mapred.tasktracker.map.tasks.maximum每台机器上的最大map任务数 mapred.tasktracker.reduce.tasks.maximum每台机器上的最大reduce任务数 mapred.reduce.slowstart.completed.maps配置reduce任务在map任务完成到百分之几的时候开始进入 这个几个参数要看实际节点的情况进行配置,reduce任务是在33%的时候完成copy,要在这之前完成map任务,(map可以提前完成) mapred.compress.map.output,mapred.output.compress配置压缩项,消耗cpu提升网络和磁盘io 合理利用combiner 注意重用writable对象 17、Hadoop高并发? 首先肯定要保证集群的高可靠性,在高并发的情况下不会挂掉,支撑不住可以通过横向扩展。 datanode挂掉了使用hadoop脚本重新启动。 18、hadoop的TextInputFormat作用是什么,如何自定义实现? InputFormat会在map操作之前对数据进行两方面的预处理。 1是getSplits,返回的是InputSplit数组,对数据进行split分片,每片交给map操作一次 。 2是getRecordReader,返回的是RecordReader对象,对每个split分片进行转换为key-value键值对格式传递给map。 常用的InputFormat是TextInputFormat,使用的是LineRecordReader对每个分片进行键值对的转换,以行偏移量作为键,行内容作为值。 自定义类继承InputFormat接口,重写createRecordReader和isSplitable方法 。 在createRecordReader中可以自定义分隔符。 19、hadoop和spark的都是并行计算,那么他们有什么相同和区别? 两者都是用mr模型来进行并行计算,hadoop的一个作业称为job,job里面分为map task和reduce task,每个task都是在自己的进程中运行的,当task结束时,进程也会结束。 spark用户提交的任务成为application,一个application对应一个sparkcontext,app中存在多个job,每触发一次action操作就会产生一个job。 这些job可以并行或串行执行,每个job中有多个stage,stage是shuffle过程中DAGSchaduler通过RDD之间的依赖关系划分job而来的,每个stage里面有多个task,组成taskset有TaskSchaduler分发到各个executor中执行,executor的生命周期是和app一样的,即使没有job运行也是存在的,所以task可以快速启动读取内存进行计算。 hadoop的job只有map和reduce操作,表达能力比较欠缺而且在mr过程中会重复的读写hdfs,造成大量的io操作,多个job需要自己管理关系。 spark的迭代计算都是在内存中进行的,API中提供了大量的RDD操作如join,groupby等,而且通过DAG图可以实现良好的容错。 20、为什么要用flume导入hdfs,hdfs的构架是怎样的? flume可以实时的导入数据到hdfs中,当hdfs上的文件达到一个指定大小的时候会形成一个文件,或者超过指定时间的话也形成一个文件。 文件都是存储在datanode上面的,namenode记录着datanode的元数据信息,而namenode的元数据信息是存在内存中的,所以当文件切片很小或者很多的时候会卡死。 21、map-reduce程序运行的时候会有什么比较常见的问题? 比如说作业中大部分都完成了,但是总有几个reduce一直在运行。 这是因为这几个reduce中的处理的数据要远远大于其他的reduce,可能是因为对键值对任务划分的不均匀造成的数据倾斜。 解决的方法可以在分区的时候重新定义分区规则对于value数据很多的key可以进行拆分、均匀打散等处理,或者是在map端的combiner中进行数据预处理的操作。 22、简单说一下hadoop和spark的shuffle过程? hadoop:map端保存分片数据,通过网络收集到reduce端。 spark:spark的shuffle是在DAGSchedular划分Stage的时候产生的,TaskSchedule要分发Stage到各个worker的executor。 减少shuffle可以提高性能。 23、RDD机制? rdd分布式弹性数据集,简单的理解成一种数据结构,是spark框架上的通用货币。 所有算子都是基于rdd来执行的,不同的场景会有不同的rdd实现类,但是都可以进行互相转换。 rdd执行过程中会形成dag图,然后形成lineage保证容错性等。 从物理的角度来看rdd存储的是block和node之间的映射。 24、spark有哪些组件? (1)master:管理集群和节点,不参与计算。 (2)worker:计算节点,进程本身不参与计算,和master汇报。 (3)Driver:运行程序的main方法,创建spark context对象。 (4)spark context:控制整个application的生命周期,包括dagsheduler和task scheduler等组件。 (5)client:用户提交程序的入口。 25、spark工作机制? 用户在client端提交作业后,会由Driver运行main方法并创建spark context上下文。 执行add算子,形成dag图输入dagscheduler,按照add之间的依赖关系划分stage输入task scheduler。 task scheduler会将stage划分为task set分发到各个节点的executor中执行。 26、spark的优化怎么做? 通过spark-env文件、程序中sparkconf和set property设置。 (1)计算量大,形成的lineage过大应该给已经缓存了的rdd添加checkpoint,以减少容错带来的开销。 (2)小分区合并,过小的分区造成过多的切换任务开销,使用repartition。 27、kafka工作原理? producer向broker发送事件,consumer从broker消费事件。 事件由topic区分开,每个consumer都会属于一个group。 相同group中的consumer不能重复消费事件,而同一事件将会发送给每个不同group的consumer。 28、ALS算法原理? 答:对于user-product-rating数据,als会建立一个稀疏的评分矩阵,其目的就是通过一定的规则填满这个稀疏矩阵。 als会对稀疏矩阵进行分解,分为用户-特征值,产品-特征值,一个用户对一个产品的评分可以由这两个矩阵相乘得到。 通过固定一个未知的特征值,计算另外一个特征值,然后交替反复进行最小二乘法,直至差平方和最小,即可得想要的矩阵。 29、kmeans算法原理? 随机初始化中心点范围,计算各个类别的平均值得到新的中心点。 重新计算各个点到中心值的距离划分,再次计算平均值得到新的中心点,直至各个类别数据平均值无变化。 30、canopy算法原理? 根据两个阈值来划分数据,以随机的一个数据点作为canopy中心。 计算其他数据点到其的距离,划入t1、t2中,划入t2的从数据集中删除,划入t1的其他数据点继续计算,直至数据集中无数据。 31、朴素贝叶斯分类算法原理? 对于待分类的数据和分类项,根据待分类数据的各个特征属性,出现在各个分类项中的概率判断该数据是属于哪个类别的。 32、关联规则挖掘算法apriori原理? 一个频繁项集的子集也是频繁项集,针对数据得出每个产品的支持数列表,过滤支持数小于预设值的项,对剩下的项进行全排列,重新计算支持数,再次过滤,重复至全排列结束,可得到频繁项和对应的支持数。

优秀的个人博客,低调大师

面试2

JAVA 相关 1.静态内部类、内部类、匿名内部类,为什么内部类会持有外部类的引用?持有的引用是this?还是其它? 静态内部类:使用static修饰的内部类 内部类:就是在某个类的内部又定义了一个类,内部类所嵌入的类称为外部类 匿名内部类:使用new生成的内部类 因为内部类的产生依赖于外部类,持有的引用是类名.this 2.Java中try catch finally的执行顺序 先执行try中代码,如果发生异常执行catch中代码,最后一定会执行finally中代码 3.equals与==的区别: ==是判断两个变量或实例是不是指向同一个内存空间 equals是判断两个变量或实例所指向的内存空间的值是不是相 4.Object有哪些公用方法? 方法equals测试的是两个对象是否相等 方法clone进行对象拷贝 方法getClass返回和当前对象相关的Class对象 方法notify,notifyall,wait都是用来对给定对象进行线程同步的 5.String、StringBuffer与StringBuilder的区别 String 类型和 StringBuffer 类型的主要性能区别其实在于 String 是不可变的对象 StringBuffer和StringBuilder底层是 char[]数组实现的 StringBuffer是线程安全的,而StringBuilder是线程不安全的 6.Java的四种引用的区别 强引用:如果一个对象具有强引用,它就不会被垃圾回收器回收。即使当前内存空间不足,JVM 也不会回收它,而是抛出 OutOfMemoryError 错误,使程序异常终止。如果想中断强引用和某个对象之间的关联,可以显式地将引用赋值为null,这样一来的话,JVM在合适的时间就会回收该对象 软引用:在使用软引用时,如果内存的空间足够,软引用就能继续被使用,而不会被垃圾回收器回收,只有在内存不足时,软引用才会被垃圾回收器回收。 弱引用:具有弱引用的对象拥有的生命周期更短暂。因为当 JVM 进行垃圾回收,一旦发现弱引用对象,无论当前内存空间是否充足,都会将弱引用回收。不过由于垃圾回收器是一个优先级较低的线程,所以并不一定能迅速发现弱引用对象 虚引用:顾名思义,就是形同虚设,如果一个对象仅持有虚引用,那么它相当于没有引用,在任何时候都可能被垃圾回收器回收。 7.介绍垃圾回收机制 标记回收法:遍历对象图并且记录可到达的对象,以便删除不可到达的对象,一般使用单线程工作并且可能产生内存碎片 标记-压缩回收法:前期与第一种方法相同,只是多了一步,将所有的存活对象压缩到内存的一端,这样内存碎片就可以合成一大块可再利用的内存区域,提高了内存利用率 复制回收法:把现有内存空间分成两部分,gc运行时,它把可到达对象复制到另一半空间,再清空正在使用的空间的全部对象。这种方法适用于短生存期的对象,持续复制长生存期的对象则导致效率降低。 分代回收发:把内存空间分为两个或者多个域,如年轻代和老年代,年轻代的特点是对象会很快被回收,因此在年轻代使用效率比较高的算法。当一个对象经过几次回收后依然存活,对象就会被放入称为老年的内存空间,老年代则采取标记-压缩算法 集合、数据结构相关 1.你用过哪些集合类 数据结构中用于存储数据的有哪些 数组 数组存储区间是连续的,占用内存严重,故空间复杂的很大。但数组的二分查找时间复杂度小,为O(1);数组的特点是:寻址容易,插入和删除困难; 链表 链表存储区间离散,占用内存比较宽松,故空间复杂度很小,但时间复杂度很大,达O(N)。链表的特点是:寻址困难,插入和删除容易。 2.说说hashMap是怎样实现的 哈希表:由数组+链表组成的 当我们往HashMap中put元素的时候,先根据key的hashCode重新计算hash值,根据hash值得到这个元素在数组中的位置(即下标),如果数组该位置上已经存放有其他元素了,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放在链尾。如果数组该位置上没有元素,就直接将该元素放到此数组中的该位置上。 3.ArrayList,LinkedList的区别 ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。 对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。 对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。 4.ArrayList和Vector的主要区别是什么? ArrayList 和Vector底层是采用数组方式存储数据 Vector: 线程同步 当Vector中的元素超过它的初始大小时,Vector会将它的容量翻倍, ArrayList: 线程不同步,但性能很好 当ArrayList中的元素超过它的初始大小时,ArrayList只增加50%的大小 5.HashMap和 HashTable 的区别: HashTable比较老,是基于Dictionary 类实现的,HashTable 则是基于 Map接口实现的 HashTable 是线程安全的, HashMap 则是线程不安全的 HashMap可以让你将空值作为一个表的条目的key或value 算法相关 1.排序算法和稳定性,快排什么时候情况最坏? 2.给最外层的rootview,把这个根视图下的全部button背景设置成红色,手写代码,不许用递归 算法原理: Android的view视图是按树形结构分布,所以按树形结构遍历 循环判断每一层的ViewGroup元素,将其入栈;否则判断当前view是否是Button类实例,是则改写背景色 当前ViewGroup检查childView完成后,判断栈是否非空,取出栈顶元素ViewGroup重复步骤2直至栈为空。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void changeAllBtnBGColor(View view, int color) { if (view == null || !(view instanceof ViewGroup)) return ; Stack m = new Stack<>(); while (view != null ) { ViewGroup tmpGroup = (ViewGroup) view; int count = tmpGroup.getChildCount(); for ( int i = 0 ; i < count; i++) { View child = tmpGroup.getChildAt(i); if (child instanceof ViewGroup) m.add(child); else if (child instanceof Button) { child.setBackgroundColor(color); } } if (m.isEmpty()) break ; else view = m.pop(); } } Thread、AsynTask相关 1.wait()和sleep()的区别 sleep来自Thread类,和wait来自Object类 调用sleep()方法的过程中,线程不会释放对象锁。而 调用 wait 方法线程会释放对象锁 sleep睡眠后不出让系统资源,wait让出系统资源其他线程可以占用CPU sleep(milliseconds)需要指定一个睡眠时间,时间一到会自动唤醒 2.若Activity已经销毁,此时AsynTask执行完并且返回结果,会报异常吗? 当一个App旋转时,整个Activity会被销毁和重建。当Activity重启时,AsyncTask中对该Activity的引用是无效的,因此onPostExecute()就不会起作用,若AsynTask正在执行,折会报 view not attached to window manager 异常 同样也是生命周期的问题,在 Activity 的onDestory()方法中调用Asyntask.cancal方法,让二者的生命周期同步 3.Activity销毁但Task如果没有销毁掉,当Activity重启时这个AsyncTask该如何解决? 还是屏幕旋转这个例子,在重建Activity的时候,会回掉Activity.onRetainNonConfigurationInstance()重新传递一个新的对象给AsyncTask,完成引用的更新 4.Android 线程间通信有哪几种方式(重要) 共享内存(变量); 文件,数据库; Handler; Java 里的 wait(),notify(),notifyAll() 5.请介绍下 AsyncTask的内部实现,适用的场景是 AsyncTask 内部也是 Handler 机制来完成的,只不过 Android 提供了执行框架来提供线程池来 执行相应地任务,因为线程池的大小问题,所以 AsyncTask 只应该用来执行耗时时间较短的任务, 比如 HTTP 请求,大规模的下载和数据库的更改不适用于 AsyncTask,因为会导致线程池堵塞,没有 线程来执行其他的任务,导致的情形是会发生 AsyncTask 根本执行不了的问题。 网络相关 1.TCP三次握手 2.为什么TCP是可靠的,UDP早不可靠的?为什么UDP比TCP快? TCP/IP协议高,因为其拥有三次握手双向机制,这一机制保证校验了数据,保证了他的可靠性。 UDP就没有了,udp信息发出后,不验证是否到达对方,所以不可靠。 但是就速度来说,还是UDP协议更高,毕竟其无需重复返回验证,只是一次性的 3.http协议了解多少,说说里面的协议头部有哪些字段? http(超文本传输协议)是一个基于请求与响应模式的、无状态的、应用层的协议;http请求由三部分组成,分别是:请求行、消息报头、请求正文。 HTTP消息报头包括普通报头、请求报头、响应报头、实体报头 4.https了解多少 HTTPS(全称:Hyper Text Transfer Protocol over Secure Socket Layer),是以安全为目标的HTTP通道,简单讲是HTTP的安全版。即HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。 5.谈谈 HTTP 中Get 和 Post 方法的区别 GET - 从指定的服务器中获取数据,明文发送内容 POST - 提交数据给指定的服务器处理 1. POST请求不能被缓存下来 2. POST请求不会保存在浏览器浏览记录中 3. 以POST请求的URL无法保存为浏览器书签 4. POST请求没有长度限制 6.推送心跳包是TCP包还是UDP包或者HTTP包 心跳包的实现是调用了socket.sendUrgentData(0xFF)这句代码实现的,所以,当然是TCP包。 7.如何实现文件断点上传 在 Android 中上传文件可以采用 HTTP 方式,也可以采用 Socket 方式,但是 HTTP 方式不能上传 大文件,这里介绍一种通过 Socket 方式来进行断点续传的方式,服务端会记录下文件的上传进度, 当某一次上传过程意外终止后,下一次可以继续上传,这里用到的其实还是 J2SE 里的知识。 这个上传程序的原理是:客户端第一次上传时向服务端发送 “Content-Length=35;filename=WinRAR_3.90_SC.exe;sourceid=“这种格式的字符串,服务端 收到后会查找该文件是否有上传记录,如果有就返回已经上传的位置,否则返回新生成的 sourceid 以及 position 为 0,类似 sourceid=2324838389;position=0“这样的字符串,客户端收到返回后 的字符串后再从指定的位置开始上传文件。 Fragment相关 1.Fragment 如何实现类似 Activity 栈的压栈和出栈效果的? Fragment 的事物管理器内部维持了一个双向链表结构,该结构可以记录我们每次 add 的 Fragment 和 replace 的 Fragment,然后当我们点击 back 按钮的时候会自动帮我们实现退栈操作。 2.Fragment 在你们项目中的使用 Fragment 是 android3.0 以后引入的的概念,做局部内容更新更方便,原来为了到达这一点要 把多个布局放到一个 activity 里面,现在可以用多 Fragment 来代替,只有在需要的时候才加载 Fragment,提高性能。 Fragment 的好处: 1. Fragment 可以使你能够将 activity 分离成多个可重用的组件,每个都有它自己的生命周期和 UI。 2. Fragment 可以轻松得创建动态灵活的 UI 设计,可以适应于不同的屏幕尺寸。从手机到平板电 脑。 3. Fragment 是一个独立的模块,紧紧地与 activity 绑定在一起。可以运行中动态地移除、加入、 交换等。 4. Fragment 提供一个新的方式让你在不同的安卓设备上统一你的 UI。 5. Fragment 解决 Activity 间的切换不流畅,轻量切换。 6. Fragment 替代 TabActivity 做导航,性能更好。 7. Fragment 在 4.2.版本中新增嵌套 fragment 使用方法,能够生成更好的界面效果 3.如何切换 fragement,不重新实例化 正确的切换方式是 add(),切换时 hide(),add()另一个 Fragment;再次切换时,只需 hide()当前, show()另一个 四大组件相关 1.Activity和Fragment生命周期有哪些? Activity——onCreate->onStart->onResume->onPause->onStop->onDestroy Fragment——onAttach->onCreate->onCreateView->onActivityCreated->onStart->onResume->onPause->onStop->onDestroyView->onDestroy->onDetach 2.广播的两种注册方式及有什么区别 3.内存不足时,怎么保持Activity的一些状态,在哪个方法里面做具体操作? Activity的 onSaveInstanceState() 和 onRestoreInstanceState()并不是生命周期方法,它们不同于 onCreate()、onPause()等生命周期方法,它们并不一定会被触发。当应用遇到意外情况(如:内存不足、用户直接按Home键)由系统销毁一个Activity,onSaveInstanceState() 会被调用。但是当用户主动去销毁一个Activity时,例如在应用中按返回键,onSaveInstanceState()就不会被调用。除非该activity是被用户主动销毁的,通常onSaveInstanceState()只适合用于保存一些临时性的状态,而onPause()适合用于数据的持久化保存。 4.启动service的两种方法?有什么区别? 一种是startService(),另一种是bindService()。这两者的区别是第一种方式调用者开启了服务,即会与服务失去联系,两者没有关联。即使访问者退出了,服务仍在运行。如需解除服务必须显式的调用stopService方法。主要用于调用者与服务没有交互的情况下,也就是调用者不需要获取服务里的业务方法。比如电话录音。而后者调用者与服务绑定在一起的。当调用者退出的时候,服务也随之退出。用于需要与服务交互。 5.Android中的Context, Activity,Appliction有什么区别? 相同:Activity和Application都是Context的子类。 Context从字面上理解就是上下文的意思,在实际应用中它也确实是起到了管理上下文环境中各个参数和变量的总用,方便我们可以简单的访问到各种资源。 不同:维护的生命周期不同。 Context维护的是当前的Activity的生命周期,Application维护的是整个项目的生命周期。 使用context的时候,小心内存泄露,防止内存泄露,注意一下几个方面: 1. 不要让生命周期长的对象引用activity context,即保证引用activity的对象要与activity本身生命周期是一样的。 2. 对于生命周期长的对象,可以使用application,context。 3. 避免非静态的内部类,尽量使用静态类,避免生命周期问题,注意内部类对外部对象引用导致的生命周期变化。 6.Context是什么? 它描述的是一个应用程序环境的信息,即上下文。 该类是一个抽象(abstract class)类,Android提供了该抽象类的具体实现类(ContextIml)。 通过它我们可以获取应用程序的资源和类,也包括一些应用级别操作,例如:启动一个Activity,发送广播,接受Intent,信息,等。 7.Service 是否在 main thread 中执行, service 里面是否能执行耗时的操 作? 默认情况,如果没有显示的指 servic 所运行的进程, Service 和 activity 是运行在当前 app 所在进 程的 main thread(UI 主线程)里面。 service 里面不能执行耗时的操作(网络请求,拷贝数据库,大文件 ) 特殊情况 ,可以在清单文件配置 service 执行所在的进程 ,让 service 在另外的进程中执行 1 2 3 4 5 <service android:name= "com.baidu.location.f" android:enabled= "true" android:process= ":remote" > </service> 8.Activity 怎么和 Service 绑定,怎么在 Activity 中启动自己对应的 Service? Activity 通过 bindService(Intent service, ServiceConnection conn, int flags)跟 Service 进行 绑定,当绑定成功的时候 Service 会将代理对象通过回调的形式传给 conn,这样我们就拿到了 Service 提供的服务代理对象。 在 Activity 中可以通过 startService 和 bindService 方法启动 Service。一般情况下如果想获取 Service 的服务对象那么肯定需要通过 bindService()方法,比如音乐播放器,第三方支付等。如 果仅仅只是为了开启一个后台任务那么可以使用 startService()方法。 9.说说 Activity、Intent、Service 是什么关系 他们都是 Android 开发中使用频率最高的类。其中 Activity 和 Service 都是 Android 四大组件 之一。他俩都是 Context 类的子类 ContextWrapper 的子类,因此他俩可以算是兄弟关系吧。不过 兄弟俩各有各自的本领,Activity 负责用户界面的显示和交互,Service 负责后台任务的处理。Activity 和 Service 之间可以通过 Intent 传递数据,因此可以把 Intent 看作是通信使者。 10.请描述一下 BroadcastReceiver BroadCastReceiver 是 Android 四大组件之一,主要用于接收系统或者 app 发送的广播事件。 广播分两种:有序广播和无序广播。 内部通信实现机制:通过 Android 系统的 Binder 机制实现通信。 1. 无序广播:完全异步,逻辑上可以被任何广播接收者接收到。优点是效率较高。缺点是一个接收者不 能将处理结果传递给下一个接收者,并无法终止广播 intent 的传播。 2. 有序广播:按照被接收者的优先级顺序,在被接收者中依次传播。比如有三个广播接收者 A,B,C, 优先级是 A > B > C。那这个消息先传给 A,再传给 B,最后传给 C。每个接收者有权终止广播,比 如 B 终止广播,C 就无法接收到。此外 A 接收到广播后可以对结果对象进行操作,当广播传给 B 时, B 可以从结果对象中取得 A 存入的数据。 在通过 Context.sendOrderedBroadcast(intent, receiverPermission, resultReceiver, scheduler, initialCode, initialData, initialExtras)时我们可以指定 resultReceiver 广播接收者,这个接收者我们 可以认为是最终接收者,通常情况下如果比他优先级更高的接收者如果没有终止广播,那么他的 onReceive 会被执行两次,第一次是正常的按照优先级顺序执行,第二次是作为最终接收者接收。 如果比他优先级高的接收者终止了广播,那么他依然能接收到广播 11.为什么要用 ContentProvider?它和 sql 的实现上有什么差别? ContentProvider 屏蔽了数据存储的细节,内部实现对用户完全透明,用户只需要关心操作数据的 uri 就可以了,ContentProvider 可以实现不同 app 之间共享。 Sql 也有增删改查的方法,但是 sql 只能查询本应用下的数据库。而 ContentProvider 还可 以去增删改查本地文件. xml 文件的读取等。 12.说说 ContentProvider、ContentResolver、ContentObserver 之间的关系 a. ContentProvider 内容提供者,用于对外提供数据 b. ContentResolver.notifyChange(uri)发出消息 c. ContentResolver 内容解析者,用于获取内容提供者提供的数据 d. ContentObserver 内容监听器,可以监听数据的改变状态 e. ContentResolver.registerContentObserver()监听消息。 View 相关 1.onInterceptTouchEvent()和onTouchEvent()的区别 onInterceptTouchEvent()用于拦截触摸事件 onTouchEvent()用于处理触摸事件 2.RemoteView在哪些功能中使用 APPwidget和Notification中 3. SurfaceView和View的区别是什么? SurfaceView中采用了双缓存技术,在单独的线程中更新界面 View在UI线程中更新界面 4.View的绘制过程 一个View要显示在界面上,需要经历一个View树的遍历过程,这个过程又可以分为三个过程,也就是自定义View中的三要素:大小,位置,画什么,即onMesure(),onLayout(),onDraw()。 1.onMesure()确定一个View的大小; 2.onLayout()确定View在父节点上的位置; 3.onDraw()绘制View 的内容; 5.如何自定义ViewGroup 1.指定的LayoutParams 2.onMeasure中计算所有childView的宽和高,然后根据childView的宽和高,计算自己的宽和高。(当然,如果不是wrap_content,直接使用父ViewGroup传入的计算值即可) 3.onLayout中对所有的childView进行布局。 6.View中onTouch,onTouchEvent,onClick的执行顺序 dispatchTouchEvent—->onTouch—->onTouchEvent—–>onClick。在所有ACTION_UP事件之后才触发onClick点击事件。 性能优化相关 1.ListView卡顿的原因与性能优化,越多越好 重用converView: 通过复用converview来减少不必要的view的创建,另外Infalte操作会把xml文件实例化成相应的View实例,属于IO操作,是耗时操作。 减少findViewById()操作: 将xml文件中的元素封装成viewholder静态类,通过converview的setTag和getTag方法将view与相应的holder对象绑定在一起,避免不必要的findviewbyid操作 避免在 getView 方法中做耗时的操作: 例如加载本地 Image 需要载入内存以及解析 Bitmap ,都是比较耗时的操作,如果用户快速滑动listview,会因为getview逻辑过于复杂耗时而造成滑动卡顿现象。用户滑动时候不要加载图片,待滑动完成再加载,可以使用这个第三方库glide Item的布局层次结构尽量简单,避免布局太深或者不必要的重绘 尽量能保证 Adapter 的 hasStableIds() 返回 true 这样在 notifyDataSetChanged() 的时候,如果item内容并没有变化,ListView 将不会重新绘制这个 View,达到优化的目的 在一些场景中,ScollView内会包含多个ListView,可以把listview的高度写死固定下来。 由于ScollView在快速滑动过程中需要大量计算每一个listview的高度,阻塞了UI线程导致卡顿现象出现,如果我们每一个item的高度都是均匀的,可以通过计算把listview的高度确定下来,避免卡顿现象出现 使用 RecycleView 代替listview: 每个item内容的变动,listview都需要去调用notifyDataSetChanged来更新全部的item,太浪费性能了。RecycleView可以实现当个item的局部刷新,并且引入了增加和删除的动态效果,在性能上和定制上都有很大的改善 ListView 中元素避免半透明: 半透明绘制需要大量乘法计算,在滑动时不停重绘会造成大量的计算,在比较差的机子上会比较卡。 在设计上能不半透明就不不半透明。实在要弄就把在滑动的时候把半透明设置成不透明,滑动完再重新设置成半透明。 尽量开启硬件加速: 硬件加速提升巨大,避免使用一些不支持的函数导致含泪关闭某个地方的硬件加速。当然这一条不只是对 ListView。 2.如何避免 OOM 问题的出现 使用更加轻量的数据结构 例如,我们可以考虑使用ArrayMap/SparseArray而不是HashMap等传统数据结构。通常的HashMap的实现方式更加消耗内存,因为它需要一个额外的实例对象来记录Mapping操作。另外,SparseArray更加高效,在于他们避免了对key与value的自动装箱(autoboxing),并且避免了装箱后的解箱。 避免在Android里面使用Enum Android官方培训课程提到过“Enums often require more than twice as much memory as static constants. You should strictly avoid using enums on Android.”,具体原理请参考《Android性能优化典范(三)》,所以请避免在Android里面使用到枚举。 减小Bitmap对象的内存占用 Bitmap是一个极容易消耗内存的大胖子,减小创建出来的Bitmap的内存占用可谓是重中之重,,通常来说有以下2个措施: ++inSampleSize++:缩放比例,在把图片载入内存之前,我们需要先计算出一个合适的缩放比例,避免不必要的大图载入。 ++decode format++:解码格式,选择ARGB_6666/RBG_545/ARGB_4444/ALPHA_6,存在很大差异 Bitmap对象的复用 缩小Bitmap的同时,也需要提高BitMap对象的复用率,避免频繁创建BitMap对象,复用的方法有以下2个措施 LRUCache : “最近最少使用算法”在Android中有极其普遍的应用。ListView与GridView等显示大量图片的控件里,就是使用LRU的机制来缓存处理好的Bitmap,把近期最少使用的数据从缓存中移除,保留使用最频繁的数据, inBitMap高级特性:利用inBitmap的高级特性提高Android系统在Bitmap分配与释放执行效率。使用inBitmap属性可以告知Bitmap解码器去尝试使用已经存在的内存区域,新解码的Bitmap会尝试去使用之前那张Bitmap在Heap中所占据的pixel data内存区域,而不是去问内存重新申请一块区域来存放Bitmap。利用这种特性,即使是上千张的图片,也只会仅仅只需要占用屏幕所能够显示的图片数量的内存大小 使用更小的图片 在涉及给到资源图片时,我们需要特别留意这张图片是否存在可以压缩的空间,是否可以使用更小的图片。尽量使用更小的图片不仅可以减少内存的使用,还能避免出现大量的InflationException。假设有一张很大的图片被XML文件直接引用,很有可能在初始化视图时会因为内存不足而发生InflationException,这个问题的根本原因其实是发生了OOM。 StringBuilder 在有些时候,代码中会需要使用到大量的字符串拼接的操作,这种时候有必要考虑使用StringBuilder来替代频繁的“+”。 避免在onDraw方法里面执行对象的创建 类似onDraw等频繁调用的方法,一定需要注意避免在这里做创建对象的操作,因为他会迅速增加内存的使用,而且很容易引起频繁的gc,甚至是内存抖动。 避免对象的内存泄露 3.三级缓存的原理 从缓存中加载。 从本地文件中加载(数据库,SD) 从网络加载。 a.加载 bitmap 的时候无需考虑 bitmap 加载过程中出现的 oom(内存溢出)和 android 容器快速 滑动的时候出现的图片错位等现象。(16M) b. 支持加载网络图片和本地图片。 c. 内存管理使用的 lru 算法(移除里面是有频率最少的对象),更好的管理 bitmap 的内存 Android其他 1.讲一下android中进程的优先级? 前台进程 可见进程 服务进程 后台进程 空进程 2.介绍Handle的机制 Handler通过调用sendmessage方法把消息放在消息队列MessageQueue中,Looper负责把消息从消息队列中取出来,重新再交给Handler进行处理,三者形成一个循环 通过构建一个消息队列,把所有的Message进行统一的管理,当Message不用了,并不作为垃圾回收,而是放入消息队列中,供下次handler创建消息时候使用,提高了消息对象的复用,减少系统垃圾回收的次数 每一个线程,都会单独对应的一个looper,这个looper通过ThreadLocal来创建,保证每个线程只创建一个looper,looper初始化后就会调用looper.loop创建一个MessageQueue,这个方法在UI线程初始化的时候就会完成,我们不需要手动创建 3.Dalvik虚拟机与JVM有什么区别 Dalvik 基于寄存器,而 JVM 基于栈。基于寄存器的虚拟机对于更大的程序来说,在它们编译的时候,花费的时间更短。 Dalvik执行.dex格式的字节码,而JVM执行.class格式的字节码。 4.每个应用程序对应多少个Dalvik虚拟机 每一个Android应用在底层都会对应一个独立的Dalvik虚拟机实例,其代码在虚拟机的解释下得以执行 ,而所有的Android应用的线程都对应一个Linux线程 5.应用常驻后台,避免被第三方杀掉的方法 Service设置成START_STICKY kill 后会被重启(等待5秒左右),重传Intent,保持与重启前一样 通过 startForeground将进程设置为前台进程, 做前台服务,优先级和前台应用一个级别,除非在系统内存非常缺,否则此进程不会被 kill 双进程Service: 让2个进程互相保护对方,其中一个Service被清理后,另外没被清理的进程可以立即重启进程 用C编写守护进程(即子进程) : Android系统中当前进程(Process)fork出来的子进程,被系统认为是两个不同的进程。当父进程被杀死的时候,子进程仍然可以存活,并不受影响(Android5.0以上的版本不可行 联系厂商,加入白名单 6.根据自己的理解描述下Android数字签名。 所有的应用程序都必须有数字证书,Android系统不会安装一个没有数字证书的应用程序 Android程序包使用的数字证书可以是自签名的,不需要一个权威的数字证书机构签名认证 如果要正式发布一个Android程序,必须使用一个合适的私钥生成的数字证书来给程序签名,而不能使用adt插件或者ant工具生成的调试证书来发布。 数字证书都是有有效期的,Android只是在应用程序安装的时候才会检查证书的有效期。如果程序已经安装在系统中,即使证书过期也不会影响程序的正常功能。 7.Dalvik基于JVM的改进 几个class变为一个dex,constant pool,省内存 Zygote,copy-on-write shared,省内存,省cpu,省电 基于寄存器的bytecode,省指令,省cpu,省电 Trace-based JIT,省cpu,省电,省内存 8.ARGB_8888占用内存大小 本题的答案,是4byte,即ARGB各占用8个比特来描述。 9.apk安装卸载的原理 安装过程:复制apk安装包到data/app目录下,解压并扫描安装包,把dex文件(dalvik字节码)保存到dalvik-cache目录,并data/data目录下创建对应的应用数据目录。 卸载过程:删除安装过程中在上述三个目录下创建的文件及目录。 10.通过Intent传递一些二进制数据的方法有哪些? 使用Serializable接口实现序列化,这是Java常用的方法。 实现Parcelable接口,这里Android的部分类比如Bitmap类就已经实现了,同时Parcelable在Android AIDL中交换数据也很常见的。 11.横竖屏切换时Activity的生命周期 此时的生命周期跟清单文件里的配置有关系。 不设置Activity的android:configChanges时,切屏会重新调用各个生命周期默认首先销毁当前activity,然后重新加载。 设置Activity android:configChanges=”orientation|keyboardHidden|screenSize”时,切屏不会重新调用各个生命周期,只会执行onConfigurationChanged方法 12.Serializable 和 Parcelable 的区别 在使用内存的时候,Parcelable 类比 Serializable 性能高,所以推荐使用 Parcelable 类。 1. Serializable 在序列化的时候会产生大量的临时变量,从而引起频繁的 GC。 2. Parcelable 不能使用在要将数据存储在磁盘上的情况。尽管 Serializable 效率低点,但在这 种情况下,还是建议你用 Serializable 。 13.Android 中如何捕获未捕获的异常 自 定 义 一 个 Application , 比 如 叫 MyApplication 继 承 Application 实 现 UncaughtExceptionHandler。 覆写 UncaughtExceptionHandler 的 onCreate 和 uncaughtException 方法。 14.Android 的权限规则 Android 中的 apk 必须签名 基于 UserID 的进程级别的安全机制 默认 apk 生成的数据对外是不可见的 AndroidManifest.xml 中的显式权限声明 15.多线程间通信和多进程之间通信有什么不同,分别怎么实现? 一、进程间的通信方式 1. 管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的 进程间使用。进程的亲缘关系通常是指父子进程关系。 2. 有名管道 (namedpipe) : 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的 通信。 3. 信号量(semophore ) : 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它 常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进 程间以及同一进程内不同线程之间的同步手段。 4. 消息队列( messagequeue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符 标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。 5. 信号 (sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。 6. 共享内存(shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内 存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间 通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间 的同步和通信。 7. 套接字(socket ) : 套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同 及其间的进程通信。 二、线程间的通信方式 1. 锁机制:包括互斥锁、条件变量、读写锁 *互斥锁提供了以排他方式防止数据结构被并发修改的方法。 *读写锁允许多个线程同时读共享数据,而对写操作是互斥的。 *条件变量可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件的测试是在互斥锁 的保护下进行的。条件变量始终与互斥锁一起使用。 2. 信号量机制(Semaphore):包括无名线程信号量和命名线程信号量 3. 信号机制(Signal):类似进程间的信号处理 线程间的通信目的主要是用于线程同步,所以线程没有像进程通信中的用于数据交换的通信机 制。 16.说说 LruCache 底层原理 LruCache 使用一个 LinkedHashMap 简单的实现内存的缓存,没有软引用,都是强引用。如果添 加的数据大于设置的最大值,就删除最先缓存的数据来调整内存。 maxSize 是通过构造方法初始化的值,他表示这个缓存能缓存的最大值是多少。 size 在添加和移除缓存都被更新值,他通过 safeSizeOf 这个方法更新值。safeSizeOf 默认返回 1, 但一般我们会根据 maxSize 重写这个方法,比如认为 maxSize 代表是 KB 的话,那么就以 KB 为单 位返回该项所占的内存大小。 除异常外首先会判断 size 是否超过 maxSize,如果超过了就取出最先插入的缓存,如果不为空就 删掉,并把 size 减去该项所占的大小。这个操作将一直循环下去,直到 size 比 maxSize 小或者缓存 为空。 本文转自 一点点征服 博客园博客,原文链接:http://www.cnblogs.com/ldq2016/p/7513718.html,如需转载请自行联系原作者

优秀的个人博客,低调大师

初级Java面试

Java基本数据类型(8种) 整型:byte、short、int、long 对应字节(8 16 32 64) 浮点型:float、double 对应字节(32 64) Boolean型:boolean 对应字节(1) 字符型:char 对应字节(64) string、stringBuffer、stringBuilder string 若连接后得到的字符串在静态存储区中早已存在,那么使用“+”优于stringBuilder string能被继承吗? 不能,因为string被final修饰,string为常量,定义后不能修改 stringBuffer与stringBuilder的区别 stringBuffer(字符串常量)---线程安全 stringBuilder(字符串常量)---线程非安全 Array和ArrayList的区别 Array可以容纳基本类型和对象,而ArrayList只能容纳对象 Array是指定大小的,ArrayList是大小固定的 Array没有提供ArrayList那么多功能 适用与Array的场景 如果列表的大小已经指定,大部分情况是存储和遍历它们 对于遍历基本数据类型,尽管Collection使用自动装箱来减轻编码任务,在指定大小的基本类型的列表上工作也会变得很慢 ArrayList和LinkedList的区别:(使用迭代进行遍历) ArrayList和LinkedList均实现了List接口 ArrayList:实现了所有可选操作列表,包括null 特点: 查询快、增删慢(快:集合的特点,内存地址是连续的,只要知道一个,后面的都可以得到 慢:删除或增加后,该索引后的所有数据进行copy,所以较慢) LinkedList:链表数据结构 特点:查询慢,增删快(快:只是修改记录的前一个元素记录的内存地址发生改变 慢:挨个查询进行比较) LinkedList比ArrayList更占内存,因为LinkedList为每一个节点存储两个引用 类的实例化 类:指描述一种事物的定义,是一个抽象的概念,是一些事物具有相同的特性的集合 实例:该种事物的一个具体的个体,是具体的东西 例:”动物“是一个类,”猫“和”狗“就是实例 实例化的顺序 父类静态变量>父类静态代码块>子类静态变量>子类静态代码块>父类非静态变量(实例成员)>父类构造函数>子类非静态变量(子类实力成员变量)>子类构造函数 构造函数 是一种特殊的方法,主要用来创建对象时初始化对象,即为对象成员变量赋值 构造函数的重载 特别的类有多个构造函数,使用参数个数不同或或参数的类型进行区分 特点:函数名与类名完全相同,无返回值,无修饰符(包括void)不能被直接调用,须new Map类(key--value型) HashMap 依赖于哈希表实现,存储位置根据键的哈希码计算的 TreeMap 使用红黑树(二叉树),根据键的数据进行排序存储的(默认升序) 区别 HashMap是线程非安全的,TreeMap是线程安全的 抽象类和接口的区别 抽象类和接口都不能直接实例化,如果要实例化,抽象类变量必须指向实现所有抽象方法的子类对象,接口变量必须指向实现所有接口方法的类对象 抽象类要被子类继承,接口要被类实现 接口只能做方法申明,抽象类可以做方法申明,也可以做方法实现 接口是定义的变量,只能是公共的静态的常量,抽象类中的变量是普通变量 抽象类与抽象接口中的所有方法,必须被子类(实现类)全部实现,否则子类也为抽象类,接口 抽象方法只能申明,不能实现 抽象类中可以没有抽象方法 如果一个类中有抽象方法,那么这个类只能是抽象类 抽象方法要被实现,所以不能是静态的,也不能是私有的 接口可以继承接口,并可以多继承,但类只能单继承 equals与==的区别 ==是判断两个变量或实例是不是指向同一个内存空间 equals是判断两个变量或实例所指向的内存空间的值是不是相同 Object有哪些公用方法 方法equals测试的是两个对象是否相等 方法clone进行对象拷贝 方法getClass返回和当前对象相关的class对象 方法notify、notifall、wait都是用来对给定对象进行线程同步的 Override与Overload的含义机区别 Overload顾名思义是重新加载 它可以表现类的多重性,在函数里面可以有相同的函数名但是参数名、返回值、类型不能相同、或者说可以改变参数、类型、返回值但是函数名不变 Overrider是重写 在子类继承父类的时候子类中可以定义某方法与其父类有相同的名称和参数,当子类在调用这一个函数时自动调用子类的方法,而父类相当于被覆盖了 多态 抽象的讲:多态就是同一消息可以根据发送对象的不同而采用多种不同的行为方式(发送消息即为函数调用) 实现的原理是动态绑定,程序调用的方法在运行期才动态绑定,追朔源码可以发现,jvm通过参数的自动转型来找到公用的方法 面向对象是什么 面型对象是一种思想,世间万物都可以看作是一个对象,这里是讨论面向对象编程(oop),java是一个支持并发,基于类和面向对象的计算机编程语言 面向对象软件开发的优点 代码开发模块化 代码复用性强 增强代码的可靠性和灵活性 增加代码的可读性 面向对象的四大基本特性 抽象 提取现实世界中某事物的关键特性,为该事物构建模型的过程 封装 可以使类具有独立性和隔离性,保证类的高内聚,只暴露给外部或子类必须的属性和方法 继承 对现有类的一种复用机制 多态 多态是在继承的基础上实现的,三要素(1. 继承 2. 重写 3. 父类引用指向子类对象)父类引用指向不同的子类对象时,调用相同的方法,呈现出不同的行为,就是类的多态性,多态可分为编译时、运行时抽象、封装、继承和多态是面向对象的基础 面向对象的七大设计原则 SOLID原则(单一原则、开闭原则、里氏替换原则、接口隔离原则和依赖倒置原则) 迪莱特原则 组合伏于继承原则(合成复用原则) 什么是值传递和引用传递 值传递是对基本型变量而言的,传递的是该变量的一个副本,改变副本不影响变量本身 引用传递是对于对象型变量而言的,传递的是该对象的一个引用,因此外部对象对引用对象所做的改变会反映到多有的对象上一般认为Java内的传递都是值传递,Java实例对象的传递是引用传递 面向对象与面向过程的区别 面向对象 把构成问题事物分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描述事物在整个解决问题的步骤中的行为 面向过程 分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了面向对象是以功能划分,面向过程是以步骤划分 Java的优点 简单 只提供基本方法,减少了编程的复杂性 高效 可以用面向对象的方法描述用户的每一个动作 面向对象 平台无关性 交互式特性 面向对象的网络编程语言,支持TCP/IP协议,使得交互式更为容易 多线程机制 Java能够并行处理多项任务 动态的内存管理机制 自动垃圾回收管理机制进行内存管理 安全性 Java是解释型的(jvm) Java创建对象的几种形式 new 最常见的一种方法 调用对象的clone()方法 运用反射手段,调用Java.lang.class或者Java.lang.reflect.Constructor类的newInstance()实例方法 运用反序列化手段,调用Java.io.ObjectInputStream对象的readObject()方法 1和3都会明显的显示调用函数,2是在内存上对已有对象的影印,所以不会调用构造函数,4是从文件中还原类的对象也不会调用构造函数 是否可以从一个静态的(static)方法内部发出对非静态(non-static)方法的调用 不可以,静态方法只能访问静态成员,因为非静态方法的调用要先创建对象,因此在调用静态方法时可能对象并没有初始化 静态变量和实例变量的区别 静态变量 被static修饰符修饰的变量,也称为类变量,不属于类的任何一个对象,一个类不管创建多少个对象,静态变量在内存中有且仅有一个拷贝 实例变量 必须依赖存于某一实例,需要先创建对象然后通过对象才能访问到它,静态变量可以实现让多个对象共享内存,在Java开发中,上下文类和工具中通常会有大量的静态成员 如何实现对象克隆 两种方式 实现cloneable接口并重写Object类中的clone()方法 实现Serializable接口通过对象的序列化和反序列化实现克隆,可以实现真正的clone 构造函数 构造函数用于初始化对象的状态,与方法类似,构造函数还包含在创建对象时执行的语句集合(即指令) 每次使用new关键字创建对象时,都会调用至少一个构造函数,用以将初始值分配给同一类的数据成员,(构造函数在对象或实例创建时被调用) 构造函数的编写规则 类的构造函数必须与它所在的类名具有相同的名称 Java中的构造函数不能是抽象的(abstract)、最终的(final)、静态的(static)的同步的(synchronized) 访问修饰符可以用在构造函数申明中来控制它的访问即哪个其他类可以调用构造函数 Class.forName(String className)这个方法的作用 通过类的全名获得该类的类对象 AOP、OOP、IOC、DI AOP(面向切面编程) 将通用需求功能从不相关的类中分离出来,同时能够使得很多类共享一个行为,一旦行为发生变化,不必修改很多类,只要修改行为即可 OOP(面向对象编程) 是一种计算机编程架构 IOC(控制反转) 控制指的是程序相关类之间的依赖关系,传统观念设计授内阁通常由调用者来创建被调用着的实例,在spring里,创建被调用者的工作不再由调用者来完成,而是由spring容器完成,依赖关系被反转了,成为控制反转,目的是为了获得更好的扩展性和良好的可维护性 DI(依赖注入) 创建被调用者的工作由spring容器完成,然后注入调用者,因此也称依赖注入,控制反转和依赖注入是同一个概念 Java中final关键字有哪些用法 修饰类 表示该类不能被继承 修饰方法 表示方法不能被重写 修饰变量 表示变量只能一次赋值以后值不能被修改(常量) 什么时候用assert assert(断言)常用的调试方式,保证程序最基本、关键的正确性,在软件发布后,assertion检查通常是关闭的 final,finally,finalize的区别 final 修饰符(修饰类、修饰方法、修饰变量) finally 通常放在try……catch后,只要jvm不关闭都执行,内一般写释放外部资源的代码 finalize Object中定义的方法,Java中允许使用finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作,这个方法是由垃圾收集器在销毁对象时调用的,通过重写finalize()方法可以整理系统资源或执行其他清理工作

优秀的个人博客,低调大师

Redis面试总结

什么是redis? Redis是一个基于内存的高性能key-value数据库。 (有空再补充,有理解错误或不足欢迎指正) Reids的特点 Redis本质上是一个Key-Value类型的内存数据库,很像memcached,整个数据库统统加载在内存当中进行操作,定期通过异步操作把数据库数据flush到硬盘上进行保存。 因为是纯内存操作,Redis的性能非常出色,每秒可以处理超过 10万次读写操作,是已知性能最快的Key-Value DB。 Redis的出色之处不仅仅是性能,Redis最大的魅力是支持保存多种数据结构,此外单个value的最大限制是1GB,不像 memcached只能保存1MB的数据,因此Redis可以用来实现很多有用的功能,比方说用他的List来做FIFO双向链表,实现一个轻量级的高性 能消息队列服务,用他的Set可以做高性能的tag系统等等。 另外Redis也可以对存入的Key-Value设置expire时间,因此也可以被当作一 个功能加强版的memcached来用。 Redis的主要缺点是数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,因此Redis适合的场景主要局限在较小数据量的高性能操作和运算上。 Redis支持的数据类型 Redis通过Key-Value的单值不同类型来区分, 以下是支持的类型: Strings Lists Sets 求交集、并集 Sorted Set hashes 为什么redis需要把所有数据放到内存中? Redis为了达到最快的读写速度将数据都读到内存中,并通过异步的方式将数据写入磁盘。所以redis具有快速和数据持久化的特征。 如果不将数据放在内存中,磁盘I/O速度为严重影响redis的性能。在内存越来越便宜的今天,redis将会越来越受欢迎。 如果设置了最大使用的内存,则数据已有记录数达到内存限值后不能继续插入新值。 Redis是单进程单线程的 redis利用队列技术将并发访问变为串行访问,消除了传统数据库串行控制的开销 虚拟内存 当你的key很小而value很大时,使用VM的效果会比较好.因为这样节约的内存比较大. 当你的key不小时,可以考虑使用一些非常方法将很大的key变成很大的value,比如你可以考虑将key,value组合成一个新的value. vm-max-threads这个参数,可以设置访问swap文件的线程数,设置最好不要超过机器的核数,如果设置为0,那么所有对swap文件的操作都是串行的. 可能会造成比较长时间的延迟,但是对数据完整性有很好的保证. 自己测试的时候发现用虚拟内存性能也不错。如果数据量很大,可以考虑分布式或者其他数据库 分布式 redis支持主从的模式。原则:Master会将数据同步到slave,而slave不会将数据同步到master。Slave启动时会连接master来同步数据。 这是一个典型的分布式读写分离模型。我们可以利用master来插入数据,slave提供检索服务。这样可以有效减少单个机器的并发访问数量。 读写分离模型 通过增加Slave DB的数量,读的性能可以线性增长。为了避免Master DB的单点故障,集群一般都会采用两台Master DB做双机热备,所以整个集群的读和写的可用性都非常高。 读写分离架构的缺陷在于,不管是Master还是Slave,每个节点都必须保存完整的数据,如果在数据量很大的情况下,集群的扩展能力还是受限于单个节点的存储能力,而且对于Write-intensive类型的应用,读写分离架构并不适合。 数据分片模型 为了解决读写分离模型的缺陷,可以将数据分片模型应用进来。 可以将每个节点看成都是独立的master,然后通过业务实现数据分片。 结合上面两种模型,可以将每个master设计成由一个master和多个slave组成的模型。 Redis的回收策略 volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰 volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰 volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰 allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰 allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰 no-enviction(驱逐):禁止驱逐数据 1. 使用Redis有哪些好处? 速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1) 支持丰富数据类型,支持string,list,set,sorted set,hash 支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除 2. redis相比memcached有哪些优势? memcached所有的值均是简单的字符串,redis作为其替代者,支持更为丰富的数据类型 redis的速度比memcached快很多 redis可以持久化其数据 3. redis常见性能问题和解决方案: Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件 如果数据比较重要,某个Slave开启AOF备份数据,策略设置为每秒同步一次 为了主从复制的速度和连接的稳定性,Master和Slave最好在同一个局域网内 尽量避免在压力很大的主库上增加从库 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3... 这样的结构方便解决单点故障问题,实现Slave对Master的替换。如果Master挂了,可以立刻启用Slave1做Master,其他不变。 4. MySQL里有2000w数据,redis中只存20w的数据,如何保证redis中的数据都是热点数据 相关知识:redis 内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略。 redis 提供 6种数据淘汰策略,上文已经列出。 5. Memcache与Redis的区别都有哪些? 1)、存储方式 Memecache把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小。 Redis有部份存在硬盘上,这样能保证数据的持久性。 2)、数据支持类型 Memcache对数据类型支持相对简单。 Redis有复杂的数据类型。 3)、使用底层模型不同 它们之间底层实现方式 以及与客户端之间通信的应用协议不一样。 Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。 4)、value大小 redis最大可以达到1GB,而memcache只有1MB 6. Redis 常见的性能问题都有哪些?如何解决? 1).Master写内存快照,save命令调度rdbSave函数,会阻塞主线程的工作,当快照比较大时对性能影响是非常大的,会间断性暂停服务,所以Master最好不要写内存快照。 2).Master AOF持久化,如果不重写AOF文件,这个持久化方式对性能的影响是最小的,但是AOF文件会不断增大,AOF文件过大会影响Master重启的恢复速度。 Master最好不要做任何持久化工作,包括内存快照和AOF日志文件,特别是不要启用内存快照做持久化,如果数据比较关键,某个Slave开启AOF备份数据,策略为每秒同步一次。 3).Master调用BGREWRITEAOF重写AOF文件,AOF在重写的时候会占大量的CPU和内存资源,导致服务load过高,出现短暂服务暂停现象。 4). Redis主从复制的性能问题,为了主从复制的速度和连接的稳定性,Slave和Master最好在同一个局域网内 7,redis 最适合的场景 Redis最适合所有数据in-momory的场景,虽然Redis也提供持久化功能,但实际更多的是一个disk-backed的功能,跟传统意义上的持久化有比较大的差别。 那么可能大家就会有疑问,似乎Redis更像一个加强版的Memcached,那么何时使用Memcached,何时使用Redis呢? 如果简单地比较Redis与Memcached的区别,大多数都会得到以下观点: Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,zset,hash等数据结构的存储。 Redis支持数据的备份,即master-slave模式的数据备份。 Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用。 (1)、会话缓存(Session Cache) 最常用的一种使用Redis的情景是会话缓存(session cache)。用Redis缓存会话比其他存储(如Memcached)的优势在于:Redis提供持久化。当维护一个不是严格要求一致性的缓存时,如果用户的购物车信息全部丢失,大部分人都会不高兴的,现在,他们还会这样吗? 幸运的是,随着 Redis 这些年的改进,很容易找到怎么恰当的使用Redis来缓存会话的文档。甚至广为人知的商业平台Magento也提供Redis的插件。 (2)、全页缓存(FPC) 除基本的会话token之外,Redis还提供很简便的FPC平台。回到一致性问题,即使重启了Redis实例,因为有磁盘的持久化,用户也不会看到页面加载速度的下降,这是一个极大改进,类似PHP本地FPC。 再次以Magento为例,Magento提供一个插件来使用Redis作为全页缓存后端。 此外,对WordPress的用户来说,Pantheon有一个非常好的插件 wp-redis,这个插件能帮助你以最快速度加载你曾浏览过的页面。 (3)、队列 Reids在内存存储引擎领域的一大优点是提供 list 和 set 操作,这使得Redis能作为一个很好的消息队列平台来使用。Redis作为队列使用的操作,就类似于本地程序语言(如Python)对 list 的 push/pop 操作。 如果你快速的在Google中搜索“Redis queues”,你马上就能找到大量的开源项目,这些项目的目的就是利用Redis创建非常好的后端工具,以满足各种队列需求。例如,Celery有一个后台就是使用Redis作为broker,你可以从这里去查看。 (4),排行榜/计数器 Redis在内存中对数字进行递增或递减的操作实现的非常好。集合(Set)和有序集合(Sorted Set)也使得我们在执行这些操作的时候变的非常简单,Redis只是正好提供了这两种数据结构。所以,我们要从排序集合中获取到排名最靠前的10个用户–我们称之为“user_scores”,我们只需要像下面一样执行即可: 当然,这是假定你是根据你用户的分数做递增的排序。如果你想返回用户及用户的分数,你需要这样执行: ZRANGE user_scores 0 10 WITHSCORES Agora Games就是一个很好的例子,用Ruby实现的,它的排行榜就是使用Redis来存储数据的,你可以在这里看到。 (5)、发布/订阅 最后(但肯定不是最不重要的)是Redis的发布/订阅功能。发布/订阅的使用场景确实非常多。我已看见人们在社交网络连接中使用,还可作为基于发布/订阅的脚本触发器,甚至用Redis的发布/订阅功能来建立聊天系统!(不,这是真的,你可以去核实)。 Redis提供的所有特性中,我感觉这个是喜欢的人最少的一个,虽然它为用户提供如果此多功能。 欢迎工作一到五年的Java工程师朋友们加入Java架构开发:860113481 群内提供免费的Java架构学习资料(里面有高可用、高并发、高性能及分布式、Jvm性能调优、Spring源码,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多个知识点的架构资料)合理利用自己每一分每一秒的时间来学习提升自己,不要再用"没有时间“来掩饰自己思想上的懒惰!趁年轻,使劲拼,给未来的自己一个交代!

优秀的个人博客,低调大师

java面试宝典

面向对象编程(OOP) Java是一个支持并发、基于类和面向对象的计算机编程语言。下面列出了面向对象软件开发的优点: 代码开发模块化,更易维护和修改。 代码复用。 增强代码的可靠性和灵活性。 增加代码的可理解性。 面向对象编程有很多重要的特性,比如:封装,继承,多态和抽象。下面的章节我们会逐个分析这些特性。 封装 封装给对象提供了隐藏内部特性和行为的能力。对象提供一些能被其他对象访问的方法来改变它内部的数据。在Java当中,有4种修饰符:public,private、protected和默认。每一种修饰符给其他的位于同一个包或者不同包下面对象赋予了不同的访问权限。 下面列出了使用封装的一些好处: 通过隐藏对象的属性来保护对象内部的状态。 提高了代码的可用性和可维护性,因为对象的行为可以被单独的改变或者是扩展。 禁止对象之间的不良交互提高模块化。 参考这个文档获取更多关于封装的细节和示例。 多态 多态是编程语言给不同的底层数据类型做相同的接口展示的一种能力。一个多态类型上的操作可以应用到其他类型的值上面。 继承 继承给对象提供了从基类获取字段和方法的能力。继承提供了代码的重用行,也可以在不修改类的情况下给现存的类添加新特性。 抽象 抽象是把想法从具体的实例中分离出来的步骤,因此,要根据他们的功能而不是实现细节来创建类。Java支持创建只暴漏接口而不包含方法实现的抽象的类。这种抽象技术的主要目的是把类的行为和实现细节分离开。 抽象和封装的不同点 抽象和封装是互补的概念。一方面,抽象关注对象的行为。另一方面,封装关注对象行为的细节。一般是通过隐藏对象内部状态信息做到封装,因此,封装可以看成是用来提供抽象的一种策略。 常见的Java问题 什么是Java虚拟机?为什么Java被称作是“平台无关的编程语言”? Java虚拟机是一个可以执行Java字节码的虚拟机进程。Java源文件被编译成能被Java虚拟机执行的字节码文件。 Java被设计成允许应用程序可以运行在任意的平台,而不需要程序员为每一个平台单独重写或者是重新编译。Java虚拟机让这个变为可能,因为它知道底层硬件平台的指令长度和其他特性。 JDK和JRE的区别是什么? Java运行时环境(JRE)是将要执行Java程序的Java虚拟机。它同时也包含了执行applet需要的浏览器插件。Java开发工具包(JDK)是完整的Java软件开发包,包含了JRE,编译器和其他的工具(比如:JavaDoc,Java调试器),可以让开发者开发、编译、执行Java应用程序。 ”static”关键字是什么意思?Java中是否可以覆盖(override)一个private或者是static的方法? “static”关键字表明一个成员变量或者是成员方法可以在没有所属的类的实例变量的情况下被访问。 Java中static方法不能被覆盖,因为方法覆盖是基于运行时动态绑定的,而static方法是编译时静态绑定的。static方法跟类的任何实例都不相关,所以概念上不适用。 是否可以在static环境中访问非static变量? static变量在Java中是属于类的,它在所有的实例中的值是一样的。当类被Java虚拟机载入的时候,会对static变量进行初始化。如果你的代码尝试不用实例来访问非static的变量,编译器会报错,因为这些变量还没有被创建出来,还没有跟任何实例关联上。 Java支持的数据类型有哪些?什么是自动拆装箱? Java语言支持的8中基本数据类型是: byte short int long float double boolean char 自动装箱是Java编译器在基本数据类型和对应的对象包装类型之间做的一个转化。比如:把int转化成Integer,double转化成double,等等。反之就是自动拆箱。 Java中的方法覆盖(Overriding)和方法重载(Overloading)是什么意思? Java中的方法重载发生在同一个类里面两个或者是多个方法的方法名相同但是参数不同的情况。与此相对,方法覆盖是说子类重新定义了父类的方法。方法覆盖必须有相同的方法名,参数列表和返回类型。覆盖者可能不会限制它所覆盖的方法的访问。 Java中,什么是构造函数?什么是构造函数重载?什么是复制构造函数? 当新对象被创建的时候,构造函数会被调用。每一个类都有构造函数。在程序员没有给类提供构造函数的情况下,Java编译器会为这个类创建一个默认的构造函数。 Java中构造函数重载和方法重载很相似。可以为一个类创建多个构造函数。每一个构造函数必须有它自己唯一的参数列表。 Java不支持像C++中那样的复制构造函数,这个不同点是因为如果你不自己写构造函数的情况下,Java不会创建默认的复制构造函数。 Java支持多继承么? 不支持,Java不支持多继承。每个类都只能继承一个类,但是可以实现多个接口。 接口和抽象类的区别是什么? Java提供和支持创建抽象类和接口。它们的实现有共同点,不同点在于: 接口中所有的方法隐含的都是抽象的。而抽象类则可以同时包含抽象和非抽象的方法。 类可以实现很多个接口,但是只能继承一个抽象类 类如果要实现一个接口,它必须要实现接口声明的所有方法。但是,类可以不实现抽象类声明的所有方法,当然,在这种情况下,类也必须得声明成是抽象的。 抽象类可以在不提供接口方法实现的情况下实现接口。 Java接口中声明的变量默认都是final的。抽象类可以包含非final的变量。 Java接口中的成员函数默认是public的。抽象类的成员函数可以是private,protected或者是public。 接口是绝对抽象的,不可以被实例化。抽象类也不可以被实例化,但是,如果它包含main方法的话是可以被调用的。 也可以参考JDK8中抽象类和接口的区别 什么是值传递和引用传递? 对象被值传递,意味着传递了对象的一个副本。因此,就算是改变了对象副本,也不会影响源对象的值。 对象被引用传递,意味着传递的并不是实际的对象,而是对象的引用。因此,外部对引用对象所做的改变会反映到所有的对象上。 Java线程 进程和线程的区别是什么? 进程是执行着的应用程序,而线程是进程内部的一个执行序列。一个进程可以有多个线程。线程又叫做轻量级进程。 创建线程有几种不同的方式?你喜欢哪一种?为什么? 有三种方式可以用来创建线程: 继承Thread类 实现Runnable接口 应用程序可以使用Executor框架来创建线程池 实现Runnable接口这种方式更受欢迎,因为这不需要继承Thread类。在应用设计中已经继承了别的对象的情况下,这需要多继承(而Java不支持多继承),只能实现接口。同时,线程池也是非常高效的,很容易实现和使用。 概括的解释下线程的几种可用状态。 线程在执行过程中,可以处于下面几种状态: 就绪(Runnable):线程准备运行,不一定立马就能开始执行。 运行中(Running):进程正在执行线程的代码。 等待中(Waiting):线程处于阻塞的状态,等待外部的处理结束。 睡眠中(Sleeping):线程被强制睡眠。 I/O阻塞(Blocked on I/O):等待I/O操作完成。 同步阻塞(Blocked on Synchronization):等待获取锁。 死亡(Dead):线程完成了执行。 同步方法和同步代码块的区别是什么? 在Java语言中,每一个对象有一把锁。线程可以使用synchronized关键字来获取对象上的锁。synchronized关键字可应用在方法级别(粗粒度锁)或者是代码块级别(细粒度锁)。 在监视器(Monitor)内部,是如何做线程同步的?程序应该做哪种级别的同步? 监视器和锁在Java虚拟机中是一块使用的。监视器监视一块同步代码块,确保一次只有一个线程执行同步代码块。每一个监视器都和一个对象引用相关联。线程在获取锁之前不允许执行同步代码。 什么是死锁(deadlock)? 两个进程都在等待对方执行完毕才能继续往下执行的时候就发生了死锁。结果就是两个进程都陷入了无限的等待中。 如何确保N个线程可以访问N个资源同时又不导致死锁? 使用多线程的时候,一种非常简单的避免死锁的方式就是:指定获取锁的顺序,并强制线程按照指定的顺序获取锁。因此,如果所有的线程都是以同样的顺序加锁和释放锁,就不会出现死锁了。 Java集合类 Java集合类框架的基本接口有哪些? Java集合类提供了一套设计良好的支持对一组对象进行操作的接口和类。Java集合类里面最基本的接口有: Collection:代表一组对象,每一个对象都是它的子元素。 Set:不包含重复元素的Collection。 List:有顺序的collection,并且可以包含重复元素。 Map:可以把键(key)映射到值(value)的对象,键不能重复。 为什么集合类没有实现Cloneable和Serializable接口? 集合类接口指定了一组叫做元素的对象。集合类接口的每一种具体的实现类都可以选择以它自己的方式对元素进行保存和排序。有的集合类允许重复的键,有些不允许。 什么是迭代器(Iterator)? Iterator接口提供了很多对集合元素进行迭代的方法。每一个集合类都包含了可以返回迭代器实例的 迭代方法。迭代器可以在迭代的过程中删除底层集合的元素。 克隆(cloning)或者是序列化(serialization)的语义和含义是跟具体的实现相关的。因此,应该由集合类的具体实现来决定如何被克隆或者是序列化。 Iterator和ListIterator的区别是什么? 下面列出了他们的区别: Iterator可用来遍历Set和List集合,但是ListIterator只能用来遍历List。 Iterator对集合只能是前向遍历,ListIterator既可以前向也可以后向。 ListIterator实现了Iterator接口,并包含其他的功能,比如:增加元素,替换元素,获取前一个和后一个元素的索引,等等。 快速失败(fail-fast)和安全失败(fail-safe)的区别是什么? Iterator的安全失败是基于对底层集合做拷贝,因此,它不受源集合上修改的影响。java.util包下面的所有的集合类都是快速失败的,而java.util.concurrent包下面的所有的类都是安全失败的。快速失败的迭代器会抛出ConcurrentModificationException异常,而安全失败的迭代器永远不会抛出这样的异常。 Java中的HashMap的工作原理是什么? Java中的HashMap是以键值对(key-value)的形式存储元素的。HashMap需要一个hash函数,它使用hashCode()和equals()方法来向集合/从集合添加和检索元素。当调用put()方法的时候,HashMap会计算key的hash值,然后把键值对存储在集合中合适的索引上。如果key已经存在了,value会被更新成新值。HashMap的一些重要的特性是它的容量(capacity),负载因子(load factor)和扩容极限(threshold resizing)。 hashCode()和equals()方法的重要性体现在什么地方? Java中的HashMap使用hashCode()和equals()方法来确定键值对的索引,当根据键获取值的时候也会用到这两个方法。如果没有正确的实现这两个方法,两个不同的键可能会有相同的hash值,因此,可能会被集合认为是相等的。而且,这两个方法也用来发现重复元素。所以这两个方法的实现对HashMap的精确性和正确性是至关重要的。 HashMap和Hashtable有什么区别? HashMap和Hashtable都实现了Map接口,因此很多特性非常相似。但是,他们有以下不同点: HashMap允许键和值是null,而Hashtable不允许键或者值是null。 Hashtable是同步的,而HashMap不是。因此,HashMap更适合于单线程环境,而Hashtable适合于多线 程环境。 HashMap提供了可供应用迭代的键的集合,因此,HashMap是快速失败的。另一方面,Hashtable提供了对键的列举(Enumeration)。 一般认为Hashtable是一个遗留的类。 数组(Array)和列表(ArrayList)有什么区别?什么时候应该使用Array而不是ArrayList? 下面列出了Array和ArrayList的不同点: Array可以包含基本类型和对象类型,ArrayList只能包含对象类型。 Array大小是固定的,ArrayList的大小是动态变化的。 ArrayList提供了更多的方法和特性,比如:addAll(),removeAll(),iterator()等等。 对于基本类型数据,集合使用自动装箱来减少编码工作量。但是,当处理固定大小的基本数据类型的时候,这种方式相对比较慢。 ArrayList和LinkedList有什么区别? ArrayList和LinkedList都实现了List接口,他们有以下的不同点: ArrayList是基于索引的数据接口,它的底层是数组。它可以以O(1)时间复杂度对元素进行随机访问。与此对应,LinkedList是以元素列表的形式存储它的数据,每一个元素都和它的前一个和后一个元素链接在一起,在这种情况下,查找某个元素的时间复杂度是O(n)。 相对于ArrayList,LinkedList的插入,添加,删除操作速度更快,因为当元素被添加到集合任意位置的时候,不需要像数组那样重新计算大小或者是更新索引。 LinkedList比ArrayList更占内存,因为LinkedList为每一个节点存储了两个引用,一个指向前一个元素,一个指向下一个元素。 也可以参考ArrayList vs. LinkedList。 Comparable和Comparator接口是干什么的?列出它们的区别。 Java提供了只包含一个compareTo()方法的Comparable接口。这个方法可以个给两个对象排序。具体来说,它返回负数,0,正数来表明输入对象小于,等于,大于已经存在的对象。 Java提供了包含compare()和equals()两个方法的Comparator接口。compare()方法用来给两个输入参数排序,返回负数,0,正数表明第一个参数是小于,等于,大于第二个参数。equals()方法需要一个对象作为参数,它用来决定输入参数是否和comparator相等。只有当输入参数也是一个comparator并且输入参数和当前comparator的排序结果是相同的时候,这个方法才返回true。 什么是Java优先级队列(Priority Queue)? PriorityQueue是一个基于优先级堆的无界队列,它的元素是按照自然顺序(natural order)排序的。在创建的时候,我们可以给它提供一个负责给元素排序的比较器。PriorityQueue不允许null值,因为他们没有自然顺序,或者说他们没有任何的相关联的比较器。最后,PriorityQueue不是线程安全的,入队和出队的时间复杂度是O(log(n))。 你了解大O符号(big-O notation)么?你能给出不同数据结构的例子么? 大O符号描述了当数据结构里面的元素增加的时候,算法的规模或者是性能在最坏的场景下有多么好。 大O符号也可用来描述其他的行为,比如:内存消耗。因为集合类实际上是数据结构,我们一般使用大O符号基于时间,内存和性能来选择最好的实现。大O符号可以对大量数据的性能给出一个很好的说明。 如何权衡是使用无序的数组还是有序的数组? 有序数组最大的好处在于查找的时间复杂度是O(log n),而无序数组是O(n)。有序数组的缺点是插入操作的时间复杂度是O(n),因为值大的元素需要往后移动来给新元素腾位置。相反,无序数组的插入时间复杂度是常量O(1)。 Java集合类框架的最佳实践有哪些? 根据应用的需要正确选择要使用的集合的类型对性能非常重要,比如:假如元素的大小是固定的,而且能事先知道,我们就应该用Array而不是ArrayList。 有些集合类允许指定初始容量。因此,如果我们能估计出存储的元素的数目,我们可以设置初始容量来避免重新计算hash值或者是扩容。 为了类型安全,可读性和健壮性的原因总是要使用泛型。同时,使用泛型还可以避免运行时的ClassCastException。 使用JDK提供的不变类(immutable class)作为Map的键可以避免为我们自己的类实现hashCode()和equals()方法。 编程的时候接口优于实现。 底层的集合实际上是空的情况下,返回长度是0的集合或者是数组,不要返回null。 Enumeration接口和Iterator接口的区别有哪些? Enumeration速度是Iterator的2倍,同时占用更少的内存。但是,Iterator远远比Enumeration安全,因为其他线程不能够修改正在被iterator遍历的集合里面的对象。同时,Iterator允许调用者删除底层集合里面的元素,这对Enumeration来说是不可能的。 HashSet和TreeSet有什么区别? HashSet是由一个hash表来实现的,因此,它的元素是无序的。add(),remove(),contains()方法的时间复杂度是O(1)。 另一方面,TreeSet是由一个树形的结构来实现的,它里面的元素是有序的。因此,add(),remove(),contains()方法的时间复杂度是O(logn)。 垃圾收集器(Garbage Collectors) Java中垃圾回收有什么目的?什么时候进行垃圾回收? 垃圾回收的目的是识别并且丢弃应用不再使用的对象来释放和重用资源。 System.gc()和Runtime.gc()会做什么事情? 这两个方法用来提示JVM要进行垃圾回收。但是,立即开始还是延迟进行垃圾回收是取决于JVM的。 finalize()方法什么时候被调用?析构函数(finalization)的目的是什么? 在释放对象占用的内存之前,垃圾收集器会调用对象的finalize()方法。一般建议在该方法中释放对象持有的资源。 如果对象的引用被置为null,垃圾收集器是否会立即释放对象占用的内存? 不会,在下一个垃圾回收周期中,这个对象将是可被回收的。 Java堆的结构是什么样子的?什么是堆中的永久代(Perm Gen space)? JVM的堆是运行时数据区,所有类的实例和数组都是在堆上分配内存。它在JVM启动的时候被创建。对象所占的堆内存是由自动内存管理系统也就是垃圾收集器回收。 堆内存是由存活和死亡的对象组成的。存活的对象是应用可以访问的,不会被垃圾回收。死亡的对象是应用不可访问尚且还没有被垃圾收集器回收掉的对象。一直到垃圾收集器把这些对象回收掉之前,他们会一直占据堆内存空间。 串行(serial)收集器和吞吐量(throughput)收集器的区别是什么? 吞吐量收集器使用并行版本的新生代垃圾收集器,它用于中等规模和大规模数据的应用程序。而串行收集器对大多数的小应用(在现代处理器上需要大概100M左右的内存)就足够了。 在Java中,对象什么时候可以被垃圾回收? 当对象对当前使用这个对象的应用程序变得不可触及的时候,这个对象就可以被回收了。 JVM的永久代中会发生垃圾回收么? 垃圾回收不会发生在永久代,如果永久代满了或者是超过了临界值,会触发完全垃圾回收(Full GC)。如果你仔细查看垃圾收集器的输出信息,就会发现永久代也是被回收的。这就是为什么正确的永久代大小对避免Full GC是非常重要的原因。请参考下Java8:从永久代到元数据区

优秀的个人博客,低调大师

面试

1.事务的特性 原子性 事务中的全部操作在数据库中是不分隔的,要么全部完成,要么均不执行 隔离型 事务的执行不受其他事务的干扰,事务 执行的中间结果对其他事务必须是透明的。 持久性 对于任意已提交事务,系统必须保证该 事务对数据库的改变不被丢失,即使数据库出现故障 一致性 几个并行执行的事务,其执行结果必 须与按某一顺序串行执行的结果相一致。 2.redis 和mysql的区别 redis是内存数据库,数据保存在内存中,速度快 mysql是关系型数据库,持久化存储,存放在磁盘里面,功能强大,检索的话,会涉及到一定的IO 3.Django 重定向 使用HttpResponseRedirect redirect 和 reverse 状态码:302,301 说一下Django,MIDDLEWARES 中间件的作用? 答:中间件是介于request 与 response 处理之间的一道处理过程,相对比较轻量级,并且在全局上改变django 5.简述uwsgi与Nginx配置 a)nginx 具备优秀的静态内容处理能力, , 然后将动态内容转发给 uWSGI 服务器, , 这样可以达到很好的客户端响应 6.迭代器和生成器区别? 答::迭代器是一个更抽象的概念,有任何对如果它类有next 方法和iter方法返回自己本身 对于 strings、 、list、 、dict、 、tuple用 等这类容器对象,使用for台 循环遍历是很方便的。在后台for语句用对容器象调用 iter()函数,iter()是 是python的内置函数 。iter()会返回一个定义next()方法的迭代器对象,它在 容器 中逐个访问容器内元素,next()也是python的内置函数。在没有后续元素时next()会抛出一个StopIter 异常 ((2 2( )生成器( Generator)是创建迭代器的简单而强大工具 。 用它们写起来就像是正规的函数,只在需要返回据时候使用d yield 语 次句。每次next()被调用,生成器会返回它脱离的位置 , 记忆语句最 后一次执行和所有数据。 事区别:生成器能做到迭代的所有事, ,而且因为自动创建了 __iter__()和 和 next()法 方法, ,洁 生成器显得特别简洁, ,而且生成器也是 的高效的表,使用生成器表省达式取代列解析可以同时节省内存。除了 创建和保程序状态的自动方法, ,时 当发生器终结时, ,还会自动抛出 n StopIteration 异常。 本文转自 xxl714 51CTO博客,原文链接:http://blog.51cto.com/dreamgirl1314/1980472,如需转载请自行联系原作者

优秀的个人博客,低调大师

IOS面试宝典

Object-C有多继承吗?没有的话用什么代替? cocoa 中所有的类都是NSObject 的子类,多继承在这里是用protocol 委托代理来实现的 你不用去考虑繁琐的多继承 ,虚基类的概念.ood的多态特性 在 obj-c 中通过委托来实现. Object-C有私有方法吗?私有变量呢? objective-c– 类里面的方法只有两种, 静态方法和实例方法. 这似乎就不是完整的面向对象了,按照OO的原则就是一个对象只暴露有用的东西. 如果没有了私有方法的话, 对于一些小范围的代码重用就不那么顺手了. 在类里面声名一个私有方法 @interface Controller : NSObject { NSString *something; } + (void)thisIsAStaticMethod; – (void)thisIsAnInstanceMethod; @end @interface Controller (private) - (void)thisIsAPrivateMethod; @end @private可以用来修饰私有变量 在Objective‐C中,所有实例变量默认都是私有的,所有实例方法默认都是公有的 关键字const什么含义 const意味着”只读”,下面的声明都是什么意思? const int a; int const a; const int *a; int * const a; int const * a const; 前两个的作用是一样,a是一个常整型数。第三个意味着a是一个指向常整型数的指针(也就是,整型数是不可修改的,但指针可以)。第四个意思a是一个指向整型数的常指针(也就是说,指针指向的整型数是可以修改的,但指针是不可修改的)。最后一个意味着a是一个指向常整型数的常指针(也就是说,指针指向的整型数是不可修改的,同时指针也是不可修改的)。 结论: 本文转自蓬莱仙羽51CTO博客,原文链接:http://blog.51cto.com/dingxiaowei/1366597,如需转载请自行联系原作者

资源下载

更多资源
优质分享App

优质分享App

近一个月的开发和优化,本站点的第一个app全新上线。该app采用极致压缩,本体才4.36MB。系统里面做了大量数据访问、缓存优化。方便用户在手机上查看文章。后续会推出HarmonyOS的适配版本。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。