spring(continue)

20250426

多线程 / 异步

public class ThreadPoolTest {

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        // 使用ThreadPoolExecutor创建线程池
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                5,
                5,
                60L,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(100),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );
        

        // 1. runAsync: 无返回值   2. supplyAsync:支持返回值
        CompletableFuture<String> cp1 = CompletableFuture.supplyAsync(() -> {
            System.out.println("--- cp1: ---");
            return "hcy_cp1";
        }, executor);
        System.out.println("cp1.get(): " + cp1.get());

        
        // thenRun: 无传参 无返回值
        CompletableFuture<Void> cp2 = cp1.thenRun(() -> {
            System.out.println("--- cp2: ---");
        });
        System.out.println("cp2.get(): " + cp2.get());

        
        // thenAccept:有传参 无返回值
        CompletableFuture<Void> cp3 = cp1.thenAccept((a) -> {
            try {
                System.out.println("--- cp3: ---");
                System.out.println("thenAccept 接收cp1的输入为:" + cp1.get());
            } catch (InterruptedException | ExecutionException e) {
                throw new RuntimeException(e);
            }
        });
        System.out.println("cp3.get(): " + cp3.get());

        
        // thenApply:有传参 有返回值
        CompletableFuture<String> cp4 = cp1.thenApply((a) -> {
            try {
                System.out.println("--- cp4: ---");
                return cp1.get() + " cp4";
            } catch (InterruptedException | ExecutionException e) {
                throw new RuntimeException(e);
            }
        });
        System.out.println("cp4.get(): " + cp4.get());

    }
}

20250312

excel表单导出(实战)

以下是实现Spring Boot导出包含两个工作表的Excel文件的详细步骤和代码示例:
创建一个Controller类,用于处理Excel导出的请求:
@RestController
public class ExcelExportController {

    @GetMapping("/export/excel")
    public ResponseEntity<?> exportExcel(@RequestParam("req") String req) throws IOException {
        // 获取数据
        CaseDataVO caseDataVO = getData(req);

        // 创建Excel工作簿
        Workbook workbook = new XSSFWorkbook();

        // 处理CaseView数据
        Sheet caseViewSheet = workbook.createSheet("Case View");
        writeCaseView(caseViewSheet, caseDataVO.getCaseView());

        // 处理RankList数据
        Sheet rankListSheet = workbook.createSheet("Rank List");
        writeRankList(rankListSheet, caseDataVO.getCaseRank());

        // 将工作簿写入输出流
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        workbook.write(outputStream);

        // 设置响应
        ResponseEntity<byte[]> response = ResponseEntity
                .ok()
                .contentLength(outputStream.size())
                .header("Content-Disposition", "attachment; filename=\"export.xlsx\"")
                .body(outputStream.toByteArray());

        return response;
    }

    private void writeCaseView(Sheet sheet, CaseView caseView) {
        // 设置列标题
        Row headerRow = sheet.createRow(0);
        headerRow.createCell(0).setCellValue("案件编号");
        headerRow.createCell(1).setCellValue("案件名称");
        // 添加其他需要的列

        // 填充数据
        Row dataRow = sheet.createRow(1);
        dataRow.createCell(0).setCellValue(caseView.getCaseId());
        dataRow.createCell(1).setCellValue(caseView.getCaseName());
        // 添加其他数据
    }

    private void writeRankList(Sheet sheet, List<CaseRank> rankList) {
        // 设置列标题
        Row headerRow = sheet.createRow(0);
        headerRow.createCell(0).setCellValue("排名");
        headerRow.createCell(1).setCellValue("名称");
        headerRow.createCell(2).setCellValue("完成率");

        // 填充数据
        for (int i = 1; i <= rankList.size(); i++) {
            Row dataRow = sheet.createRow(i);
            CaseRank rank = rankList.get(i - 1);
            dataRow.createCell(0).setCellValue(rank.getRank());
            dataRow.createCell(1).setCellValue(rank.getName());
            dataRow.createCell(2).setCellValue(rank.getFinishRate().doubleValue());
        }
    }
}


处理数据获取逻辑:
在Controller中,添加一个方法来获取需要导出的数据:
private CaseDataVO getData(String req) {
    CaseView caseView = caseMapper.getCaseView(req);
    List<CaseRank> rankList = caseMapper.getRankList(req);

    CaseDataVO caseDataVO = new CaseDataVO();
    caseDataVO.setCaseView(caseView);
    caseDataVO.setCaseRank(rankList);
    return caseDataVO;
}


20250227

EXCEL上传&&下载

上传:

    /**
     * 上传Excel功能
     * @param mutipartfile
     * @return
     * @throws IOException
     */
    public String upload(Mutipartfile mutipartfile) throws IOException {

        // 获取文件名
        String fileName = mutipartfile.getOriginalFilename();
        String prefix = fileName.substring(fileName.lastIndexOf('.')).toLowerCase();
        if (!".xls".equals(prefix) && !".xlsx".equals(prefix)) {
            return "后缀不对!";
        }
        ZipSecureFile.setMinInflateRatio(-1.0d);

        // 创建临时文件
        Path path = Files.createTempFile(System.currentTimeMillis() + "", prefix);
        MutipartFile.transferTo(path);
        File file = path.toFile();
        InputStream is = Files.newInputStream(file.toPath());
        Workbook wb = new XSSFWorkbook(is);

        // 解析文件数据
        Sheet sheet = wb.getSheetAt(0);
        Row row = sheet.getRow(0);
        String cellValue = PoiExcelUtil.getValue(row.getCell(0));
    }

下载:

    /**
     * 下载Excel功能
     * @param url
     * @return
     * @throws IOException
     */
    public ResponseEntity<byte[]> downLoad(String url) throws IOException {

        // 创建SXSSFWorkbook
        InputStream stream = getClass().getClassLoader().getResourceAsStream(url);
        XSSFWorkbook wb = new XSSFWorkbook(stream);
        SXSSFWorkbook swb = new SXSSFWorkbook(wb, 200);
        Sheet sheet = swb.getSheetAt(0);
        Row row = sheet.createRow(0);
        Cell cell = null;

        // 创建单元格并赋值
        cell = row.createCell(0);
        cell.setCellValue("value1");
        cell = row.createCell(1);
        cell.setCellValue("value2");

        // 输出为下载文件
        byte[] bytes = PoiExcelUtil.pubOutputExcel(swb);
        return new ResponseEntity<>(bytes, HttpStatus.OK);
    }

20250214

RestTemplate

调用:

public Resp test(Req req) throws JsonProcessingException {
        // 构建请求头
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.MULTIPART_FORM_DATA);

        // 创建请求体
        MultiValueMap<String, Object> requestBody = new LinkedMultiValueMap<>();
        requestBody.add("id", req.getId);

        log.info("----- start -----");

        // 发送POST请求
        String API_URL;
        API_URL = String.format(API_URL, uniqueCode);
        ResponseEntity<String> response = httpClientService.sendPost(API_URL, requestBody, headers);
        if (response.getStatusCode().is2xxSuccessful()) {
            // 将响应体反序列化为Resp对象
            log.info("----- submit file success! resp: {} -----", objectMapper.readValue(response.getBody(), Resp.class));
            return objectMapper.readValue(response.getBody(), Resp.class);
        } else {
            // 处理错误响应
            log.error("----- Failed! -----");
            throw new RuntimeException("Failed: " + response.getStatusCodeValue() + " - " + response.getBody());
        }
    }

 HttpClientService:

