Java学习路线 - 第三阶段笔记

Java学习路线 - 第三阶段笔记

Java高级特性(2-3个月)

1. 集合框架深入

1.1 List详解
  • ArrayList:基于动态数组实现,随机访问高效,插入删除效率低
  • LinkedList:基于双向链表实现,插入删除高效,随机访问效率低
  • Vector:线程安全的ArrayList,性能较低
  • Stack:基于Vector实现的LIFO(后进先出)栈
1.2 Set详解
  • HashSet:基于HashMap实现,无序,不允许重复元素,查找效率高
  • LinkedHashSet:在HashSet基础上维护了元素插入顺序
  • TreeSet:基于红黑树实现,元素自动排序,查找和遍历效率较高
1.3 Map详解
1.3.1 HashMap详解
  • 基本概念:基于哈希表实现的Map接口,存储键值对,允许null键和null值

  • 核心特性

    • 无序集合
    • 非线程安全
    • 查询/插入/删除的平均时间复杂度为O(1)
    • 默认初始容量16,负载因子0.75
  • 底层数据结构

    • JDK 1.7:数组 + 链表
    • JDK 1.8及以后:数组 + 链表 + 红黑树(当链表长度超过8时转为红黑树)
  • 工作原理

    1. 通过key的hashCode()方法计算hash值
    2. 将hash值与数组长度-1进行按位与操作确定数组下标
    3. 如发生hash冲突,使用链表法解决
    4. 当链表过长(>8)时,转换为红黑树提高查询效率
    5. 当负载因子超过阈值时(默认0.75),进行扩容(2倍)
  • 重要源码解析

    // 哈希值计算
    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
    
    // 获取桶位置
    int i = (n - 1) & hash  // n为数组长度
    
  • 扩容机制

    • 触发条件:当前元素数量 > 容量 × 负载因子
    • 扩容过程:创建2倍大小的新数组,重新计算每个元素的位置
    • 性能影响:扩容是耗时操作,合理设置初始容量可减少扩容次数
  • 常见面试问题

    1. HashMap的put方法流程
    2. HashMap如何处理哈希冲突
    3. HashMap和Hashtable的区别
    4. 为什么HashMap的容量总是2的n次幂
  • 代码示例

    // 创建HashMap
    HashMap<String, Integer> map = new HashMap<>();
    
    // 指定初始容量和负载因子
    HashMap<String, Integer> customMap = new HashMap<>(32, 0.8f);
    
    // 添加元素
    map.put("Java", 95);
    map.put("Python", 90);
    map.put("C++", 85);
    
    // 获取元素
    int javaScore = map.get("Java");  // 95
    
    // 检查键是否存在
    boolean hasJava = map.containsKey("Java");  // true
    boolean has90 = map.containsValue(90);  // true
    
    // 遍历HashMap - 方式1:通过EntrySet
    for (Map.Entry<String, Integer> entry : map.entrySet()) {
        System.out.println(entry.getKey() + ": " + entry.getValue());
    }
    
    // 遍历HashMap - 方式2:通过KeySet
    for (String key : map.keySet()) {
        System.out.println(key + ": " + map.get(key));
    }
    
    // 遍历HashMap - 方式3:Java 8 Lambda表达式
    map.forEach((k, v) -> System.out.println(k + ": " + v));
    
    // 删除元素
    map.remove("Python");
    
    // 替换元素
    map.replace("C++", 88);
    
    // 清空HashMap
    map.clear();
    
  • 性能优化

    • 合理设置初始容量,减少扩容次数
    • 重写hashCode()和equals()方法保证正确性
    • 使用不可变对象作为key
    • 使用缓存计算的hashCode值提高性能
  • LinkedHashMap:在HashMap基础上维护了键值对的插入顺序

  • TreeMap:基于红黑树实现,键自动排序

  • Hashtable:线程安全的HashMap,性能较低

  • Properties:用于处理属性配置文件的特殊Map

1.4 集合排序和查找
  • Comparable接口:自然排序
  • Comparator接口:定制排序
  • Collections工具类常用方法
    • sort():排序
    • binarySearch():二分查找
    • reverse():反转
    • shuffle():随机排序
    • max()/min():查找最大/最小值
1.5 泛型
  • 泛型类:class MyClass<T> {}
  • 泛型方法:public <T> T method(T t) {}
  • 泛型通配符:
    • <?>: 无限制通配符
    • <? extends T>: 上限通配符
    • <? super T>: 下限通配符
  • 类型擦除:Java泛型实现原理
  • 泛型的限制和最佳实践
1.6 迭代器
  • Iterator接口:hasNext(), next(), remove()
  • ListIterator接口:双向迭代器
  • Iterable接口与for-each循环
  • fail-fast机制:并发修改异常ConcurrentModificationException

2. 多线程编程

2.1 线程基础
  • 线程的生命周期:新建、就绪、运行、阻塞、死亡
  • 创建线程的方式:
    • 继承Thread类
    • 实现Runnable接口
    • 实现Callable接口(有返回值)
  • 线程常用方法:
    • start():启动线程
    • run():线程执行体
    • sleep():休眠
    • join():等待线程结束
    • yield():让出CPU执行权