@Service
public class HttpClientService {

    RestTemplate restTemplate = new RestTemplate();

    // 发送GET请求的方法
    public ResponseEntity<String> sendGet(String url, HttpHeaders headers) {
        HttpEntity<String> request = new HttpEntity<>(headers);
        return restTemplate.exchange(url, HttpMethod.GET, request, String.class);
    }

    // GET请求 针对字节流的返回类型
    public ResponseEntity<byte[]> sendGet1(String url, HttpHeaders headers) {
        HttpEntity<byte[]> request = new HttpEntity<>(headers);
        return restTemplate.exchange(url, HttpMethod.GET, request, byte[].class);
    }

    // 发送POST请求的方法,支持JSON请求体
    public ResponseEntity<String> sendPost(String url, Object requestBody, HttpHeaders headers) {
        HttpEntity<Object> request = new HttpEntity<>(requestBody, headers);
        return restTemplate.exchange(url, HttpMethod.POST, request, String.class);
    }

    // 发送PUT请求的方法,支持JSON请求体
    public ResponseEntity<String> sendPut(String url, Object requestBody, HttpHeaders headers) {
        HttpEntity<Object> request = new HttpEntity<>(requestBody, headers);
        return restTemplate.exchange(url, HttpMethod.PUT, request, String.class);
    }
}

20250211

Cron表达式

语法格式:

[分钟] [小时] [天] [月] [星期几] [年]


六个字段(一个标准的cron表达式由六个字段组成,顺序如下:)
          
秒 分钟 小时 日期 月份 星期

例子:

cron例子:import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
public class ScheduledTasks {

    @Scheduled(cron = "0 0 * * *")
    public void dailyTask() {
        System.out.println("每日任务执行于: " + new Date());
    }

    @Scheduled(cron = "0 0 1 * *")
    public void morningTask() {
        System.out.println("早晨任务执行于: " + new Date());
    }
}


每周一早上8点执行一次:
0 8 * * 1

每个周一到周五的15:00(下午3点)执行一次:
0 15 * * 1-5

每个月的最后一个星期五的15:00执行一次:
0 15 * * 5L

每个月的1号和15号的00:00执行一次:
0 0 1,15, * *

每周的周一和周三早上8点(08:00)执行一次:
0 8 * * 1,3

每天的15:00到17:00(下午3点到5点)的整点执行一次:
0 15-17 * * *

每个月的最后一天的00:00执行一次:
0 0 L * *

每个月的最后一个星期五的15:00执行一次:
0 15 * * 5L

每个工作日的早上8点执行一次:
0 8 * * W

每5分钟执行一次:
0/5 * * * *

每小时的第0分钟和第30分钟执行一次:
0,30 * * * *

每星期的第1天(周一)的下午3点执行一次:
0 15 * * 1

20250204

Nginx

负载均衡配置

location/api/:意思是如果请求能匹配上/api/这个字符串。

proxy_pass:该指令的作用是设定转发的目的地,其后跟的是转发的目的地址。


20250125

今日学习:

实体类(ENTITY,VO,DTO)


职责不同:

1. entity是与数据库表一一对应的对象,通常由ORM框架生成,用于直接操作数据库;

2. vo是用于在前后端之间传递数据的对象,通常表示一组业务相关的展示数据。它是只读的。

3. dto是用于模块间数据传递的对象,通常对entity进行裁剪或组合

应用场景:

1. ENTIRY:主要是ORM框架中进行数据库操作,以及实现业务逻辑;

2. VO:主要用于前端展示层,封装数据以适合特定UI的形式呈现;

3. DTO:主要用于网络传输或远程调用,减少网络开销,高速传输。

Java策略模式

定义:

1. Context封装角色:也叫做上下文角色,起承上启下封装作用,屏蔽高层模块对

策略,算法的直接访问,封装可能存在的变化。

2. Strategy抽象策略对象:策略的抽象,通常为接口,定义每个策略或算法必须具有的方法和属性。

3. ConcreteStrategy具体策略角色:实现抽象策略中的操作,该类含有具体的算法。

示例代码:

public class OfficeHandlerStrategyFactory {
    private static final Map<String,OfficeHandlerStrategy> map = new HashMap<>();
    static {
        map.put("docx",new OfficeHandlerDocxStrategy());
        map.put("xlsx",new OfficeHandlerXlsxStrategy());
        map.put("pptx",new OfficeHandlerPptxStrategy());
    }
    public static OfficeHandlerStrategy getStrategy(String type){
        return map.get(type);
    }
}

public interface OfficeHandlerStrategy {
    void handlerOffice(String filePath);
}

public class OfficeHandlerDocxStrategy implements OfficeHandlerStrategy {
    @Override
    public void handlerOffice(String filePath) {
        System.out.println("处理docx");
    }
}

测试:

public class OfficeHandlerStrategyClient {
    public static void main(String[] args) {
        String filePath = "C://file/123.xlsx";
        String type = getFileExtension(filePath);
        OfficeHandlerStrategy strategy = OfficeHandlerStrategyFactory.getStrategy(type);
        strategy.handlerOffice(filePath);
    }
 
    private static String getFileExtension(String filePath){
        // 解析文件名获取文件扩展名,比如 文档.docx,返回 docx
        String fileExtension = filePath.substring(filePath.lastIndexOf(".")+1);
        return fileExtension;
    }
}

20241107

merge && rebase

区别:

工作原理不同:

1. merge操作将两个分支的不同提交记录合并成一个新的提交记录。这个合并提交会作为两个分支历史的一个交汇点,保留每个分支上的提交记录。

2. rebase操作是指改变基准点,将一个分支的提交记录(如feature分支),在另一个分支(如master分支)重新应用。Git会将 要变基的分支上的提交记录挨个应用到目标分支上,并重新创建提交历史。

分支历史不同:

1. merge保留完整的提交历史;

2. rebase操作会修改分支的历史,将当前分支的提交,插入到目标分支的提交之后,显得线性。

20241020

B树和B+树的区别

一、节点存储数据的方式

  • B树:在B树中,无论是叶子节点还是非叶子节点,都会存储数据。这意味着数据和指针共同保存在同一节点中。
  • B+树:与B树不同,B+树的数据全部保存在叶子节点中,而非叶子节点则只存储索引信息,不直接存储数据。

二、查找数据的过程

  • B树:在B树中,查找数据的过程可能涉及多个节点。由于非叶子节点也存储数据,因此可能在非叶子节点就命中所需数据。然而,这种查找方式可能导致效率不稳定。
  • B+树:在B+树中,查找数据的过程始终从根节点开始,逐层向下遍历,直到叶子节点。由于非叶子节点只存储索引信息,因此查找过程更加稳定。每次查找都会从父节点到叶子节点结束,确保在叶子节点中找到所需数据。

三、空间利用率

  • B树:由于B树的每个节点都存储数据,这可能导致空间利用率相对较低。特别是当数据项较大时,每个节点能存储的索引项数量有限,从而增加了树的高度和I/O操作次数。
  • B+树:B+树只有叶子节点存储数据,非叶子节点只存储索引信息。这种结构使得B+树能够存储更多的索引项,指向更多的子节点,从而降低了树的高度。因此,B+树的空间利用率更高,减少了I/O操作次数。

四、结构稳定性

  • B树:在B树中,插入和删除数据可能需要频繁变更树的结构。特别是当插入或删除操作导致节点分裂或合并时,可能需要重新平衡整个树结构。这可能导致B树的结构相对不稳定。
  • B+树:B+树的插入和删除操作主要发生在叶子节点上。由于非叶子节点只存储索引信息,因此插入和删除操作对树结构的影响较小。这有助于维护B+树结构的稳定性。

五、范围查找性能

  • B树:在B树中进行范围查找时,需要逐个节点进行遍历。由于非叶子节点也存储数据,因此可能需要访问多个节点才能找到满足条件的数据范围。这可能导致范围查找效率较低。
  • B+树:B+树的所有数据记录都存储在叶子节点上,且叶子节点之间通过指针相连形成链表。这种结构使得B+树在进行范围查询时更加高效。一旦找到起始点,就可以沿着链表顺序访问后续的节点,直到达到范围的终点。因此,B+树的范围查找性能优于B树。

六、应用场景

  • B树:B树更适合于数据库的索引结构,特别是当需要处理大量点查询时。由于B树的非叶子节点也存储数据,因此在某些情况下可能能够更快地找到所需数据。
  • B+树:B+树更适合于文件系统等场景,特别是当需要处理大量范围查询和排序操作时。由于B+树的叶子节点之间通过链表相连,因此在进行范围查询和排序操作时更加高效。

综上所述,B树和B+树在节点存储数据的方式、查找数据的过程、空间利用率、结构稳定性、范围查找性能以及应用场景等方面都存在显著差异。在实际应用中,应根据具体需求选择合适的索引结构。

InnoDB索引底层数据结构

MySQL的InnoDB存储引擎的索引底层数据结构主要采用B+树。以下是关于InnoDB索引底层数据结构的详细解析:

一、B+树的特点

  1. 非叶子节点只存储索引:B+树中,非叶子节点仅存储索引键(通常是主键或索引列的值),而不存储实际的数据记录。这使得非叶子节点能够存储更多的索引项,从而降低树的高度,提高查找效率。
  2. 叶子节点存储数据:B+树的叶子节点存储了实际的数据记录或指向数据记录的指针。对于InnoDB存储引擎来说,叶子节点存储的是整行数据(对于聚集索引)或主键值(对于二级索引)。
  3. 叶子节点通过链表相连:B+树的叶子节点之间通过双向链表相连,这有助于进行范围查询和顺序读取。当需要查找某个范围内的数据时,可以从起始节点开始,沿着链表顺序访问后续的节点,直到达到范围的终点。

二、InnoDB索引类型

  1. 聚集索引(Clustered Index)

    • InnoDB存储引擎会按照每张表的主键构造一棵B+树,这棵树就是聚集索引。
    • 聚集索引的叶子节点中存放的即为整张表的行记录数据,因此也称为数据页。
    • 聚集索引的存储并不是物理上连续的,而是逻辑上连续的,这有助于维护低成本的数据访问。
  2. 二级索引(Secondary Index)

    • 二级索引也称为辅助索引或普通索引。
    • 二级索引的非叶子节点存储的是索引键,而叶子节点存储的是主键值。这意味着,当通过二级索引查找数据时,首先找到主键值,然后再通过主键值回表查找实际的数据记录。

三、B+树在InnoDB中的应用优势

  1. 高效的范围查询:由于B+树的叶子节点通过链表相连,因此InnoDB能够高效地处理范围查询和排序操作。
  2. 稳定的查找性能:B+树的非叶子节点只存储索引信息,这使得查找过程更加稳定。每次查找都会从父节点到叶子节点结束,确保在叶子节点中找到所需数据。
  3. 良好的空间利用率:B+树的结构使得InnoDB能够存储更多的索引项,指向更多的子节点,从而降低了树的高度。这有助于减少I/O操作次数,提高数据访问效率。

综上所述,MySQL的InnoDB存储引擎采用B+树作为索引底层数据结构,这是基于B+树在数据访问、查找效率、空间利用率以及范围查询等方面的优势。这种设计使得InnoDB能够高效地处理大量的数据访问和查询操作。

20240927

常见的队列及应用场景

队列:

1. 顺序队列:使用数组实现;

2. 链式队列:使用链表实现;

队列应用场景

1. 任务调度。如操作系统中的任务队列,定时任务调度等。

2. 消息传递。如消息队列中间件RabbitMQ, Kafka等。

3. 缓冲区。队列可作缓冲区,用于存储生产者产生的数据,然后由消费者逐个消费。

4. 网络请求处理。队列可用于保存待处理的网络请求。

常见的Map

1. HashMap

2. HashTable(线程安全)

3.LinkedHashMap

4. TreeMap

5.ConcurrentHashMap(线程安全)

常见的List

1. ArrayList

2. LinkedList

3. Vector(线程安全)

4. Stack(线程安全)

单例模型既然已经用了synchronized,为什么还要在加volatile?,你觉得构造函数前需要添加什么修饰符?

volatile关键字:

1. 解决可见性问题;

确保了被修饰的变量对所有线程的可见性。即,当一个线程修改了被volatile修饰的变量的值时,这个新值就立刻被更新到主内存中,并被其他线程所看到。

2. 指令重排问题

例如,在单例模式的实现中,instance = new Singleton()这行代码虽然看似简单,但实际上包含了三个步骤:分配内存空间给instance、在内存中初始化Singleton对象、将instance指向分配的内存地址。如果这三个步骤被重排序(如先分配内存地址给instance,再初始化对象),那么在没有volatile的情况下,就可能出现一个线程看到instance非null但对象尚未完全初始化的状态。

常见的限流算法

1. 计数器限流算法(固定窗口);

原理:通过维护一个计数器变量来限制在特定时间间隔内的请求数量。

实现:在一段时间间隔内,对请求计数,如果请求数超过了最大值,则进行限流处理。当达到时间间隔的终点时,计数器会被清零,重新开始计数。

2. 滑动窗口限流算法;

原理:本质也是一种计数器,但它通过对时间窗口的滑动来管理请求的计数。

实现:将时间窗口分为多个小周期,每个小周期都有自己的计数器。随着时间的滑动,过期的小周期数据被删除,这样可以更精确地控制流量。

3. 漏桶限流算法;

原理:通过一个带有恒定速率的漏桶来模拟数据流量的处理过程。不关心数据流入速率,漏桶流出速度始终保持不变。

实现:当数据包到达时,假如桶未满,则接受数据包并排队处理;反之则丢弃数据包。

4. 令牌桶限流算法。