2.2 线程同步
  • 线程安全问题分析

    • 多线程环境下,共享资源可能被多个线程同时访问和修改
    • 导致的问题:脏读、丢失更新、不可重复读、幻读等
    • 解决方案:互斥(同步)和可见性保证
  • synchronized关键字

    • 同步代码块

      public void method() {
          // 非同步代码
          
          synchronized(锁对象) {  // 锁对象可以是this、类对象或任意对象
              // 同步代码,一次只允许一个线程执行
          }
          
          // 非同步代码
      }
      
    • 同步实例方法

      // 相当于synchronized(this) { 方法体 }
      public synchronized void method() {
          // 同步代码,锁是当前实例对象(this)
      }
      
    • 同步静态方法

      // 相当于synchronized(当前类.class) { 方法体 }
      public static synchronized void method() {
          // 同步代码,锁是当前类的Class对象
      }
      
    • 底层实现原理

      • 基于监视器(Monitor)机制
      • 使用对象头中的Mark Word存储锁信息
      • 锁的升级过程:偏向锁 -> 轻量级锁 -> 重量级锁
    • 代码示例

      public class Counter {
          private int count = 0;
          
          // 同步方法
          public synchronized void increment() {
              count++;
          }
          
          // 同步代码块
          public void decrement() {
              synchronized(this) {
                  count--;
              }
          }
          
          // 使用类锁
          public static synchronized void staticMethod() {
              // 静态同步方法
          }
          
          public void anotherMethod() {
              synchronized(Counter.class) {
                  // 使用类锁的同步代码块
              }
          }
          
          public int getCount() {
              return count;
          }
      }
      
      // 使用示例
      Counter counter = new Counter();
      
      // 线程1
      new Thread(() -> {
          for (int i = 0; i < 1000; i++) {
              counter.increment();
          }
      }).start();
      
      // 线程2
      new Thread(() -> {
          for (int i = 0; i < 1000; i++) {
              counter.decrement();
          }
      }).start();
      
  • volatile关键字

    • 作用

      • 保证变量的可见性:对变量的修改立即对其他线程可见
      • 禁止指令重排序:保证有序性
      • 但不保证原子性:不能替代锁解决竞态条件
    • 适用场景

      • 状态标志(如停止线程的标志)
      • 单例模式中的双重检查锁定
      • 独立于其他变量的操作(无复合操作)
    • 工作原理

      • 读操作:直接从主内存读取,不从CPU缓存读取
      • 写操作:立即写入主内存,并使其他CPU缓存失效
    • 代码示例

      public class VolatileExample {
          // 使用volatile保证可见性
          private volatile boolean flag = false;
          
          public void writer() {
              flag = true;  // 对所有线程立即可见
          }
          
          public void reader() {
              while (!flag) {
                  // 当flag变为true时,会立即看到变化并退出循环
              }
          }
      }
      
      // 双重检查锁定单例模式
      public class Singleton {
          private static volatile Singleton instance;
          
          private Singleton() {}
          
          public static Singleton getInstance() {
              if (instance == null) {  // 第一次检查
                  synchronized (Singleton.class) {
                      if (instance == null) {  // 第二次检查
                          instance = new Singleton();  // volatile保证对象完全初始化
                      }
                  }
              }
              return instance;
          }
      }
      
    • volatile的局限性

      public class VolatileLimitation {
          private volatile int count = 0;
          
          // volatile不保证原子性,下面的操作不是线程安全的
          public void increment() {
              count++;  // 实际是读-修改-写三步操作
          }
          
          // 正确做法: 使用synchronized
          public synchronized void safeIncrement() {
              count++;
          }
          
          // 或者使用原子类
          private AtomicInteger atomicCount = new AtomicInteger(0);
          
          public void atomicIncrement() {
              atomicCount.incrementAndGet();
          }
      }
      
  • Lock接口

    • 基本概念

      • java.util.concurrent.locks包中的显式锁
      • 比synchronized更灵活
      • 支持非阻塞获取锁、可中断获取锁、超时获取锁等
    • 主要实现类

      • ReentrantLock:可重入锁
      • ReentrantReadWriteLock:读写锁
      • StampedLock:Java 8引入的性能更高的锁
    • ReentrantLock使用

      public class LockExample {
          private final Lock lock = new ReentrantLock();
          private int count = 0;
          
          public void increment() {
              lock.lock();  // 获取锁
              try {
                  count++;
              } finally {
                  lock.unlock();  // 在finally块中释放锁
              }
          }
          
          // 可中断获取锁
          public void incrementInterruptibly() throws InterruptedException {
              lock.lockInterruptibly();  // 可被中断的锁获取
              try {
                  count++;
              } finally {
                  lock.unlock();
              }
          }
          
          // 尝试获取锁
          public boolean tryIncrement() {
              if (lock.tryLock()) {  // 非阻塞尝试获取锁
                  try {
                      count++;
                      return true;
                  } finally {
                      lock.unlock();
                  }
              }
              return false;
          }
          
          // 超时获取锁
          public boolean timedIncrement() throws InterruptedException {
              if (lock.tryLock(1, TimeUnit.SECONDS)) {  // 尝试1秒获取锁
                  try {
                      count++;
                      return true;
                  } finally {
                      lock.unlock();
                  }
              }
              return false;
          }
      }
      
    • 公平锁与非公平锁

      // 公平锁:按请求顺序获取锁
      Lock fairLock = new ReentrantLock(true);
      
      // 非公平锁(默认):不保证顺序
      Lock unfairLock = new ReentrantLock(false);
      
    • 读写锁:允许多个线程同时读,但只允许一个线程写

      public class ReadWriteLockExample {
          private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
          private final Lock readLock = rwLock.readLock();
          private final Lock writeLock = rwLock.writeLock();
          private List<String> data = new ArrayList<>();
          
          // 读操作(允许并发)
          public List<String> readData() {
              readLock.lock();
              try {
                  return new ArrayList<>(data);  // 返回副本
              } finally {
                  readLock.unlock();
              }
          }
          
          // 写操作(独占)
          public void writeData(String newValue) {
              writeLock.lock();
              try {
                  data.add(newValue);
              } finally {
                  writeLock.unlock();
              }
          }
          
          // 先读后写的复合操作
          public void updateIfPresent(String oldValue, String newValue) {
              // 先获取写锁(隐含获取读锁)
              writeLock.lock();
              try {
                  int index = data.indexOf(oldValue);
                  if (index != -1) {
                      data.set(index, newValue);
                  }
              } finally {
                  writeLock.unlock();
              }
          }
      }
      
    • StampedLock:Java 8引入的性能更高的锁

      public class StampedLockExample {
          private final StampedLock sl = new StampedLock();
          private double x, y;
          
          // 写操作(独占锁)
          public void move(double deltaX, double deltaY) {
              long stamp = sl.writeLock();  // 获取写锁
              try {
                  x += deltaX;
                  y += deltaY;
              } finally {
                  sl.unlockWrite(stamp);  // 释放写锁
              }
          }
          
          // 读操作(悲观读锁)
          public double distanceFromOrigin() {
              long stamp = sl.readLock();  // 获取读锁
              try {
                  return Math.sqrt(x * x + y * y);
              } finally {
                  sl.unlockRead(stamp);  // 释放读锁
              }
          }
          
          // 乐观读操作
          public double distanceFromOriginOptimistic() {
              long stamp = sl.tryOptimisticRead();  // 获取乐观读锁
              double currentX = x, currentY = y;
              
              // 检查读取后是否有写操作
              if (!sl.validate(stamp)) {
                  // 如果有写操作,切换到悲观读锁
                  stamp = sl.readLock();
                  try {
                      currentX = x;
                      currentY = y;
                  } finally {
                      sl.unlockRead(stamp);
                  }
              }
              
              return Math.sqrt(currentX * currentX + currentY * currentY);
          }
      }
      
  • 死锁问题

    • 死锁形成的四个必要条件

      • 互斥条件:资源不能被共享使用
      • 持有并等待条件:线程持有资源的同时等待其他资源
      • 不可抢占条件:资源不能被强制释放
      • 循环等待条件:形成一个等待环路
    • 死锁示例

      public class DeadlockExample {
          private final Object resource1 = new Object();
          private final Object resource2 = new Object();
          
          // 可能导致死锁的方法
          public void method1() {
              synchronized(resource1) {
                  System.out.println("Thread 1: Holding resource 1...");
                  
                  try { Thread.sleep(100); } 
                  catch (InterruptedException e) {}
                  
                  System.out.println("Thread 1: Waiting for resource 2...");
                  
                  synchronized(resource2) {
                      System.out.println("Thread 1: Holding resource 1 & 2...");
                  }
              }
          }
          
          public void method2() {
              synchronized(resource2) {  // 与method1获取锁的顺序相反
                  System.out.println("Thread 2: Holding resource 2...");
                  
                  try { Thread.sleep(100); } 
                  catch (InterruptedException e) {}
                  
                  System.out.println("Thread 2: Waiting for resource 1...");
                  
                  synchronized(resource1) {
                      System.out.println("Thread 2: Holding resource 1 & 2...");
                  }
              }
          }
          
          // 测试死锁
          public static void main(String[] args) {
              DeadlockExample deadlock = new DeadlockExample();
              
              new Thread(() -> deadlock.method1()).start();
              new Thread(() -> deadlock.method2()).start();
          }
      }
      
    • 死锁预防与避免

      • 固定锁的获取顺序:始终以相同的顺序获取锁
      • 超时获取锁:使用Lock接口的tryLock方法尝试获取锁
      • 使用可中断锁:让线程能够被打断
      • 针对资源分级:为资源分配优先级
    • 修正上面的死锁示例

      // 方法1:固定锁的获取顺序
      public void methodFixed1() {
          synchronized(resource1) {  // 两个方法都先获取resource1
              synchronized(resource2) {
                  // 使用资源
              }
          }
      }
      
      public void methodFixed2() {
          synchronized(resource1) {  // 两个方法都先获取resource1
              synchronized(resource2) {
                  // 使用资源
              }
          }
      }
      
      // 方法2:使用Lock接口的tryLock避免死锁
      public void methodWithTryLock() {
          Lock lock1 = new ReentrantLock();
          Lock lock2 = new ReentrantLock();
          
          boolean gotFirstLock = false;
          boolean gotSecondLock = false;
          
          try {
              gotFirstLock = lock1.tryLock();
              gotSecondLock = lock2.tryLock();
              
              if (gotFirstLock && gotSecondLock) {
                  // 使用资源
              }
          } finally {
              if (gotSecondLock) {
                  lock2.unlock();
              }
              if (gotFirstLock) {
                  lock1.unlock();
              }
          }
      }
      
    • 死锁检测

      • 使用jstack命令查看线程堆栈
      • 使用jconsole或VisualVM等工具监控检测
      • 程序中实现死锁检测算法
  • 线程同步最佳实践

    • 尽量缩小同步范围,只在必要的代码块上使用同步
    • 选择合适的锁:粗粒度锁简单但竞争激烈,细粒度锁减少竞争但复杂
    • 避免在持有锁时执行耗时操作
    • 使用并发容器而非同步容器提高性能
    • 遵循固定的锁获取顺序避免死锁
    • 优先使用并发工具类而非低级别的锁机制
    • 使用ThreadLocal减少共享变量
    • 正确使用volatile关键字
  • 面试常见问题

    1. synchronized和Lock的区别与选择
    2. 如何解决死锁问题
    3. volatile关键字的作用和使用场景
    4. 如何实现线程安全的单例模式
    5. 什么是线程安全,如何实现线程安全
    6. synchronized的底层实现原理和锁升级过程