原理:令牌桶算法使用一个固定容量的桶来存放令牌,这些令牌以恒定的速率被添加到桶中。当数据包需要发送到网络时,它们会消耗桶中的令牌。桶中有足够的令牌,则允许数据包被发送;否则,数据包等待或丢弃。

实现:令牌桶以恒定的速率产生令牌,并将它们添加到桶中。如果桶已满,新生成的令牌将被丢弃。当数据包需要发送时,消耗桶中相应数量的令牌。

20240926

spring的新特性;

1. 轻量级;Spring框架的核心库非常小巧,核心JAR包大小通常较小;

2. 控制反转(IOC)、依赖注入(DI);

IOC是Spring的核心原则之一,它将对象的创建和依赖关系的管理从代码中分离出来,转交给Spring容器进行管理

DI是IOC的一种实现方式,Spring通过DI机制将依赖对象自动注入到需要它们的类中,减少了代码的冗余和复杂性。

3. 面向切面编程

Spring提供了AOP的支持,允许开发人员将横切关注点(如日志记录,事务管理等)与业务逻辑分离,从而提高了代码的内聚性和可重用性。

4. 容器管理

Spring框架提供了一个容器(ApplicationContext),用于管理和配置应用程序的各个组件(如Bean,数据库连接,事务等)。方便管理对象,减少重复的代码和配置。

ioc和aop的概念

  • IOC(控制反转):是一种设计原则,用于减少代码间的耦合。在Spring中,对象的创建、配置和生命周期管理不再由代码直接控制,而是由IOC容器负责。
  • AOP(面向切面编程):是一种编程范式,允许开发者将横切关注点(如日志、事务管理等)从业务逻辑中分离出来,通过“切面”的方式织入到目标对象中。


java反射;

反射概念:Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类所有的属性和方法;对于任意一个类,都能调用它的任意一个方法和属性。这种动态获取的信息以及动态调用对象的方法的功能,称为Java语言的反射机制。

元信息包括1. 类本身,2. 类的构造方法,3. 类的字段,4. 类的方法。

反射的使用:

主要涉及以下几个步骤

1. 获取Class对象;

2. 获取类的信息;如构造器,字段,方法等。

3. 调用类的方法;

4. 创建类的实例。


java垃圾回收;


基本类型和引用类型;

基本类型概念:基本类型是Java预定义的一组数据类型,用于表示简单的数据值。这些类型在Java语言规范中被明确定义,固定大小和取值范围。

引用类型概念:Java中除基本类型之外的所有类型,包括类,接口,数组。这些类型的变量存储的是对象的引用,而不是对象本身。

区别

基本类型引用类型
存储内容存储数据的实际值存储对数据的引用(即对象的地址)
内存分配栈内存堆内存
大小和取值范围固定大小和取值范围大小和取值范围根据实际对象的大小和类型而变化
默认值自动赋予默认值(如0, false等)默认为null(表示没有引用任何对象)
传递方式按值传递(传递变量值的拷贝)按引用传递(传递变量的引用/地址)

堆内存和栈内存的区别

堆内存

栈内存

1. 分配与释放方式

动态分配和释放

自动分配和释放

2. 存储内容

主要存储对象的实例,动态分配的数据结构,大型数据集。

主要存储函数的调用和局部变量。

3. 访问速度

相对较慢

相对较快

4. 生命周期

取决于程序员,由程序员手动申请和释放堆内存空间。

生命周期短。

5. 空间大小

2024/0919

八股

JVM

JVM 内存结构

答:主要包括以下几部分:

1. 堆(Heap):堆是JVM中最大的一块内存区域。用于存储对象实例。所有通过 new 关键字创建的对象都会被分配到堆内存中

2. 方法区(Method Area):方法区用于存储已被虚拟机加载的类信息,常量,静态变量,等等。

3. 虚拟机栈(VM Stack):每个线程在执行 Java 方法时都会创建一个对应的虚拟机栈,用于存储局部变量、方法参数、方法返回地址等信息。虚拟机栈会随着方法的调用和返回而动态地进行压栈和出栈操作。

4. 程序计数器(Program Counter Register):程序计数器是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。

5. 本地方法栈(Native Method Stack):本地方法栈(Native Method Stacks)与虚拟机栈作用相似,区别在于虚拟机栈为虚拟机执行Java方法(字节码)服务,而本地方法栈是为虚拟机使用到的Native方法服务。

虚拟机栈里面有什么;本地变量表存的是什么

答:

虚拟机栈主要包括:

1. 局部变量表:用于存放方法参数,方法内部的局部变量;

2. 操作数栈:用于存放方法执行过程中的中间结果

3. 动态链接

4. 方法返回地址:用于存储方法执行完毕后,需要返回到方法被调用的位置信息,以便继续执行。

本地变量表存放信息:

本地变量表(Local Variable Table)是虚拟机栈中栈帧的一部分,用于存储

1. 方法的参数

2. 方法内部定义的局部变量。

new 一个对象的过程

答:包含以下阶段

1. 类加载:JVM通过类加载器将该类的字节码加载到内存;

2. 内存分配:JVM在堆内存中为对象分配内存;

3. 构造对象:JVM调用类的构造函数来初始化对象;

4. 返回对象引用:JVM将初始化完毕的对象的引用返回给new表达式。

哪些场景会出现 OOM(内存不足 Out Of Memory)

答:OOM错误通常发生在以下几种场景中:

1. 内存泄露:内存泄露累积,最终导致系统可用内存耗尽;

2. 大量数据加载:一次性加载大量数据,超出系统可用的内存容量;

3. 内存限制

4. 堆内存不足:Java程序在运行时会将对象分配到堆内存中。当堆内存不足时,就会抛出OOM异常。

什么是新生代和老年代

新生代和老年代是java虚拟机(JVM)中不同的内存区域,它们都属于堆内存(Heap)一部分。

新生代:用于存放新创建对象的内存区域。由于大多数对象在创建后很快变得不可达(即成为垃圾),因此新生代垃圾回收频率较高,存放短生命周期的对象。

老年代:垃圾回收频率低,用于存放长生命周期的对象。

java常见什么垃圾收集器;

1. Serial收集器

特点:单线程执行垃圾收集,适用于单核处理器或小型应用。在垃圾收集过程中会暂停所以应用线程。

2. Parallel收集器

特点:Parallel收集器是Serial收集器的多线程版本;主要用于新生代。

3. CMS(Concurrent Mark Sweep)收集器

特点:以低停顿时间为目标,采用标记-清除算法,与应用程序线程并发执行垃圾收集。

4. G1(Garbage-First)收集器

特点:面向服务端应用,设计目标是代替CMS收集器。它使用分代收集的思想,但不再坚持将整个堆划分为固定大小的新生代,老生代等,而是将堆划分为多个区域,根据各区域的垃圾堆积价值动态调整收集策略。

垃圾回收过程;

通常包含以下几个阶段(以G1收集器为例)

1. 初始标记:停止所有应用线程(STW),标记从GC Roots直接可达的对象。

2. 并发标记:与应用线程并发执行,标记通过已标记对象间接可达的对象。

3. 最终标记:再次停止所用应用线程(STW),处理在并发标记阶段发生变化的标记。