2.3 线程池
  • 线程池基本概念:用于管理和复用线程的资源池,避免频繁创建和销毁线程带来的开销

  • 线程池优势

    • 减少资源消耗:复用已创建的线程
    • 提高响应速度:任务到达时直接使用线程池中空闲线程
    • 提高线程可管理性:统一管理、分配、调优、监控
    • 防止系统资源耗尽:限制并发线程数量
  • ThreadPoolExecutor核心参数

    public ThreadPoolExecutor(
        int corePoolSize,      // 核心线程数
        int maximumPoolSize,   // 最大线程数
        long keepAliveTime,    // 线程空闲超时时间
        TimeUnit unit,         // 时间单位
        BlockingQueue<Runnable> workQueue,  // 工作队列
        ThreadFactory threadFactory,        // 线程工厂
        RejectedExecutionHandler handler)   // 拒绝策略
    
  • 线程池工作原理

    1. 当线程数小于核心线程数时,新任务到达会创建新线程执行
    2. 当线程数等于核心线程数时,新任务会进入工作队列等待
    3. 当工作队列已满,但线程数小于最大线程数时,创建新线程执行任务
    4. 当工作队列已满,且线程数等于最大线程数时,触发拒绝策略
    5. 当线程空闲时间超过keepAliveTime,非核心线程会被回收
  • 工作队列类型

    • ArrayBlockingQueue:基于数组的有界阻塞队列,FIFO
    • LinkedBlockingQueue:基于链表的可选有界阻塞队列,FIFO
    • SynchronousQueue:不存储元素的阻塞队列,每个插入操作必须等待对应的移除操作
    • PriorityBlockingQueue:带优先级的无界阻塞队列
  • 拒绝策略

    • AbortPolicy:默认策略,丢弃任务并抛出RejectedExecutionException
    • DiscardPolicy:丢弃任务但不抛出异常
    • DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务
    • CallerRunsPolicy:由调用线程处理该任务
  • 常用线程池

    // 固定大小线程池
    ExecutorService fixedPool = Executors.newFixedThreadPool(5);
    
    // 单线程线程池
    ExecutorService singlePool = Executors.newSingleThreadExecutor();
    
    // 缓存线程池(无限制)
    ExecutorService cachedPool = Executors.newCachedThreadPool();
    
    // 定时任务线程池
    ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(5);
    
  • 自定义线程池示例

    ThreadPoolExecutor executor = new ThreadPoolExecutor(
        5,                             // 核心线程数
        10,                            // 最大线程数
        60, TimeUnit.SECONDS,          // 线程空闲超时时间
        new ArrayBlockingQueue<>(100), // 工作队列
        Executors.defaultThreadFactory(),
        new ThreadPoolExecutor.CallerRunsPolicy());
    
    // 提交任务
    executor.execute(() -> {
        System.out.println("Task executed by " + Thread.currentThread().getName());
    });
    
    // 提交有返回值的任务
    Future<String> future = executor.submit(() -> {
        Thread.sleep(1000);
        return "Task completed";
    });
    
    // 等待任务完成并获取结果
    try {
        String result = future.get();
        System.out.println(result);
    } catch (Exception e) {
        e.printStackTrace();
    }
    
    // 关闭线程池
    executor.shutdown();
    
  • 线程池监控

    // 获取线程池状态
    int activeCount = executor.getActiveCount();      // 活动线程数
    int poolSize = executor.getPoolSize();            // 当前线程数
    int corePoolSize = executor.getCorePoolSize();    // 核心线程数
    int largestPoolSize = executor.getLargestPoolSize(); // 历史最大线程数
    long taskCount = executor.getTaskCount();         // 已提交任务数
    long completedTaskCount = executor.getCompletedTaskCount(); // 已完成任务数
    
  • 线程池最佳实践

    • 根据任务类型选择合适的队列
    • 避免使用无界队列,防止OOM
    • 根据CPU核心数、任务性质合理设置线程数
    • 为线程池线程指定有意义的名称,便于调试
    • 任务应该是独立的,避免共享可变状态
    • 线程池关闭前确保所有任务完成
  • 面试常见问题

    1. 线程池的主要参数及其影响
    2. 如何合理设置线程池大小
    3. 线程池中submit()和execute()的区别
    4. 线程池的状态转换(RUNNING, SHUTDOWN, STOP, TIDYING, TERMINATED)