4. 筛选回收:识别可回收的区域,并计算存活对象的数量。将存活对象复制到其他区域,并释放原区域的空间。

5. 并发清理:清理标记阶段判断为死亡的对象(未被标记的对象,即垃圾),释放空间内存。

G1 回收器特点

1. 分区化内存管理:区域划分,灵活分配,不受限于固定大小和固定数量的内存分区。

2. 并行和并发:G1回收期间,可由多个GC线程同时工作。

3. 垃圾优先策略:可优先回收,因为G1使用Garbage-First策略来确定先回收哪些区域;可混合回收,同时处理新生代和老生代的垃圾回收。

4. 高效的空间整理:在回收的过程中,G1会进行适当的对象移动,以减少空间碎片。

标记清除和标记整理的区别;

1. 内存碎片问题

标记清除:会产生大量不连续的内存碎片,因为它只回收垃圾对象而不移动存活对象;

标记整理:可以避免内存碎片的产生,通过整理存活对象,有利于后续的内存分配。

2. 适用场景

标记清除:适用于对象存活率较低的场景,如新生代内存区域;

标记整理:适用于对象存活率较高的场景,如老年代内存区域。

3. 性能方面

标记清除:实现简单,但是会产生内存碎片,降低了内存的利用率和分配效率;

标记整理:可避免内存碎片的产生,但整理存活对象需要消耗时间资源,可能增加垃圾回收的停顿时间。

出现内存碎片会有什么问题

1. 内存利用率降低:内存碎片会将空闲内存分割成小块,这些小块无法满足程序申请大内存请求。

2. 性能下降:内存碎片会增加内存管理的复杂度,导致内存分配和回收的效率降低。

3. 增加系统稳定性风险:极端情况下,内存碎片会导致无法为关键任务分配内存,导致程序崩溃。

还有什么别的回收器;

CMS 回收器特点

1. 并发收集:CMS回收器垃圾收集工作可与应用程序线程并发执行。

2. 采用标记清除算法:该算法首先标记存活对象,然后清除未被标记的对象,即垃圾对象。

3. 垃圾回收分阶段:初始标记,并发标记,重新标记,并发清除。

MySQL

MySQL 用的什么版本

InnoDB 索引

索引分类

1. 按逻辑区分:普通索引,唯一索引,主键索引

2. 按实现方式:BTREE索引,HASH索引

B+树索引跟哈希索引的区别

B+树索引

哈希索引

1. 数据结构和存储方式

多路平衡查找树;

顺序存储。

基于哈希表实现;

无序。

2. 查询效率

O(log n)

O(1)

3. 适用场景

尤其适用于数据量较大的情况,因为B+树索引的查询效率相对稳定。

在大量唯一等值查询时,哈希索引的效率通常更高。

4. 更新开销

插入和删除操作需要维护B+树的平衡,但通常比哈希索引的维护成本低。

插入和删除操作需要重新计算哈希值并移动数据,可能增加维护成本。

索引失效

1. 确保数据类型一致。

2. 避免在索引列上使用函数或者表达式。

3. 谨慎使用LIKE查询:当使用LIKE查询且通配符%位于开头时(如LIKE '%xxx'),数据库无法利用索引来限制搜索范围,导致索引失效。

4. 遵循最左前缀原则:在使用复合索引时,如果没有按照索引列的顺序使用最左列开始查询,索引可能无法使用。

脏读:一个事务读取了另一个事务未提交的数据,这些数据是不可信的,因为可能会被回滚。

不可重复读:是指在一个事务内,多次读取同一数据,由于其他事务的修改提交,导致多次读取的结果不一致。

幻读:是指当事务重新读取了一个范围的记录时,另一个事务插入了新纪录,之前的事务再次读取这个范围的数据时,出现“幻影行”。

四个隔离级别

1. 读未提交

定义:最低的隔离级别,允许事务读取尚未提交的数据变更。

问题:存在脏堵,不可重复读,幻读。

2. 读已提交

定义:允许事务读取并发事务已提交的数据。

问题:存在不可重复读,幻读。

3. 可重复读

定义:MySQL的默认隔离级别。确保同一事务在并发读取时看到相同的数据航,即在一个事务内,多次读取同一数据的结果是一致的。

问题:存在幻读

4. 可串行化

定义:最高的隔离级别,通过强制事务串行执行,避免冲突。

问题:可能导致大量超时和锁竞争,影响系统性能。

可重复读解决了什么问题

MVCC 什么原理

MVCC(Muti-Version Concurrency Control,多版本并发控制)是一种数据库并发控制机制,旨在解决数据库并发访问中的数据一致性问题,同时提高数据库的并发性能。以下是MVCC主要原理:

1. 版本管理:MVCC通过为数据库中的每一行数据保存多个版本来实现。每当数据被修改时,都会生成一个新的数据版本,并且每个版本都有唯一的标识。

2. 读写操作:在MVCC中,读操作通常被分为快照读和当前读;写操作会生成新的数据版本,并将旧版本数据保存在Undo日志中,以便回滚或者构造旧版本数据。

3. 可见性规则:Read View(读视图),在MVCC中,每个事务在开始时都会创建一个读视图,用于觉得哪个版本的数据对该事务是可见的。

4. Undo日志:不仅用于事务的回滚,还用于构造旧版本的数据,使得读操作能够读取到事务开始之前的数据状态。

5. 并发控制:MVCC允许读写操作同时进行,互不干扰。

幻读是什么;怎么解决

多线程

用了什么线程池

怎么创建线程池的;

1. 通过Executors执行器自动创建线程池;

2. 直接使用ThreadPoolExcutor手动创建线程池

线程池参数

1. corePoolSize(核心线程数)

线程池中的核心线程数,即使这些线程处于空闲状态,它们也不会被销毁。只有当工作队列满了后,才会创建超过这个数量的线程。

2. maxinumPoolSize(最大线程数)

线程池中允许的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会尝试创建新的线程来执行任务。

3. keepAliveTime(存活时间)

当线程池中的线程数量超过核心线程数时,这是多余空闲线程在终止前等待新任务的最长时间。

4. unit(时间单位)

keepAliveTime参数的时间单位,如TimeUnit.SECONDS,TimeUnit.MINUTES等。

5. wordQueue(工作队列)

用于存放待执行的任务的阻塞队列。当所有线程都在执行任务时,新任务会被添加到这个队列中等待执行。这个队列可以是无界的,有界的。可以选择有界队列,防止资源被耗

6. threadFactory(线程工厂)

用来创建新线程的工厂类。通过自定义线程工厂,可以为线程设置特定的属性,如线程名称、优先级等。

7. handler(拒绝策略)

  1. 线程池已满且队列容量达到上限时,对于新任务的处理策略。常见的拒绝策略包括:

    • DiscardOldestPolicy:丢弃队列中等待时间最长的任务,然后尝试执行新任务。
    • DiscardPolicy:直接丢弃任务,不抛出异常。
    • CallerRunsPolicy:由调用线程执行任务(降低新任务的提交速度)。
    • AbortPolicy:直接抛出RejectedExecutionException异常。

设计模式