2.4 并发工具类
  • CountDownLatch:等待多个线程完成
  • CyclicBarrier:等待线程集合同步执行
  • Semaphore:信号量,控制并发数
  • Exchanger:线程数据交换
  • 原子类:AtomicInteger, AtomicLong等
  • 并发集合:
    • ConcurrentHashMap
    • CopyOnWriteArrayList
    • BlockingQueue

3. Java8+新特性

3.1 Lambda表达式
  • Lambda基本概念

    • Lambda表达式是Java 8引入的一种匿名函数表达式,可以创建匿名函数实例
    • 主要用于简化函数式接口的实现代码
    • 语法更简洁,提高代码可读性和可维护性
  • Lambda语法

    (parameters) -> expression
    

    (parameters) -> { statements; }
    
  • Lambda表达式示例

    // 无参数,返回值为空
    Runnable r1 = () -> System.out.println("Hello World");
    
    // 单个参数(可省略括号)
    Consumer<String> c1 = s -> System.out.println(s);
    
    // 多个参数
    Comparator<String> c2 = (s1, s2) -> s1.compareTo(s2);
    
    // 带代码块
    ActionListener listener = event -> {
        System.out.println("Button clicked!");
        System.out.println("Event: " + event.getActionCommand());
    };
    
    // 带返回值
    BinaryOperator<Integer> add = (a, b) -> a + b;
    
  • 函数式接口

    • 任何只包含一个抽象方法的接口,都可以作为Lambda表达式的目标类型
    • 可以使用@FunctionalInterface注解标记(非必须但推荐)
    • Java 8在java.util.function包中提供了许多标准函数式接口
  • 常用函数式接口详解

    • Predicate:接收T类型参数,返回boolean值

      // 定义
      @FunctionalInterface
      public interface Predicate<T> {
          boolean test(T t);
          // 还有其他默认方法: and(), or(), negate()
      }
      
      // 使用示例
      Predicate<String> isEmpty = s -> s.isEmpty();
      Predicate<String> isNotEmpty = isEmpty.negate();
      
      // 组合Predicate
      Predicate<String> isLongAndNotEmpty = 
          s -> s.length() > 10      // 长度>10
          .and(isNotEmpty);         // 且不为空
      
      List<String> filtered = strings.stream()
          .filter(isLongAndNotEmpty)
          .collect(Collectors.toList());
      
    • Consumer:接收T类型参数,无返回值

      // 定义
      @FunctionalInterface
      public interface Consumer<T> {
          void accept(T t);
          // 还有默认方法: andThen()
      }
      
      // 使用示例
      Consumer<String> print = System.out::println;
      Consumer<String> log = s -> logger.info(s);
      
      // 组合Consumer
      Consumer<String> printAndLog = print.andThen(log);
      
      strings.forEach(printAndLog);
      
    • Function<T, R>:接收T类型参数,返回R类型结果

      // 定义
      @FunctionalInterface
      public interface Function<T, R> {
          R apply(T t);
          // 还有默认方法: compose(), andThen(), identity()
      }
      
      // 使用示例
      Function<String, Integer> toLength = String::length;
      Function<Integer, Integer> doubled = i -> i * 2;
      
      // 组合Function
      Function<String, Integer> lengthThenDouble = toLength.andThen(doubled);
      
      // 结果:10 (先计算"Java"长度为4,然后乘以2)
      int result = lengthThenDouble.apply("Java");
      
    • Supplier:无参数,返回T类型结果

      // 定义
      @FunctionalInterface
      public interface Supplier<T> {
          T get();
      }
      
      // 使用示例
      Supplier<LocalDate> today = LocalDate::now;
      Supplier<User> createUser = User::new;  // 调用无参构造器
      
      // 用于延迟计算
      Logger logger = LoggerFactory.getLogger(MyClass.class);
      
      // 仅当日志级别是DEBUG时才执行计算
      logger.debug("Complex object: {}", (Supplier<String>) () -> {
          return expensiveOperation();  // 仅在需要时执行
      });
      
    • BiFunction<T, U, R>:接收T和U类型参数,返回R类型结果

      // 定义
      @FunctionalInterface
      public interface BiFunction<T, U, R> {
          R apply(T t, U u);
          // 还有默认方法: andThen()
      }
      
      // 使用示例
      BiFunction<String, String, String> concat = (s1, s2) -> s1 + s2;
      BiFunction<Integer, Integer, Integer> multiply = (a, b) -> a * b;
      
      // 结果:"HelloWorld"
      String result1 = concat.apply("Hello", "World");
      
      // 结果:35
      Integer result2 = multiply.apply(5, 7);
      
    • 其他常用函数式接口

      // BiConsumer<T, U>:接收两个参数,无返回值
      BiConsumer<String, Integer> printKeyValue = 
          (key, value) -> System.out.println(key + ": " + value);
      
      // BinaryOperator<T>:接收两个T类型参数,返回T类型结果
      BinaryOperator<Integer> sum = (a, b) -> a + b;
      
      // UnaryOperator<T>:接收T类型参数,返回T类型结果
      UnaryOperator<String> toUpperCase = String::toUpperCase;
      
  • 方法引用:简化Lambda表达式的特殊语法

    // 静态方法引用
    Function<String, Integer> parseInt = Integer::parseInt;
    
    // 实例方法引用(特定对象)
    Consumer<String> printer = System.out::println;
    
    // 实例方法引用(任意对象)
    Function<String, Integer> lengthFn = String::length;
    
    // 构造方法引用
    Supplier<ArrayList<String>> listCreator = ArrayList::new;
    Function<String, User> userCreator = User::new;
    
  • 变量捕获:Lambda表达式可以访问定义它的方法的局部变量和成员变量

    int multiplier = 2;  // 局部变量
    
    // 捕获局部变量(必须是final或effectively final)
    Function<Integer, Integer> multiply = n -> n * multiplier;
    
    // 修改捕获的变量会导致编译错误
    // multiplier = 3;  // 错误:变量在Lambda中使用,不能修改
    
    // 成员变量可以正常修改
    this.factor = 3;  // 成功:成员变量可以修改
    Function<Integer, Integer> multiplyByFactor = n -> n * this.factor;
    
  • 与匿名内部类的比较

    // 使用匿名内部类
    Comparator<String> comparator1 = new Comparator<String>() {
        @Override
        public int compare(String s1, String s2) {
            return s1.compareTo(s2);
        }
    };
    
    // 使用Lambda表达式
    Comparator<String> comparator2 = (s1, s2) -> s1.compareTo(s2);
    
    // 使用方法引用更简洁
    Comparator<String> comparator3 = String::compareTo;
    
  • 实际应用场景

    1. 事件处理

      // Swing事件处理
      JButton button = new JButton("Click Me");
      
      // 传统方式
      button.addActionListener(new ActionListener() {
          @Override
          public void actionPerformed(ActionEvent e) {
              System.out.println("Button clicked!");
          }
      });
      
      // Lambda方式
      button.addActionListener(e -> System.out.println("Button clicked!"));
      
    2. 集合遍历和操作

      List<String> names = Arrays.asList("John", "Mary", "Steve");
      
      // 传统方式
      for (String name : names) {
          System.out.println(name);
      }
      
      // Lambda方式
      names.forEach(name -> System.out.println(name));
      
      // 方法引用
      names.forEach(System.out::println);
      
    3. 多线程编程

      // 传统方式
      new Thread(new Runnable() {
          @Override
          public void run() {
              System.out.println("Running in another thread");
          }
      }).start();
      
      // Lambda方式
      new Thread(() -> System.out.println("Running in another thread")).start();
      
    4. 策略模式实现

      // 传统策略模式
      public interface ValidationStrategy {
          boolean validate(String s);
      }
      
      public class IsAllLowerCase implements ValidationStrategy {
          @Override
          public boolean validate(String s) {
              return s.matches("[a-z]+");
          }
      }
      
      // 使用传统策略
      Validator lowerCaseValidator = new Validator(new IsAllLowerCase());
      boolean result = lowerCaseValidator.validate("abc");
      
      // 使用Lambda简化
      Validator lowerCaseValidator = new Validator(s -> s.matches("[a-z]+"));
      Validator numericValidator = new Validator(s -> s.matches("\\d+"));
      
    5. 资源管理

      // 自定义函数式接口
      @FunctionalInterface
      interface ResourceHandler<T, R> {
          R handle(T resource) throws IOException;
      }
      
      // 资源管理工具方法
      public static <T extends AutoCloseable, R> R withResource(
              Supplier<T> resourceSupplier, 
              ResourceHandler<T, R> handler) throws Exception {
          try (T resource = resourceSupplier.get()) {
              return handler.handle(resource);
          }
      }
      
      // 使用方式
      String content = withResource(
          () -> new BufferedReader(new FileReader("file.txt")),
          reader -> reader.lines().collect(Collectors.joining("\n"))
      );
      
  • Lambda表达式最佳实践

    • 保持简短和清晰,避免复杂逻辑
    • 优先使用方法引用提高可读性
    • 避免修改外部变量
    • 为复杂Lambda创建描述性方法
    • 使用标准函数式接口而不是创建新接口
    • 使用@FunctionalInterface注解标记函数式接口
  • 常见面试问题

    1. Lambda表达式和匿名内部类的区别
    2. 为什么Lambda表达式只能访问final或effectively final的局部变量
    3. 函数式接口是什么,如何定义自己的函数式接口
    4. Lambda表达式的底层实现原理
    5. Lambda表达式的局限性和适用场景
3.3 Optional类
  • Optional基本概念

    • Optional是Java 8引入的容器类,可以存储一个值或空值
    • 主要目的是避免空指针异常(NullPointerException)
    • 迫使开发者显式地处理可能为null的情况
    • 不是为了替代所有null引用,而是为了更优雅地处理null
  • 创建Optional对象

    // 创建包含值的Optional
    Optional<String> opt1 = Optional.of("Hello");
    
    // 创建可能包含null的Optional
    Optional<String> opt2 = Optional.ofNullable(possiblyNullString);
    
    // 创建空的Optional
    Optional<String> empty = Optional.empty();
    
  • 检查值是否存在

    // 检查是否有值
    boolean isPresent = opt1.isPresent();  // true
    boolean isEmpty = empty.isEmpty();     // true (Java 11+)
    
    // 条件执行
    opt1.ifPresent(s -> System.out.println("Value: " + s));
    
    // 条件执行带else情况(Java 9+)
    opt1.ifPresentOrElse(
        s -> System.out.println("Value: " + s),
        () -> System.out.println("No value")
    );
    
  • 获取Optional中的值

    // 直接获取值(如果为空会抛出NoSuchElementException)
    String value1 = opt1.get();  // 不推荐使用
    
    // 值不存在时返回默认值
    String value2 = empty.orElse("Default");
    
    // 值不存在时通过Supplier计算默认值
    String value3 = empty.orElseGet(() -> computeDefault());
    
    // 值不存在时抛出指定异常
    String value4 = empty.orElseThrow(() -> 
        new IllegalStateException("Value not available"));
    
  • orElse与orElseGet的区别

    // 使用orElse - 无论Optional是否为空,都会执行computeDefault()
    String result1 = opt1.orElse(computeDefault());
    
    // 使用orElseGet - 只有当Optional为空时,才会执行computeDefault()
    String result2 = opt1.orElseGet(() -> computeDefault());
    
  • 转换Optional中的值

    // 使用map转换值
    Optional<Integer> length = opt1.map(String::length);
    
    // 链式调用
    Optional<String> upperCase = opt1
        .map(s -> s.trim())
        .filter(s -> !s.isEmpty())
        .map(String::toUpperCase);
    
    // 使用flatMap处理返回Optional的方法
    Optional<User> user = Optional.ofNullable(getUser(id));
    Optional<Address> address = user.flatMap(User::getAddress);
    Optional<Street> street = address.flatMap(Address::getStreet);
    Optional<String> streetName = street.map(Street::getName);
    
    // 简化上面的嵌套Optional链(避免了多层null检查)
    Optional<String> streetName = Optional.ofNullable(getUser(id))
        .flatMap(User::getAddress)
        .flatMap(Address::getStreet)
        .map(Street::getName);
    
  • 过滤Optional中的值

    // 根据条件过滤
    Optional<String> filteredOpt = opt1
        .filter(s -> s.length() > 3);
    
    // 组合条件
    Optional<User> validUser = Optional.ofNullable(user)
        .filter(u -> u.isActive())
        .filter(u -> u.getAge() >= 18);
    
  • Optional与Stream的结合

    // 将Optional流转换为值流
    List<Optional<String>> listOfOptionals = Arrays.asList(
        Optional.of("A"), Optional.empty(), Optional.of("B"));
        
    // Java 9+: Optional的stream()方法
    List<String> filteredList = listOfOptionals.stream()
        .flatMap(Optional::stream)  // 只保留有值的Optional
        .collect(Collectors.toList());  // ["A", "B"]
    
    // Java 8: 使用filter()和map()
    List<String> filteredList = listOfOptionals.stream()
        .filter(Optional::isPresent)
        .map(Optional::get)
        .collect(Collectors.toList());  // ["A", "B"]
    
  • 实际应用场景

    1. 避免空指针检查

      // 传统的null检查
      public String getUserName(User user) {
          if (user != null) {
              String name = user.getName();
              if (name != null) {
                  return name.toUpperCase();
              }
          }
          return "UNKNOWN";
      }
      
      // 使用Optional
      public String getUserName(User user) {
          return Optional.ofNullable(user)
              .map(User::getName)
              .map(String::toUpperCase)
              .orElse("UNKNOWN");
      }
      
    2. 方法返回值处理

      // 返回Optional而不是null
      public Optional<User> findUserById(long id) {
          User user = userRepository.findById(id);
          return Optional.ofNullable(user);
      }
      
      // 调用方处理
      findUserById(123)
          .ifPresent(user -> sendEmail(user));
      
    3. 集合元素处理

      // 查找第一个匹配元素
      Optional<User> user = users.stream()
          .filter(u -> u.getAge() > 30)
          .findFirst();
      
      // 进一步处理
      user.ifPresent(this::processUser);
      
    4. 配置值获取

      // 从多个可能的来源获取配置
      public Optional<String> getConfigValue(String key) {
          return Optional.ofNullable(System.getProperty(key))
              .or(() -> Optional.ofNullable(environmentVars.get(key)))
              .or(() -> Optional.ofNullable(defaultProps.get(key)));
      }
      
      // 使用配置值
      String timeout = getConfigValue("app.timeout")
          .map(Integer::parseInt)
          .orElse(DEFAULT_TIMEOUT);
      
    5. 构建验证管道

      public Optional<User> validateUser(User user) {
          return Optional.ofNullable(user)
              .filter(u -> u.getName() != null && !u.getName().trim().isEmpty())
              .filter(u -> u.getEmail() != null && u.getEmail().contains("@"))
              .filter(u -> u.getAge() >= 18);
      }
      
      // 使用验证结果
      validateUser(user)
          .ifPresentOrElse(
              userService::registerUser,
              () -> throw new ValidationException("Invalid user data")
          );
      
  • Optional最佳实践

    • 不要将Optional用作字段类型
    • 不要将Optional用作方法参数
    • 不要创建空的Optional对象集合
    • 优先使用orElseGet()而不是orElse()处理计算开销大的默认值
    • 不要过度使用Optional替代所有可能为null的场景
    • 使用Optional作为返回类型表明值可能不存在
  • 常见面试问题

    1. Optional的设计目的是什么
    2. Optional和null的区别
    3. 为什么不应该序列化Optional
    4. orElse()和orElseGet()方法的区别
    5. 如何正确地使用Optional提高代码质量