常用的设计模式

1. 工厂模式;

2. 观察者模式;

3. 模板方法模式;

4. 代理模式;

5. 策略模式;

工厂模式和简单工厂模式对比

1. 实现方式

工厂模式:定义一个创建对象的抽象类,该类中声明了用于创建对象的工厂方法;

简单工厂:定义一个静态工厂类,该类中包含了多个静态方法。

2. 适用场景

工厂模式:需要创建的对象种类多,适用频繁增加或修改产品的场景;

简单工厂:需要创建的对象种类少,产品种类相对稳定。

3. 优缺点

工厂模式:优点是降低耦合性,提高系统的灵活性和可扩展性;缺点是增加了复杂度,每增加一个产品类,都需要增加一个相应的工厂子类

简单工厂:优点是客户端不需关心对象的创建细节,简化客户端代码;缺点是违反了开闭原则(对扩展开放,对修改封闭),增加新产品需要修改工厂类。

策略模式如何实现

1. 定义策略接口;

2. 创建具体策略类;

3. 上下文类(策略管理器)

4. 使用策略。

计网

七层协议;TCP 位于哪一层

TCP 三次握手和四次挥手

Redis

redis和mysql怎么保证数据的一致性

1. 双写模式:在进行写操作时,先写MySQL,再写Redis。

2. 异步更新:进行写操作时,只将数据写入MySQL,然后异步地将数据写入Redis。

3. 基于消息队列的同步:使用消息队列(Kafka,RabbitMQ)等来实现MySQL和Redis之间的消息同步。当MySQL中的数据发生变化时,将变化的数据写入消息队列,然后Redis订阅消息队列,根据消息内容更新自己的数据。

4. 定期全量同步:定期从MySQL中读取全量数据,然后覆盖到Redis中。

SQL优化(慢SQL)

1. 索引优化

创建合适的索引:为经常查询的字段创建索引,可以显著提高查询速度。特别是在where字句,join条件或order by字句中出现的字段。

使用复合索引:如果查询条件经常涉及多个字段,可以考虑创建复合索引。

2. 查询优化

避免" select * ",只查询需要的字段,减少数据传输和处理的开销。

优化查询语句:尽量避免使用子查询,复杂的JOIN操作和嵌套查询。

使用合适的查询算法:如哈希表,B树等高效的数据结构来实现快速查询。

3. 数据分区

水平分区:将表中的数据按照一定的规则(如时间,地区等)划分为多个字表,每个字表存储表中的一部分数据。这样可以减少查询时需要扫描的数据量,提高速度。

垂直分区:将表中的列按照一定的规则划分为多个字表,每个字表存储表中的一部分列。

4. 缓存技术

使用缓存系统:如redis,memcached等分布式缓存系统,将经常查询的数据存储在内存中,以减少对数据库的访问次数,提高查询速度。

使用内存数据库:如SAP HANA, Oracle TimesTen等,将数据完全驻留在内存,实现极快的查询速度。

5. 分布式查询处理

负载均衡:通过负载均衡技术将查询请求分配给不同的服务器,提高系统的并发处理能力。

并行查询:利用多核多处理器进行并行查询处理,可以显著提高查询的执行效率。

6. 数据库优化

合理设计数据库表结构:包括选择合适的数据类型,设计合理的字段和索引等。

定期维护数据库:如重建索引,清理无用数据。

使用数据库集群技术:通过数据库集群技术,提高数据库的并发处理能力和扩展性。

spring用到哪些设计模式

  • 工厂模式(Factory Pattern):用于创建对象实例,如BeanFactory。
  • 单例模式(Singleton Pattern):Spring容器中的Bean默认是单例的。
  • 代理模式(Proxy Pattern):AOP实现的基础,使用动态代理。
  • 适配器模式(Adapter Pattern):Spring的AOP使用适配器模式来使原有的对象与接口匹配。
  • 观察者模式(Observer Pattern):用于监听事件,如ApplicationListener。
  • 模板方法模式(Template Method Pattern):如JdbcTemplate、HibernateTemplate等。

@Configuration 注解的作用

@Configuration注解用于指示一个类声明了一个或多个@Bean方法,并且这个类可以被Spring容器处理以生成bean定义和服务请求。它允许通过Java配置类来替代XML配置文件。 

@SpringBootApplication 包含哪些注解

@SpringBootApplication是一个方便的注解,它包含了@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan

  • @SpringBootConfiguration:表明该类是一个配置类,等同于@Configuration
  • @EnableAutoConfiguration:告诉Spring Boot基于添加的jar依赖自动配置Spring应用。
  • @ComponentScan:告诉Spring在包和子包中查找其他组件、配置和服务,让@Component、@Service、@Repository等注解的类被扫描到。

观察者模式的概念

ConcurrentHashMap的特点是线程安全

ConcurrentHashMap是Java并发包中的一个线程安全的哈希表,它使用了分段锁(在Java 8中改为基于Node的锁和CAS操作)来提高并发级别。它允许并发读写操作,且无需进行外部同步。

线程安全的集合类Vector、HashTable、Stack、ConcurrentHashMap

  • Vector
    • 原因:Vector是Java早期提供的动态数组类,与ArrayList类似,但它是同步的。Vector的每个方法都加了synchronized修饰符,这意味着在同一时刻,只有一个线程能够执行Vector中的方法,从而保证了线程安全。
  • Hashtable
    • 原因:Hashtable是一个线程安全的散列表,它给几乎所有public方法都加上了synchronized关键字,这使得在多线程环境中对Hashtable的访问是安全的。但需要注意的是,Hashtable不允许键或值为null。
  • Stack
    • 原因:Stack继承自Vector,因此它也是一个线程安全的集合。Stack实现了后进先出(LIFO)的堆栈数据结构。
  • ConcurrentHashMap
    • 原因:ConcurrentHashMap采用了分段锁(在Java 8及以后版本中,分段锁被替换为了更高效的CAS操作和synchronized关键字结合的方式),这允许ConcurrentHashMap在多个线程之间同时读写数据,而无需进行外部同步。它通过将数据分成多个段(segment),每个段都有自己的锁,从而提高了并发性能。

线程安全的集合类之所以安全,是因为它们通过不同的同步机制(如synchronized关键字、分段锁、CAS操作、复制写等)来确保在多线程环境中对集合的访问是互斥的,从而避免了数据不一致或数据污染的问题。

Java线程池

  1. 降低资源消耗:通过重用已存在的线程,减少线程创建和销毁造成的消耗。
  2. 提高响应速度:当任务到达时,可以立即分配线程执行,而不是等待线程创建。
  3. 提高线程的可管理性:线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配、调优和监控。

总结:“低消 高速 易管理”

redis主从复制

SpringBoot自动装配

1. 主启动类上添加了SpringBootApplication注解,这个注解组合了EnableAutoConfiguration注解;

2. EnableAutoConfiguration注解又组合Import注解,导入了AutoConfigurationImportSelector类;

3. 实现selectImports方法,这个方法经过层层调用,最终会读取META-INF目录下的 后缀名为imports文件;(注:springboot2.7以前的版本,读取的是spring.factories文件)。