3.4 新的日期时间API
  • LocalDate:不包含时间的日期
  • LocalTime:不包含日期的时间
  • LocalDateTime:包含日期和时间
  • ZonedDateTime:带时区的日期时间
  • Instant:时间戳
  • Duration:时间段
  • Period:日期间隔
  • DateTimeFormatter:日期时间格式化
3.5 默认方法
  • 接口中的默认方法:default关键字
  • 接口中的静态方法
  • 默认方法冲突解决策略

4. JDBC数据库编程

4.1 数据库基础知识
  • 关系型数据库概念

    • 表(Table):存储数据的基本单元,由行和列组成
    • 主键(Primary Key):唯一标识表中的一行数据
    • 外键(Foreign Key):表示表之间的关系
    • 索引(Index):提高查询性能的数据结构
    • 视图(View):虚拟表,基于一个或多个表的查询结果
  • SQL基础

    • DDL(数据定义语言):CREATE, ALTER, DROP
    • DML(数据操作语言):INSERT, UPDATE, DELETE
    • DQL(数据查询语言):SELECT
    • DCL(数据控制语言):GRANT, REVOKE
  • 数据库设计三范式

    1. 第一范式(1NF):字段值不可再分
    2. 第二范式(2NF):非主键字段必须依赖于整个主键
    3. 第三范式(3NF):非主键字段不能相互依赖
  • 索引与性能优化

    • B+树索引:最常用的索引类型
    • 哈希索引:等值查询更快,但不支持排序和范围查询
    • 索引最佳实践:
      • 为频繁查询的字段创建索引
      • 为外键字段创建索引
      • 避免对频繁更新的字段创建索引
      • 避免创建过多索引
  • 事务ACID特性

    • 原子性(Atomicity):事务是最小执行单位,要么全部执行,要么全部不执行
    • 一致性(Consistency):事务执行前后数据库状态一致
    • 隔离性(Isolation):多个事务并发执行互不干扰
    • 持久性(Durability):事务一旦提交,对数据库的改变是永久的
  • 事务隔离级别

    • READ UNCOMMITTED(读未提交)
    • READ COMMITTED(读已提交)
    • REPEATABLE READ(可重复读)
    • SERIALIZABLE(串行化)
4.2 MySQL安装和基本操作
  • MySQL基本命令
    -- 登录MySQL
    mysql -u username -p
    
    -- 显示数据库
    SHOW DATABASES;
    
    -- 创建数据库
    CREATE DATABASE db_name CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
    
    -- 使用数据库
    USE db_name;
    
    -- 创建表
    CREATE TABLE user (
        id INT PRIMARY KEY AUTO_INCREMENT,
        username VARCHAR(50) NOT NULL UNIQUE,
        password VARCHAR(100) NOT NULL,
        email VARCHAR(100),
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    );
    
    -- 增加数据
    INSERT INTO user (username, password, email) 
    VALUES ('alice', 'password123', 'alice@example.com');
    
    -- 查询数据
    SELECT * FROM user WHERE username = 'alice';
    
    -- 更新数据
    UPDATE user SET email = 'new_email@example.com' WHERE id = 1;
    
    -- 删除数据
    DELETE FROM user WHERE id = 1;
    
    -- 添加索引
    CREATE INDEX idx_username ON user(username);
    
4.3 JDBC连接数据库
  • JDBC核心组件

    • DriverManager:管理JDBC驱动
    • Connection:数据库连接
    • Statement/PreparedStatement/CallableStatement:执行SQL语句
    • ResultSet:查询结果集
  • JDBC驱动加载方式

    // 方式一:Class.forName (推荐)
    Class.forName("com.mysql.cj.jdbc.Driver");
    
    // 方式二:DriverManager.registerDriver
    DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
    
    // 方式三:系统属性
    System.setProperty("jdbc.drivers", "com.mysql.cj.jdbc.Driver");
    
  • 建立数据库连接

    // MySQL连接URL格式
    String url = "jdbc:mysql://localhost:3306/db_name?useSSL=false&serverTimezone=UTC";
    String username = "root";
    String password = "password";
    
    // 获取连接
    Connection connection = DriverManager.getConnection(url, username, password);
    
  • JDBC优化 - 使用连接池

    // HikariCP连接池配置
    HikariConfig config = new HikariConfig();
    config.setJdbcUrl("jdbc:mysql://localhost:3306/db_name");
    config.setUsername("root");
    config.setPassword("password");
    config.setMaximumPoolSize(10);
    config.setMinimumIdle(5);
    config.setIdleTimeout(30000);
    
    // 创建连接池
    HikariDataSource dataSource = new HikariDataSource(config);
    
    // 获取连接
    Connection connection = dataSource.getConnection();
    
  • 常用连接池对比

    连接池优点缺点
    HikariCP速度最快、配置简单、轻量级功能相对简单
    Druid功能丰富、监控统计、SQL防注入配置相对复杂
    C3P0稳定性好、自动恢复能力强性能较低
    DBCPApache官方推荐、配置简单不支持自动恢复连接