4. 读取到全类名之后,会解析注册条件,也就是@Conditional及其衍生注解,把满足注册条件的Bean对象自动注入到IOC容器中。


重载和重写

共同点:

多态性的体现,无论是重载还是重写,都是多态性在面向对象编程中的体现。多态性允许我们以统一的接口处理不同的数据类型或执行不同的操作

不同点

重载(Overloading)重写(Overriding)
定义在同一个类中定义多个同名方法,但这些方法的参数列表(参数个数、类型、顺序)不同子类提供一个特定签名的方法,该方法与父类中已有的方法具有相同的名称、参数列表和返回类型(或子类型)
范围发生在同一个类中发生在父类与子类之间
多态性类型编译时多态性运行时多态性
参数列表必须不同(参数个数、类型、顺序至少有一项不同)必须相同
返回类型可以相同,也可以不同必须相同或返回父类返回类型的子类型(协变)


& 和 && 的区别

共同点:两者都可以用作逻辑与的运算符,表示逻辑与(and)的关系。即当运算符两边的表达式的结果都为true时,整个运算结果才为true;否则,只要有一方为false,则结果为false。

不同点:短路功能:
&&(逻辑与运算符):具有短路的功能。即如果第一个表达式的结果为false,则不会再计算第二个表达式,直接返回false。这种特性在判断条件时非常有用,特别是当第二个表达式涉及复杂计算或可能导致异常时(如空指针引用)。
&(按位与运算符):不具备短路功能。不管第一个表达式的结果如何,都会对两个表达式进行运算。

IOC

一、IOC的基本概念

  • 控制反转:在传统的编程方式中,对象的创建和依赖关系的维护往往由程序员在代码中直接完成。而在Spring中,这些工作被转移到了Spring容器中,由容器负责创建对象并管理它们之间的依赖关系。这就是所谓的“控制反转”。
  • 依赖注入:作为IOC的一种实现方式,依赖注入强调在对象创建时,容器将对象所需的依赖项(即其他对象)注入到该对象中。这样,对象就不需要自己查找和创建其依赖项,从而降低了对象之间的耦合度。

二、IOC的作用

  1. 解耦和模块化:通过将对象的创建和依赖关系的管理交给容器来完成,IOC降低了不同模块之间的耦合度,使得开发者可以更加专注于业务逻辑的实现,而不是对象之间的依赖关系。
  2. 灵活性和可扩展性:使用IOC容器,可以方便地修改和调整对象的配置和依赖关系,而无需修改源代码。这使得应用程序更加灵活和可扩展,能够适应不断变化的需求和业务场景。
  3. 简化对象的创建和管理:IOC容器负责对象的创建和生命周期管理,开发者无需手动创建对象,也不需要处理对象的初始化和销毁操作。这减少了代码的冗余,提高了开发效率。

三、Spring中IOC的实现方式

在Spring框架中,IOC容器有两种主要的实现方式:

1. BeanFactory:是Spring中最原始的IOC容器,提供了基本的IOC支持。但是,近提供简单的实例化对象和依赖注入功能,没有提供如自动装配,事件发布等高级功能。

2. ApplicationContext:是BeanFactory的子接口,提供了许多高级功能,如国际化支持,事件传播,资源加载等。

Spring提供了多种方式来实现IOC,包括:

1. 基于XML的配置文件

2. 基于注解的配置,@Component

3. 基于Java的配置类,@Configuration

Bean声明注解:

1. @Component

2. @Repository

3. @Service

4. @Controller

5. @Bean

四、IOC的注入方式

Spring支持多种依赖注入的方式,主要包括:

  1. 构造器注入:通过构造函数将依赖项注入到对象中。这种方式要求对象在创建时就需要其依赖项。
  2. Setter方法注入:通过调用对象的setter方法来注入依赖项。这种方式允许对象在创建后再注入其依赖项。
  3. 接口注入(不常用):通过实现特定的接口来注入依赖项,但这种方式在Spring中并不常用。

五、总结

Spring中的IOC是一种强大的机制,它通过控制反转和依赖注入的方式,实现了对象之间的解耦和模块化,提高了应用程序的灵活性、可扩展性和可维护性。在Spring框架中,IOC是核心之一,为开发者提供了一种优雅和高效的方式来组织和管理应用程序的组件。

工厂模式

工厂模式是一种创建型设计模式,主要用于创建对象,并将对象的创建和使用分离,增加系统的灵活性和可扩展性。

简单工厂模式

概念

简单工厂模式,又称静态工厂方法模式,由工厂对象决定创建出哪一种产品类的实例。实质是由一个工厂类根据传入的参数,动态决定应该创建哪一个产品类。

例子

假如有一个披萨店,提供多种口味的披萨。根据简单工厂模式创建出不同口味的披萨。

// 披萨接口  
public interface Pizza {  
    void prepare();  
    void bake();  
    void cut();  
    void box();  
}  
  
// 纽约风味披萨类  
public class NYStylePizza implements Pizza {  
    // 实现prepare(), bake(), cut(), box()方法  
}  
  
// 芝加哥风味披萨类  
public class ChicagoStylePizza implements Pizza {  
    // 实现prepare(), bake(), cut(), box()方法  
}  
  
// 披萨工厂类  
public class PizzaFactory {  
    public Pizza createPizza(String type) {  
        if (type.equalsIgnoreCase("NY")) {  
            return new NYStylePizza();  
        } else if (type.equalsIgnoreCase("Chicago")) {  
            return new ChicagoStylePizza();  
        }  
        return null;  
    }  
}  

工厂方法模式

概念

工厂方法模式定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类的实例化推迟到子类中进行

// 手机接口  
public interface Phone {  
    void getBrand();  
}  
  
// 小米品牌手机类  
public class XiaomiPhone implements Phone {  
    @Override  
    public void getBrand() {  
        System.out.println("小米");  
    }  
}  
  
// 魅族品牌手机类  
public class MeizuPhone implements Phone {  
    @Override  
    public void getBrand() {  
        System.out.println("魅族");  
    }  
}  
  
// 手机工厂接口  
public interface PhoneFactory {  
    Phone getPhone();  
}  
  
// 小米手机工厂类  
public class XiaomiFactory implements PhoneFactory {  
    @Override  
    public Phone getPhone() {  
        return new XiaomiPhone();  
    }  
}  
  
// 魅族手机工厂类  
public class MeizuFactory implements PhoneFactory {  
    @Override  
    public Phone getPhone() {  
        return new MeizuPhone();  
    }  
}  
  
// 客户端代码  
public class Client {  
    public static void main(String[] args) {  
        PhoneFactory xiaomiFactory = new XiaomiFactory();  
        Phone xiaomiPhone = xiaomiFactory.getPhone();  
        xiaomiPhone.getBrand();  
  
        PhoneFactory meizuFactory = new MeizuFactory();  
        Phone meizuPhone = meizuFactory.getPhone();  
        meizuPhone.getBrand();  
    }  
}

 在这个例子中,PhoneFactory是手机工厂接口,定义了生产手机的方法。XiaomiFactory和MeizuFactory是具体的手机工厂类,分别实现了PhoneFactory接口,并返回各自品牌的手机对象。客户端代码通过不同的工厂类来创建不同品牌的手机对象。