4.4 增删改查操作
  • 使用Statement

    try (
        Connection conn = DriverManager.getConnection(url, username, password);
        Statement stmt = conn.createStatement()
    ) {
        // 执行查询
        ResultSet rs = stmt.executeQuery("SELECT * FROM user");
        
        // 处理结果集
        while (rs.next()) {
            int id = rs.getInt("id");
            String name = rs.getString("username");
            System.out.println(id + " - " + name);
        }
        
        // 执行更新(Insert/Update/Delete)
        int rowsAffected = stmt.executeUpdate(
            "UPDATE user SET email = 'new@example.com' WHERE id = 1");
        System.out.println("Updated " + rowsAffected + " rows");
    } catch (SQLException e) {
        e.printStackTrace();
    }
    
  • 使用PreparedStatement(推荐)

    String sql = "SELECT * FROM user WHERE username = ? AND created_at > ?";
    
    try (
        Connection conn = DriverManager.getConnection(url, username, password);
        PreparedStatement pstmt = conn.prepareStatement(sql)
    ) {
        // 设置参数
        pstmt.setString(1, "alice");
        pstmt.setTimestamp(2, Timestamp.valueOf("2022-01-01 00:00:00"));
        
        // 执行查询
        ResultSet rs = pstmt.executeQuery();
        
        // 处理结果
        while (rs.next()) {
            // 处理每一行数据
        }
        
        // 插入示例
        String insertSql = "INSERT INTO user (username, password, email) VALUES (?, ?, ?)";
        try (PreparedStatement insertStmt = conn.prepareStatement(insertSql, 
                                                  Statement.RETURN_GENERATED_KEYS)) {
            insertStmt.setString(1, "bob");
            insertStmt.setString(2, "securepass");
            insertStmt.setString(3, "bob@example.com");
            
            int rows = insertStmt.executeUpdate();
            
            // 获取自动生成的主键
            try (ResultSet generatedKeys = insertStmt.getGeneratedKeys()) {
                if (generatedKeys.next()) {
                    long id = generatedKeys.getLong(1);
                    System.out.println("New user ID: " + id);
                }
            }
        }
    } catch (SQLException e) {
        e.printStackTrace();
    }
    
  • 事务处理

    Connection conn = null;
    try {
        conn = DriverManager.getConnection(url, username, password);
        
        // 关闭自动提交
        conn.setAutoCommit(false);
        
        // 执行多个SQL操作
        try (PreparedStatement stmt1 = conn.prepareStatement("UPDATE accounts SET balance = balance - ? WHERE id = ?");
             PreparedStatement stmt2 = conn.prepareStatement("UPDATE accounts SET balance = balance + ? WHERE id = ?")) {
            
            // 转账:从账户1转100到账户2
            double amount = 100.0;
            
            stmt1.setDouble(1, amount);
            stmt1.setInt(2, 1);
            stmt1.executeUpdate();
            
            // 模拟可能出现的异常
            if (amount > 50) {
                // throw new SQLException("Transaction test exception");
            }
            
            stmt2.setDouble(1, amount);
            stmt2.setInt(2, 2);
            stmt2.executeUpdate();
            
            // 提交事务
            conn.commit();
        }
    } catch (SQLException e) {
        // 发生异常,回滚事务
        if (conn != null) {
            try {
                conn.rollback();
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
        }
        e.printStackTrace();
    } finally {
        // 恢复自动提交
        if (conn != null) {
            try {
                conn.setAutoCommit(true);
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
    
  • 批处理操作

    try (
        Connection conn = DriverManager.getConnection(url, username, password);
        PreparedStatement pstmt = conn.prepareStatement(
            "INSERT INTO user (username, password, email) VALUES (?, ?, ?)")
    ) {
        // 关闭自动提交
        conn.setAutoCommit(false);
        
        // 添加批量操作
        for (int i = 0; i < 1000; i++) {
            pstmt.setString(1, "user" + i);
            pstmt.setString(2, "pass" + i);
            pstmt.setString(3, "user" + i + "@example.com");
            pstmt.addBatch();
            
            // 每500条执行一次
            if (i % 500 == 0) {
                pstmt.executeBatch();
                pstmt.clearBatch();
            }
        }
        
        // 执行剩余的批处理
        pstmt.executeBatch();
        
        // 提交事务
        conn.commit();
    } catch (SQLException e) {
        e.printStackTrace();
    }
    
  • 处理大数据类型

    // 存储BLOB(二进制大对象)
    String insertBlob = "INSERT INTO documents (name, content) VALUES (?, ?)";
    try (
        Connection conn = DriverManager.getConnection(url, username, password);
        PreparedStatement pstmt = conn.prepareStatement(insertBlob)
    ) {
        pstmt.setString(1, "report.pdf");
        
        // 从文件读取二进制数据
        File file = new File("report.pdf");
        try (FileInputStream fis = new FileInputStream(file)) {
            pstmt.setBinaryStream(2, fis, file.length());
            pstmt.executeUpdate();
        }
    }
    
    // 读取BLOB数据
    String selectBlob = "SELECT content FROM documents WHERE id = ?";
    try (
        Connection conn = DriverManager.getConnection(url, username, password);
        PreparedStatement pstmt = conn.prepareStatement(selectBlob)
    ) {
        pstmt.setInt(1, 1);
        ResultSet rs = pstmt.executeQuery();
        
        if (rs.next()) {
            Blob blob = rs.getBlob("content");
            try (InputStream is = blob.getBinaryStream();
                 FileOutputStream fos = new FileOutputStream("downloaded_report.pdf")) {
                
                byte[] buffer = new byte[4096];
                int bytesRead;
                while ((bytesRead = is.read(buffer)) != -1) {
                    fos.write(buffer, 0, bytesRead);
                }
            }
        }
    }
    
  • 数据库连接管理模式

    // 数据访问对象(DAO)模式
    public class UserDAO {
        private final DataSource dataSource;
        
        public UserDAO(DataSource dataSource) {
            this.dataSource = dataSource;
        }
        
        public User findById(long id) throws SQLException {
            String sql = "SELECT * FROM user WHERE id = ?";
            try (
                Connection conn = dataSource.getConnection();
                PreparedStatement pstmt = conn.prepareStatement(sql)
            ) {
                pstmt.setLong(1, id);
                try (ResultSet rs = pstmt.executeQuery()) {
                    if (rs.next()) {
                        User user = new User();
                        user.setId(rs.getLong("id"));
                        user.setUsername(rs.getString("username"));
                        user.setEmail(rs.getString("email"));
                        user.setCreatedAt(rs.getTimestamp("created_at"));
                        return user;
                    }
                    return null;
                }
            }
        }
        
        // 其他CRUD方法...
    }
    
  • JDBC最佳实践

    1. 始终使用PreparedStatement:防止SQL注入攻击
    2. 使用连接池:提高性能和资源管理
    3. 关闭资源:使用try-with-resources确保资源关闭
    4. 批处理操作:处理大量数据时使用批处理
    5. 正确处理异常:捕获具体异常而不是通用Exception
    6. 使用事务:确保数据一致性
    7. 避免大结果集:使用分页或限制结果数量
    8. 缓存预编译语句:频繁执行的SQL考虑缓存
    9. 参数化SQL文件:将SQL语句与Java代码分离
    10. 定期关闭空闲连接:避免连接泄漏
  • 常见面试问题

    1. JDBC中Statement和PreparedStatement的区别
    2. 如何防止SQL注入攻击
    3. 事务的ACID特性与隔离级别
    4. JDBC连接池的工作原理和优势
    5. 批处理操作的性能影响

5. 项目练习

5.1 多线程下载器
  • 需求分析:文件分片与多线程下载
  • 核心功能:
    • HTTP连接管理
    • 线程池实现并发下载
    • 文件分片与合并
    • 断点续传功能
    • 下载进度监控
5.2 基于JDBC的商城管理系统
  • 数据库设计:
    • 用户表
    • 商品表
    • 订单表
    • 购物车表
  • 核心功能:
    • 用户登录注册
    • 商品查询与过滤
    • 购物车管理
    • 订单处理
    • 数据统计报表
5.3 日志分析工具
  • 功能设计:
    • 日志文件读取
    • 多线程解析
    • 使用Stream API处理数据
    • 结果统计与可视化
  • 技术要点:
    • NIO文件处理
    • 正则表达式解析
    • 并发处理
    • 统计算法

学习资源推荐

  • 《Effective Java》第三版 - Joshua Bloch
  • 《Java并发编程实战》- Brian Goetz等
  • 《Java 8实战》- Raoul-Gabriel Urma等
  • 《高性能MySQL》- Baron Schwartz等
  • 中国MOOC的Java进阶课程
  • 尚硅谷Java高级部分视频
  • 黑马程序员Java并发编程视频
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值