观察者模式

定义与概念

定义:观察者模式是一种行为设计模式,用于建立一种对象与对象之间的依赖关系,以便当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动更新。

核心要素:

1. 观察者(observer):依赖于主题(subject)的对象,它注册自己以便在主题状态变化时得到通知。

2. 主题(subject):维护一系列依赖于它的观察者对象,并在其状态改变时主动通知观察者。

例子

观察者模式的一个典型例子是气象站与听众之间的关系。在例子中,气象站作为被观察者(subject),负责收集并更新天气信息;而听众(如居民,农民,旅行者等)则作为观察者(observer),他们关注天气变化并据此做出相应的动作。

气象站与听众的例子

场景描述

1. 气象站每天定时收集天气信息;

2. 听众注册为气象站的观察者,他们希望每天了解天气变化;

3. 当气象站的天气信息发生变化时,它会自动通知所有注册的观察者。

角色划分

1. 气象站(subject):维护一个观察者列表,当天气发生变化时,遍历列表,通知所有观察者。

2. 听众(observer):注册到气象站,以便在天气信息变化的时候得到通知,并根据通知做出相应的行动(如增减衣物,调整农作计划,准备雨具等)。

实现

1. 定义抽象观察者(observer)接口:定义一个接口,规定观察者接收消息的方法。例如,可以定义一个update()方法,用于接收天气信息的变化通知。

2. 实现具体观察者(observer):不同类型的听众实现抽象观察者接口,根据接收到的天气信息变化通知执行响应的操作。例如,居民观察者在收到降温变化会增加衣物;农民观察者可能根据降雨预测调整灌溉计划。

3. 定义抽象被观察者(subject)接口:定义一个接口,规定被观察者添加,删除和通知观察者的方法。例如,可以定义addObserver(),deleteObserver(),notifyObserver()等方法。

4. 实现具体被观察者(subject):气象站实现抽象被观察者接口,维护一个观察者列表,并在天气信息发生变化时调用notifyObserver()方法通知所有观察者。

5. 注册与通知:

5.1 听众通过调用气象站的addObserver()方法注册为观察者。

5.2 当气象站的天气信息发生变化时,它会自动调用notifyObserver()方法,遍历观察者列表并调用每个观察者的update()方法,将天气信息的变化通知给它们。

模板方法模式

定义

模板方法模式定义了一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构,即可重定义该算法的某些特定步骤。

例子

常见的例子就是在Spring的JDBC操作中。Spring的JdbcTemplate类就是一个模板方法模式的实现,它封装了数据库操作(如查询,更新等)的通用步骤。开发者只需要提供SQL语句和结果集的处理逻辑,而不关心数据库连接的获取和释放等细节。

import org.springframework.jdbc.core.JdbcTemplate;  
import org.springframework.jdbc.datasource.DriverManagerDataSource;  
  
import javax.sql.DataSource;  
import java.util.List;  
import java.util.Map;  
  
public class JdbcTemplateExample {  
  
    public static void main(String[] args) {  
        // 创建数据源  
        DataSource dataSource = new DriverManagerDataSource(  
                "jdbc:mysql://localhost:3306/testdb", "username", "password");  
  
        // 创建JdbcTemplate实例  
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);  
  
        // 使用JdbcTemplate执行查询  
        String sql = "SELECT id, name, age FROM users";  
        List<Map<String, Object>> results = jdbcTemplate.queryForList(sql);  
  
        // 输出结果  
        for (Map<String, Object> row : results) {  
            System.out.println(row);  
        }  
    }  
}

总结

Spring中的模板方法模式通过提供通用的算法骨架,使得开发者可以专注于实现具体业务逻辑,而不需要关心底层复杂的算法细节。这种方式大大提高了开发效率和代码的可维护性。

Redis

主要功能

1. 缓存

通过将频繁访问但不易变更的数据存储在redis中,可减少对数据库的直接查询,从而加快数据访问速度,减轻数据库的负担。

2. 会话管理

Redis可以用来存储和管理用户会话信息,支持跨多个服务器的会话共享。

3. 分布式锁

Redis提供了原子操作,如SETNX(已在新版Redis中废弃,推荐使用Lua脚本或者SET命令的NX, PX选项),来实现分布式锁。

4. 计数器

Redis的原子操作使其成为实现计数器的理想选择。无论是网站访问量,用户点击量还是其他需要计数的场景,Redis都能提供高性能的计数器实现。

5. 发布/订阅

Redis的发布订阅模式允许消息的生产者发送消息到频道,而消费者可以订阅一个或多个频道以接收消息。这种机制非常适合实现消息队列和实时通知系统。

分布式锁

redis内部是单线程的,但是外部客户端请求是并发的。当多个客户端并发访问Redis的同一数据时,就有可能出现线程安全问题。

解决办法:分布式锁机制。下面是具体步骤:

1. 加锁

使用SETNX(SET if NOT Exists,“如果不存在则设置”)命令尝试将锁标识,设置一个唯一的值。如果SETNX命令返回1,表示成功获取锁;返回0,表示锁已经被其他节点持有。为防止死锁,可在设置锁的同时使用EXPIRE命令设置锁的过期时间。

2. 解锁

当节点完成操作后,需要使用DEL命令删除锁标识以释放锁。

20240829

redis缓存:

高性能

通过redis缓存,减少与mysql的交互,从而提高系统性能

高并发

以SQL查询为例子,将高频的SQL语句在MySQL处理完成之后,放入redis里面。接下来很多用户,就都是通过redis进行处理的(基于内存处理,支持高并发),从而提高了访问速度。

使用缓存带来的问题:

1. 双写不一致:指mysql跟redis数据不一致

2. 穿透雪崩:穿透,指redis某个key过期,但是它还在承受5000的并发访问,请求会到达mysql

重新访问;雪崩,指redis宕机,引发mysql宕机。

3. 并发竞争:多线程并发,竞争同一个key 

redis数据类型

redis数据类型
redisjava
String-字符串String
Hash-字典HashMap
List-列表LinkedList
Set-集合HashSet
zSet-有序集合TreeSet

redis进程:

redis过期策略

1. 惰性删除:在被访问的时候,再删除。

2. 定期删除:定期循环删除。

redis内存淘汰策略

1. 不淘汰策略

2. 最近最少使用

3. 根据过期时间优先

4. 随机删除

redis哨兵机制

监听:当主Redis节点宕机后,实现主备节点的自动切换。

 

配置优先级

1. 项目中resources目录下的application.yml

2. jar包所在目录下的application.yml

3. 操作系统环境变量

4. 命令行参数

vue是一款用于构建用户界面的渐进式的JavaScript框架。

指令:

1. v-for

作用:

写法:

2. v-bind

作用:为HTML标签的属性动态绑定属性值

写法:

v-bind:属性名="属性值"

或 

:属性名="属性值"

3. v-if & v-show

作用:控制元素的显示和隐藏

语法:v-if="表达式",表达式值为true,显示;false,隐藏。

两者区别:

v-if是根据

.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值