<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>AuroraLilis</title><description>AuroraLilis Site</description><link>http://zaoweiceng.github.io/</link><language>zh_CN</language><item><title>Spring框架</title><link>http://zaoweiceng.github.io/posts/java_web/spring/</link><guid isPermaLink="true">http://zaoweiceng.github.io/posts/java_web/spring/</guid><description>Java Spring Boot是一个基于Spring框架的开源项目，旨在简化Spring应用的创建、配置和部署过程。</description><pubDate>Tue, 18 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Spring 框架&lt;/h2&gt;
&lt;p&gt;Java Spring Boot是一个基于Spring框架的开源项目，旨在简化Spring应用的创建、配置和部署过程。以下是Spring Boot中的主要概念：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Spring Boot Starter&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;Spring Boot Starter是一系列依赖描述符，用于简化依赖管理。例如，&lt;code&gt;spring-boot-starter-web&lt;/code&gt;包含了构建Web应用所需的所有依赖。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;自动配置（Auto-Configuration）&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;Spring Boot通过自动配置尝试根据类路径设置、其他bean和各种属性设置来配置Spring应用。这样可以减少或消除需要手动编写的配置。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;嵌入式服务器&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;Spring Boot支持嵌入式服务器，如Tomcat、Jetty或Undertow，使得开发人员可以轻松地将应用打包为独立的JAR文件，直接运行。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Spring Boot CLI&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;Spring Boot CLI是一个命令行工具，用于快速开发和测试Spring Boot应用。它支持Groovy脚本，使得编写Spring应用更加简洁。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Actuator&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;Spring Boot Actuator提供了生产级别的功能，如健康检查、审计、指标收集和HTTP跟踪等，帮助监控和管理应用。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Properties和YAML配置&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;Spring Boot允许使用&lt;code&gt;.properties&lt;/code&gt;或&lt;code&gt;.yaml&lt;/code&gt;文件来外部化配置，使得配置管理更加灵活。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Profile&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;Profile是Spring Boot中用于在不同环境中配置应用的方式。例如，可以有不同的配置文件用于开发、测试和生产环境。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Spring Boot Starter Parent&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;spring-boot-starter-parent&lt;/code&gt;是一个特殊的 Starter，它提供了默认的配置，如Maven依赖管理、插件配置等。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Spring Boot Maven/Gradle插件&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;这些插件提供了打包、运行和测试Spring Boot应用的便捷方式。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;主类（Main Class）&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;Spring Boot应用通常包含一个主类，使用&lt;code&gt;@SpringBootApplication&lt;/code&gt;注解，并包含一个&lt;code&gt;main&lt;/code&gt;方法来启动应用。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SpringApplicationBuilder&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;SpringApplicationBuilder&lt;/code&gt;用于以编程方式配置和启动Spring Boot应用。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;事件和监听器&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;Spring Boot提供了事件和监听器机制，允许在应用的生命周期中执行特定操作。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;安全性（Security）&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;Spring Boot集成了Spring Security，提供了安全认证和授权的功能。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数据访问&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;Spring Boot支持多种数据访问技术，如JPA、JDBC、MongoDB等，并通过Starter简化配置。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Thymeleaf&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;Thymeleaf是Spring Boot推荐的模板引擎，用于构建动态Web页面。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RESTful API&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;Spring Boot使得创建RESTful API变得简单，通过Controller来处理HTTP请求。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AOP（面向切面编程）&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;Spring Boot支持AOP，允许开发者在不修改核心代码的情况下添加新功能。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;测试&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;Spring Boot提供了丰富的测试支持，包括单元测试、集成测试和端到端测试。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;日志&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;Spring Boot集成了日志框架，如Logback、Log4j2等，提供了灵活的日志配置。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;国际化（i18n）&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;Spring Boot支持国际化，允许根据用户的语言和地区设置显示不同的内容。
这些概念共同构成了Spring Boot框架，使得Java开发更加高效、简洁和强大。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;核心概念&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;控制反转：&lt;/strong&gt; Inversion Of Control，简称&lt;strong&gt;IOC&lt;/strong&gt;。对象的创建控制权由程序自身转移到外部（容器），这种思想称为控制反转。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对象的创建权由程序员主动创建转移到容器(由容器创建、管理对象)。这个容器称为：IOC容器或Spring容器。&lt;/li&gt;
&lt;li&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;依赖注入：&lt;/strong&gt; Dependency Injection，简称&lt;strong&gt;DI&lt;/strong&gt;。容器为应用程序提供运行时，所依赖的资源，称之为依赖注入。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;程序运行时需要某个资源，此时容器就为其提供这个资源。&lt;/li&gt;
&lt;li&gt;例：EmpController程序运行时需要EmpService对象，Spring容器就为其提供并注入EmpService对象。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;**bean对象：**IOC容器中创建、管理的对象，称之为：bean对象。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;要把某个对象交给IOC容器管理，需要在类上添加一个注解：&lt;strong&gt;@Component&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;而Spring框架为了更好的标识web应用程序开发当中，bean对象到底归属于哪一层，又提供了@Component的衍生注解：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;注解&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;th&gt;位置&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;@Component&lt;/td&gt;
&lt;td&gt;声明bean的基础注解&lt;/td&gt;
&lt;td&gt;不属于以下三类时，用此注解&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;@Controller&lt;/td&gt;
&lt;td&gt;@Component的衍生注解&lt;/td&gt;
&lt;td&gt;标注在控制层类上&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;@Service&lt;/td&gt;
&lt;td&gt;@Component的衍生注解&lt;/td&gt;
&lt;td&gt;标注在业务层类上&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;@Repository&lt;/td&gt;
&lt;td&gt;@Component的衍生注解&lt;/td&gt;
&lt;td&gt;标注在数据访问层类上（由于与mybatis整合，用的少）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
</content:encoded></item><item><title>HTTP协议</title><link>http://zaoweiceng.github.io/posts/other/http%E5%8D%8F%E8%AE%AE/</link><guid isPermaLink="true">http://zaoweiceng.github.io/posts/other/http%E5%8D%8F%E8%AE%AE/</guid><description>HTTP协议简单介绍</description><pubDate>Tue, 18 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;基本介绍&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;HTTP&lt;/strong&gt;：Hyper Text Transfer Protocol(超文本传输协议)，规定了浏览器与服务器之间数据传输的规则。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;基于TCP协议:&lt;/strong&gt; 面向连接&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;TCP是一种面向连接的(建立连接之前是需要经过三次握手)、可靠的、基于字节流的传输层通信协议，在数据传输方面更安全&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;基于请求-响应模型:&lt;/strong&gt;   一次请求对应一次响应（先请求后响应）&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;请求和响应是一一对应关系，没有请求，就没有响应&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;HTTP协议是无状态协议:&lt;/strong&gt;  对于数据没有记忆能力。每次请求-响应都是独立的&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;无状态指的是客户端发送HTTP请求给服务端之后，服务端根据请求响应数据，响应完后，不会记录任何信息。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;缺点:  多次请求间不能共享数据&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;优点:  速度快&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;请求之间无法共享数据会引发的问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如：京东购物。加入购物车和去购物车结算是两次请求&lt;/li&gt;
&lt;li&gt;由于HTTP协议的无状态特性，加入购物车请求响应结束后，并未记录加入购物车是何商品&lt;/li&gt;
&lt;li&gt;发起去购物车结算的请求后，因为无法获取哪些商品加入了购物车，会导致此次请求无法正确展示数据&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;具体使用的时候，我们发现京东是可以正常展示数据的，原因是Java早已考虑到这个问题，并提出了使用会话技术(Cookie、Session)来解决这个问题。具体如何来做，我们后面课程中会讲到。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h2&gt;请求&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;**请求协议：**浏览器将数据以请求格式发送到服务器。包括：&lt;strong&gt;请求行、请求头 、请求体&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;GET&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;请求行&lt;/strong&gt; ：HTTP请求中的第一行数据。由：&lt;code&gt;请求方式&lt;/code&gt;、&lt;code&gt;资源路径&lt;/code&gt;、&lt;code&gt;协议/版本&lt;/code&gt;组成（之间使用空格分隔）
&lt;ul&gt;
&lt;li&gt;请求方式：GET&lt;/li&gt;
&lt;li&gt;资源路径：/brand/findAll?name=OPPO&amp;amp;status=1
&lt;ul&gt;
&lt;li&gt;请求路径：/brand/findAll&lt;/li&gt;
&lt;li&gt;请求参数：name=OPPO&amp;amp;status=1
&lt;ul&gt;
&lt;li&gt;请求参数是以key=value形式出现&lt;/li&gt;
&lt;li&gt;多个请求参数之间使用&lt;code&gt;&amp;amp;&lt;/code&gt;连接&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;请求路径和请求参数之间使用&lt;code&gt;?&lt;/code&gt;连接&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;协议/版本：HTTP/1.1&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;请求头&lt;/strong&gt;：第二行开始，上图黄色部分内容就是请求头。格式为key: value形式
&lt;ul&gt;
&lt;li&gt;http是个无状态的协议，所以在请求头设置浏览器的一些自身信息和想要响应的形式。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;请求头&lt;/th&gt;
&lt;th&gt;含义&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Host&lt;/td&gt;
&lt;td&gt;表示请求的主机名&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;User-Agent&lt;/td&gt;
&lt;td&gt;浏览器版本。 例如：Chrome浏览器的标识类似Mozilla/5.0 ...Chrome/79 ，IE浏览器的标识类似Mozilla/5.0 (Windows NT ...)like Gecko&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Accept&lt;/td&gt;
&lt;td&gt;表示浏览器能接收的资源类型，如text/*，image/&lt;em&gt;或者&lt;/em&gt;/*表示所有；&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Accept-Language&lt;/td&gt;
&lt;td&gt;表示浏览器偏好的语言，服务器可以据此返回不同语言的网页；&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Accept-Encoding&lt;/td&gt;
&lt;td&gt;表示浏览器可以支持的压缩类型，例如gzip, deflate等。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Content-Type&lt;/td&gt;
&lt;td&gt;请求主体的数据类型&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Content-Length&lt;/td&gt;
&lt;td&gt;数据主体的大小（单位：字节）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;请求体 ：存储请求参数
&lt;ul&gt;
&lt;li&gt;GET请求的请求参数在请求行中，故不需要设置请求体&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;POST&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;请求行&lt;/strong&gt;：包含请求方式、资源路径、协议/版本
&lt;ul&gt;
&lt;li&gt;请求方式：POST&lt;/li&gt;
&lt;li&gt;资源路径：/brand&lt;/li&gt;
&lt;li&gt;协议/版本：HTTP/1.1&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;请求头&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;请求体&lt;/strong&gt; ：存储请求参数
&lt;ul&gt;
&lt;li&gt;请求体和请求头之间是有一个空行隔开（作用：用于标记请求头结束）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;GET请求和POST请求的区别：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;区别方式&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;GET请求&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;POST请求&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;请求参数&lt;/td&gt;
&lt;td&gt;请求参数在请求行中。&amp;lt;br/&amp;gt;例：/brand/findAll?name=OPPO&amp;amp;status=1&lt;/td&gt;
&lt;td&gt;请求参数在请求体中&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;请求参数长度&lt;/td&gt;
&lt;td&gt;请求参数长度有限制(浏览器不同限制也不同)&lt;/td&gt;
&lt;td&gt;请求参数长度没有限制&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;安全性&lt;/td&gt;
&lt;td&gt;安全性低。原因：请求参数暴露在浏览器地址栏中。&lt;/td&gt;
&lt;td&gt;安全性相对高&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;示例&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;@RestController
public class RequestController {

    /**
     * 请求路径 http://localhost:8080/request?name=Tom&amp;amp;age=18
     * @param request
     * @return
     */
    @RequestMapping(&quot;/request&quot;)
    public String request(HttpServletRequest request){
        //1.获取请求参数 name, age
        String name = request.getParameter(&quot;name&quot;);
        String age = request.getParameter(&quot;age&quot;);
        System.out.println(&quot;name = &quot; + name + &quot;, age = &quot; + age);
        
        //2.获取请求路径
        String uri = request.getRequestURI();
        String url = request.getRequestURL().toString();
        System.out.println(&quot;uri = &quot; + uri);
        System.out.println(&quot;url = &quot; + url);
        
        //3.获取请求方式
        String method = request.getMethod();
        System.out.println(&quot;method = &quot; + method);
        
        //4.获取请求头
        String header = request.getHeader(&quot;User-Agent&quot;);
        System.out.println(&quot;header = &quot; + header);
        return &quot;request success&quot;;
    }

}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;常见状态码&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;状态码&lt;/th&gt;
&lt;th&gt;英文描述&lt;/th&gt;
&lt;th&gt;解释&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;200&lt;/td&gt;
&lt;td&gt;OK&lt;/td&gt;
&lt;td&gt;客户端请求成功，即处理成功，这是我们最想看到的状态码&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;302&lt;/td&gt;
&lt;td&gt;Found&lt;/td&gt;
&lt;td&gt;指示所请求的资源已移动到由Location响应头给定的 URL，浏览器会自动重新访问到这个页面&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;304&lt;/td&gt;
&lt;td&gt;Not Modified&lt;/td&gt;
&lt;td&gt;告诉客户端，你请求的资源至上次取得后，服务端并未更改，你直接用你本地缓存吧。隐式重定向&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;400&lt;/td&gt;
&lt;td&gt;Bad Request&lt;/td&gt;
&lt;td&gt;客户端请求有语法错误，不能被服务器所理解&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;403&lt;/td&gt;
&lt;td&gt;Forbidden&lt;/td&gt;
&lt;td&gt;服务器收到请求，但是拒绝提供服务，比如：没有权限访问相关资源&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;404&lt;/td&gt;
&lt;td&gt;Not Found&lt;/td&gt;
&lt;td&gt;请求资源不存在，一般是URL输入有误，或者网站资源被删除了&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;405&lt;/td&gt;
&lt;td&gt;Method Not Allowed&lt;/td&gt;
&lt;td&gt;请求方式有误，比如应该用GET请求方式的资源，用了POST&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;428&lt;/td&gt;
&lt;td&gt;Precondition Required&lt;/td&gt;
&lt;td&gt;服务器要求有条件的请求，告诉客户端要想访问该资源，必须携带特定的请求头&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;429&lt;/td&gt;
&lt;td&gt;Too Many Requests&lt;/td&gt;
&lt;td&gt;指示用户在给定时间内发送了太多请求（“限速”），配合 Retry-After(多长时间后可以请求)响应头一起使用&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;431&lt;/td&gt;
&lt;td&gt;Request Header Fields Too Large&lt;/td&gt;
&lt;td&gt;请求头太大，服务器不愿意处理请求，因为它的头部字段太大。请求可以在减少请求头域的大小后重新提交。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;500&lt;/td&gt;
&lt;td&gt;Internal Server Error&lt;/td&gt;
&lt;td&gt;服务器发生不可预期的错误。服务器出异常了，赶紧看日志去吧&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;503&lt;/td&gt;
&lt;td&gt;Service Unavailable&lt;/td&gt;
&lt;td&gt;服务器尚未准备好处理请求，服务器刚刚启动，还未初始化好&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
</content:encoded></item><item><title>YAML配置文件</title><link>http://zaoweiceng.github.io/posts/other/yml/</link><guid isPermaLink="true">http://zaoweiceng.github.io/posts/other/yml/</guid><description>YAML常被用于配置文件、数据交换、数据存储等场景</description><pubDate>Tue, 18 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;strong&gt;YAML配置文件详解&lt;/strong&gt;
YAML是“YAML Ain&apos;t Markup Language”（递归缩写为“YAML不是标记语言”）的缩写，原意是强调这种语言以数据做为中心，而不是标记语言。YAML是一种直观的能够被电脑读取的数据序列化格式，也易于人类阅读。它常被用于配置文件、数据交换、数据存储等场景。&lt;/p&gt;
&lt;h2&gt;一、基本语法&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;大小写敏感&lt;/strong&gt;：YAML对大小写是敏感的，&lt;code&gt;key&lt;/code&gt;和&lt;code&gt;value&lt;/code&gt;的大小写需要一致。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;缩进表示层级&lt;/strong&gt;：YAML使用缩进来表示数据层级关系，缩进只能使用空格，不能使用Tab键。通常建议每个层级缩进两个空格。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;冒号后有空格&lt;/strong&gt;：&lt;code&gt;key: value&lt;/code&gt;中的&lt;code&gt;:&lt;/code&gt;后面必须有一个空格。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;支持注释&lt;/strong&gt;：YAML允许使用&lt;code&gt;#&lt;/code&gt;进行注释，从&lt;code&gt;#&lt;/code&gt;开始到行尾的所有内容都将被视为注释。&lt;/li&gt;
&lt;li&gt;在yml格式的配置文件中，如果配置项是以0开头的，需要以&lt;code&gt;&apos;&apos;&lt;/code&gt;引起来，因为0开头在yml中表示8进制的数据。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;二、数据类型&lt;/h2&gt;
&lt;p&gt;YAML支持多种数据类型，包括：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;纯量（Scalar）&lt;/strong&gt;：单个的、不可再分的值，如字符串、布尔值、整数、浮点数等。
&lt;ul&gt;
&lt;li&gt;字符串：可以直接书写，也可以使用引号包裹。&lt;/li&gt;
&lt;li&gt;布尔值：用&lt;code&gt;true&lt;/code&gt;、&lt;code&gt;false&lt;/code&gt;表示。&lt;/li&gt;
&lt;li&gt;数值：直接书写，支持整数和浮点数。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;序列（Sequence）&lt;/strong&gt;：一组按顺序排列的值，类似于数组或列表。使用&lt;code&gt;-&lt;/code&gt;表示每个元素。&lt;pre&gt;&lt;code&gt;list:
  - item1
  - item2
  - item3
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;映射（Mapping）&lt;/strong&gt;：键值对集合，类似于字典或哈希表。使用&lt;code&gt;key: value&lt;/code&gt;表示。&lt;pre&gt;&lt;code&gt;dictionary:
  key1: value1
  key2: value2
  key3: value3
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;嵌套&lt;/strong&gt;：YAML支持数据的嵌套，可以组合使用序列和映射。&lt;pre&gt;&lt;code&gt;nested:
  - key1: value1
    key2: value2
  - key3: value3
    key4: value4
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;三、特殊语法&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;引用&lt;/strong&gt;：使用&lt;code&gt;&amp;amp;&lt;/code&gt;定义锚点，使用&lt;code&gt;*&lt;/code&gt;引用锚点。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;anchor: &amp;amp;commonSetting
  setting1: value1
  setting2: value2
ref1:
  &amp;lt;&amp;lt;: *commonSetting
  additionalSetting: value3
ref2:
  &amp;lt;&amp;lt;: *commonSetting
  anotherSetting: value4
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;合并&lt;/strong&gt;：使用&lt;code&gt;&amp;lt;&amp;lt;&lt;/code&gt;表示合并其他映射的内容。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;base:
  setting1: value1
  setting2: value2
override:
  &amp;lt;&amp;lt;: *base
  setting2: newValue
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;多行字符串&lt;/strong&gt;：使用&lt;code&gt;|&lt;/code&gt;或&lt;code&gt;&amp;gt;&lt;/code&gt;表示多行字符串。&lt;code&gt;|&lt;/code&gt;保留换行符，&lt;code&gt;&amp;gt;&lt;/code&gt;折叠换行符。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;multiLineString1: |
  This is a multi-line string
  with preserved newlines.
multiLineString2: &amp;gt;
  This is another multi-line string
  with folded newlines.
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;四、示例&lt;/h2&gt;
&lt;p&gt;以下是一个完整的YAML配置文件示例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 这是注释
name: Example Configuration
description: This is an example YAML configuration file.
settings:
  debug: true
  verbosity: 2
database:
  host: localhost
  port: 3306
  username: user
  password: pass
servers:
  - id: 1
    name: server1
    ip: 192.168.1.1
  - id: 2
    name: server2
    ip: 192.168.1.2
anchors:
  &amp;amp;defaultPorts
    http: 80
    https: 443
ports:
  &amp;lt;&amp;lt;: *defaultPorts
  ssh: 22
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这个示例中，我们定义了配置文件的名称、描述、一些设置、数据库连接信息、服务器列表以及端口号。我们还展示了如何使用锚点和引用来复用数据。&lt;/p&gt;
&lt;h2&gt;五、使用场景&lt;/h2&gt;
&lt;p&gt;YAML常用于以下场景：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;配置文件&lt;/strong&gt;：如Docker-compose、Kubernetes、Ansible等工具的配置文件。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数据交换&lt;/strong&gt;：在不同系统间交换数据。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数据存储&lt;/strong&gt;：存储结构化数据，如配置信息、用户数据等。
&lt;strong&gt;六、注意事项&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;缩进&lt;/strong&gt;：务必使用空格进行缩进，不要使用Tab键。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;引号&lt;/strong&gt;：虽然YAML允许不使用引号包裹字符串，但在某些特殊情况下（如字符串包含冒号、换行符等）需要使用引号。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;兼容性&lt;/strong&gt;：不同的YAML解析器可能存在细微的兼容性问题，建议查阅相关文档以确保兼容性。
通过以上介绍，相信您对YAML配置文件有了更深入的了解。在实际使用中，请根据具体需求和场景选择合适的语法和结构。&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item><item><title>virtual在使用中的问题</title><link>http://zaoweiceng.github.io/posts/cpp/virtual%E9%94%99%E8%AF%AF%E4%BD%BF%E7%94%A8/</link><guid isPermaLink="true">http://zaoweiceng.github.io/posts/cpp/virtual%E9%94%99%E8%AF%AF%E4%BD%BF%E7%94%A8/</guid><description>virtual在使用中的问题，bug！！！</description><pubDate>Sun, 16 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;virtual在使用中的问题&lt;/h2&gt;
&lt;p&gt;想要重写基类的方法，应该将基类方法上加入关键字&lt;code&gt;virtual&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;如果不加，在重写父类方法时可能会有问题，虽然编译器不会报错，但是使用多态时，会调用到基类的方法。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include&amp;lt;iostream&amp;gt;  
using namespace std;
class Animal{
    public:
    void say(){
        cout&amp;lt;&amp;lt;&quot;Animal say&quot;&amp;lt;&amp;lt;endl;
    }
};
class Cat:public Animal{
    public:
    void say(){
        cout&amp;lt;&amp;lt;&quot;Cat say&quot;&amp;lt;&amp;lt;endl;
    }
};

int main(){
    Animal *a=new Animal();
    Cat *c=new Cat();
    a-&amp;gt;say();
    c-&amp;gt;say();
    Animal *ac=new Cat();
    ac-&amp;gt;say();
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Animal say
Cat say
Animal say
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;父类方法使用&lt;code&gt;virtual&lt;/code&gt;关键字之后，仍然可以通过父类获取到父类的方法，但是使用多态时不会出现问题。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include&amp;lt;iostream&amp;gt;  
using namespace std;
class Animal{
    public:
    virtual void say(){
        cout&amp;lt;&amp;lt;&quot;Animal say&quot;&amp;lt;&amp;lt;endl;
    }
};
class Cat:public Animal{
    public:
    void say(){
        cout&amp;lt;&amp;lt;&quot;Cat say: &quot;;
        Animal::say();
    }
};

int main(){
    Animal *a=new Animal();
    Cat *c=new Cat();
    a-&amp;gt;say();
    c-&amp;gt;say();
    cout &amp;lt;&amp;lt; endl;
    Animal *ac=new Cat();
    ac-&amp;gt;say();
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Animal say
Cat say: Animal say

Cat say: Animal say
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;C++在编译类时，会创建一个包含类中所有方法的二进制对象。在非虚的情况下，将控制交给正确的方法的代码是硬编码，此时会根据编译时的类型调用方法。这成为静态绑定(static binding)， 也成为早绑定(early binding)。&lt;/p&gt;
&lt;p&gt;如果方法声明为virtual，会使用名为虚表(vtable)的特定内存区域调用正确的实现。每个具有一个或多个虚方法的类都有一张虚表，这种类的每个对象都包含指向虚表的指针，这个虚表包含指向虚方法实现的指针。通过这种方法，当使用某个对象调用方法时，指针也进入虚表，然后根据实际的对象类型执行正确版本的方法。这成为动态绑定(dynamic binding)或晚绑定(late binding)。&lt;/p&gt;
&lt;h2&gt;纯虚方法和抽象基类&lt;/h2&gt;
&lt;p&gt;纯虚方法是在类定义中显示说明该方法不需要定义。如果将某个方法设置为纯虚方法，就是告诉编译器当前类中不存在这个方法的定义。具有至少一个纯虚方法的类称为抽象类。&lt;/p&gt;
&lt;p&gt;纯虚方法定义：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;virtual void set() = 0;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;抽象类不能够被直接实例化，需要实现了纯虚方法的继承类。&lt;/p&gt;
&lt;h2&gt;override关键字&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;override&lt;/code&gt;是C++11引入的关键字，用于显式地指出某个函数是重写基类中的虚函数。它的主要目的是增加代码的可读性和维护性，同时让编译器能够检查重写是否正确。
&lt;strong&gt;基本用法&lt;/strong&gt;：
当在派生类中重写基类的一个虚函数时，可以在函数声明后加上&lt;code&gt;override&lt;/code&gt;关键字。这样，编译器会检查该函数是否真的重写了基类的某个虚函数，即函数签名（包括函数名、参数列表、常量性等）是否与基类中的虚函数完全匹配。
&lt;strong&gt;示例&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Base {
public:
    virtual void func(int) const {}
};
class Derived : public Base {
public:
    void func(int) const override {}  // 正确：显式指出重写基类的func函数
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;作用&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;编译器检查&lt;/strong&gt;：如上所述，&lt;code&gt;override&lt;/code&gt;让编译器能够检查重写是否正确。如果派生类中的函数没有正确重写基类的虚函数（例如，参数列表不匹配），编译器会报错。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;提高代码可读性&lt;/strong&gt;：通过使用&lt;code&gt;override&lt;/code&gt;，其他阅读代码的人可以很容易地知道这个函数是重写基类的虚函数，而不是派生类特有的函数。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;维护性&lt;/strong&gt;：如果基类中的虚函数签名发生变化，而派生类没有相应更新，使用&lt;code&gt;override&lt;/code&gt;的代码会在编译时出错，从而提醒开发者进行修正。
&lt;strong&gt;注意事项&lt;/strong&gt;：&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;override&lt;/code&gt;只能用于派生类中的函数声明或定义上，且该函数必须重写基类的虚函数。&lt;/li&gt;
&lt;li&gt;如果基类中的函数不是虚函数，则在派生类中使用&lt;code&gt;override&lt;/code&gt;会编译错误。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;override&lt;/code&gt;关键字应该紧跟在函数声明后，逗号与函数声明之间不能有空格。
&lt;strong&gt;错误示例&lt;/strong&gt;：&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;class Base {
public:
    void func(int) {}  // 不是虚函数
};
class Derived : public Base {
public:
    void func(int) override {}  // 错误：基类中的func不是虚函数
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;override&lt;/code&gt;关键字是C++11中一个非常有用的特性，它提高了代码的健壮性、可读性和维护性。在实际开发中，建议总是使用&lt;code&gt;override&lt;/code&gt;来显式指出重写基类的虚函数。&lt;/p&gt;
&lt;h2&gt;虚基类&lt;/h2&gt;
&lt;p&gt;在C++中，虚基类（Virtual Base Class）是多重继承中用于解决菱形继承问题的一种机制。所谓菱形继承问题，是指一个派生类从两个或多个基类继承，而这些基类又共同继承自同一个更高级的基类，导致派生类中出现了多份高级基类的实例，引起潜在的二义性和资源浪费。
虚基类的引入就是为了解决这一问题。通过将共同基类声明为虚基类，C++确保在派生类中只存在一份虚基类的实例，从而避免了数据重复和二义性。
以下是虚基类的一个简单示例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class A {
public:
    int a;
};
class B : virtual public A {
public:
    int b;
};
class C : virtual public A {
public:
    int c;
};
class D : public B, public C {
public:
    int d;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这个例子中，类B和类C都从类A虚继承，而类D又从类B和类C继承。由于类A是虚基类，所以类D中只会有一个类A的实例，避免了菱形继承问题。
虚基类的实现机制相对复杂，它通常通过虚基类表（Vtbl）来维护虚基类的指针，从而确保在派生类中只存在一份虚基类的实例。在使用虚基类时，需要注意构造函数的调用顺序和初始化，因为虚基类的构造函数会在派生类的构造过程中被调用，以确保虚基类的正确初始化。&lt;/p&gt;
</content:encoded></item><item><title>C++用户自定义字面量</title><link>http://zaoweiceng.github.io/posts/cpp/%E5%AD%97%E9%9D%A2%E9%87%8F/</link><guid isPermaLink="true">http://zaoweiceng.github.io/posts/cpp/%E5%AD%97%E9%9D%A2%E9%87%8F/</guid><description>C++11引入了用户自定义字面量（User-Defined Literals）的概念，允许开发者自定义字面量的后缀，以便于创建更直观、更符合特定领域需求的字面量。</description><pubDate>Sun, 16 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;C++11引入了用户自定义字面量（User-Defined Literals）的概念，允许开发者自定义字面量的后缀，以便于创建更直观、更符合特定领域需求的字面量。用户自定义字面量通过在常量后面添加特定的后缀来实现，这些后缀与相应的函数关联，这些函数负责将字面量转换为所需的类型。&lt;/p&gt;
&lt;h3&gt;用户自定义字面量的基本语法&lt;/h3&gt;
&lt;p&gt;用户自定义字面量是通过在标识符后面添加&lt;code&gt;operator&quot;&quot;&lt;/code&gt;后缀来定义的。例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;long double operator&quot;&quot; _km(long double val) {
    return val * 1000.0;
}
long double operator&quot;&quot; _m(long double val) {
    return val;
}
long double operator&quot;&quot; _cm(long double val) {
    return val / 100.0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这个例子中，我们定义了三个用户自定义字面量后缀：&lt;code&gt;_km&lt;/code&gt;、&lt;code&gt;_m&lt;/code&gt;和&lt;code&gt;_cm&lt;/code&gt;，它们分别用于表示千米、米和厘米。&lt;/p&gt;
&lt;h3&gt;使用用户自定义字面量&lt;/h3&gt;
&lt;p&gt;一旦定义了用户自定义字面量，就可以在代码中直接使用它们：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;long double distance = 1.5_km; // 等同于 1500.0
long double length = 100_cm;   // 等同于 1.0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样，代码的可读性得到了提高，因为字面量直接表达了其物理意义。&lt;/p&gt;
&lt;h3&gt;用户自定义字面量的类型&lt;/h3&gt;
&lt;p&gt;用户自定义字面量可以用于以下类型：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;整型字面量：&lt;code&gt;unsigned long long&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;浮点字面量：&lt;code&gt;long double&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;字符串字面量：&lt;code&gt;const char*&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;字符字面量：&lt;code&gt;char&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;C++11中还支持原始字符串字面量：&lt;code&gt;const char*&lt;/code&gt;
例如，定义一个用于字符串字面量的自定义后缀：&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;std::string operator&quot;&quot; _hello(const char* str, size_t len) {
    return std::string(str, len) + &quot; hello&quot;;
}
int main() {
    std::string greeting = &quot;World&quot;_hello; // 结果为 &quot;World hello&quot;
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;注意事项&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;用户自定义字面量函数必须是静态成员函数或全局函数。&lt;/li&gt;
&lt;li&gt;字面量运算符函数不能有默认参数。&lt;/li&gt;
&lt;li&gt;字符串字面量运算符函数必须接受两个参数：一个&lt;code&gt;const char*&lt;/code&gt;和一个&lt;code&gt;size_t&lt;/code&gt;，后者表示字符串的长度。&lt;/li&gt;
&lt;li&gt;用户自定义字面量可以提高代码的可读性和维护性，但应避免过度使用，以免造成混淆。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;实际应用&lt;/h3&gt;
&lt;p&gt;用户自定义字面量在实际开发中可以用于多种场景，如单位转换、货币转换、字符串处理等。它们使得代码更加直观，减少了转换错误的可能性。&lt;/p&gt;
&lt;h3&gt;系统自带的字面量&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;auto str1 = &quot;hello&quot;_s;
// std::string
auto str2 = &quot;hello&quot;;
// const char*
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>C++可变参数</title><link>http://zaoweiceng.github.io/posts/cpp/c%E5%8F%AF%E5%8F%98%E5%8F%82/</link><guid isPermaLink="true">http://zaoweiceng.github.io/posts/cpp/c%E5%8F%AF%E5%8F%98%E5%8F%82/</guid><description>在C++中，使用可变参数主要有两种方式：一种是通过C风格的变参函数，另一种是利用C++11引入的初始化列表和变参模板。</description><pubDate>Sun, 16 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;在C++中，使用可变参数主要有两种方式：一种是通过C风格的变参函数，另一种是利用C++11引入的初始化列表和变参模板。&lt;/p&gt;
&lt;h3&gt;C风格的变参函数&lt;/h3&gt;
&lt;p&gt;C风格的变参函数使用&lt;code&gt;stdarg.h&lt;/code&gt;头文件中的宏来实现。下面是一个简单的例子：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;cstdarg&amp;gt;
void printNumbers(int count, ...) {
    va_list args;
    va_start(args, count);
    for (int i = 0; i &amp;lt; count; ++i) {
        int num = va_arg(args, int);
        std::cout &amp;lt;&amp;lt; num &amp;lt;&amp;lt; &quot; &quot;;
    }
    va_end(args);
    std::cout &amp;lt;&amp;lt; std::endl;
}
int main() {
    printNumbers(3, 1, 2, 3);  // 输出: 1 2 3
    printNumbers(5, 4, 5, 6, 7, 8);  // 输出: 4 5 6 7 8
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这个例子中，&lt;code&gt;printNumbers&lt;/code&gt;函数可以接受任意数量的整数参数。第一个参数&lt;code&gt;count&lt;/code&gt;指定了后面参数的数量。&lt;code&gt;va_list&lt;/code&gt;、&lt;code&gt;va_start&lt;/code&gt;、&lt;code&gt;va_arg&lt;/code&gt;和&lt;code&gt;va_end&lt;/code&gt;是处理可变参数的宏。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;cstdarg&amp;gt; // C++中应使用cstdarg而不是stdarg.h

// 计算所有参数的和
double sum(int count, ...) {
    va_list args;
    va_start(args, count); // 初始化args来存储参数

    double total = 0;
    for (int i = 0; i &amp;lt; count; ++i) {
        total += va_arg(args, double); // 获取下一个参数
    }

    va_end(args); // 清理工作
    return total;
}

int main() {
    std::cout &amp;lt;&amp;lt; &quot;Sum: &quot; &amp;lt;&amp;lt; sum(3, 1.1, 2.2, 3.3) &amp;lt;&amp;lt; std::endl; // 输出应该是6.6
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++11的初始化列表和变参模板&lt;/h3&gt;
&lt;p&gt;C++11引入了更加现代化和类型安全的方法来处理可变参数，即使用初始化列表和变参模板。下面是一个例子：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;initializer_list&amp;gt;
void printNumbers(std::initializer_list&amp;lt;int&amp;gt; args) {
    for (int num : args) {
        std::cout &amp;lt;&amp;lt; num &amp;lt;&amp;lt; &quot; &quot;;
    }
    std::cout &amp;lt;&amp;lt; std::endl;
}
int main() {
    printNumbers({1, 2, 3});  // 输出: 1 2 3
    printNumbers({4, 5, 6, 7, 8});  // 输出: 4 5 6 7 8
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这个例子中，&lt;code&gt;printNumbers&lt;/code&gt;函数接受一个&lt;code&gt;initializer_list&amp;lt;int&amp;gt;&lt;/code&gt;类型的参数，它可以包含任意数量的整数。
此外，C++11还支持变参模板，允许模板接受任意数量的参数。下面是一个使用变参模板的例子：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
template&amp;lt;typename... Args&amp;gt;
void printNumbers(Args... args) {
    (std::cout &amp;lt;&amp;lt; ... &amp;lt;&amp;lt; args) &amp;lt;&amp;lt; std::endl;
}
int main() {
    printNumbers(1, &quot; &quot;, 2, &quot; &quot;, 3);  // 输出: 1 2 3
    printNumbers(4, &quot; &quot;, 5, &quot; &quot;, 6, &quot; &quot;, 7, &quot; &quot;, 8);  // 输出: 4 5 6 7 8
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这个例子中，&lt;code&gt;printNumbers&lt;/code&gt;是一个变参模板函数，可以接受任意类型和数量的参数。参数展开使用折叠表达式&lt;code&gt;(std::cout &amp;lt;&amp;lt; ... &amp;lt;&amp;lt; args)&lt;/code&gt;实现。
这两种方法都可以在C++中实现可变参数的功能，但C++11的初始化列表和变参模板提供了更好的类型安全和便利性。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;tuple&amp;gt;

// 变参模板函数的递归终止条件
template&amp;lt;typename T&amp;gt;
T sum(T last) {
    return last;
}

// 变参模板函数
template&amp;lt;typename T, typename... Args&amp;gt;
T sum(T first, Args... args) {
    return first + sum(args...); // 递归调用
}

int main() {
    std::cout &amp;lt;&amp;lt; &quot;Sum: &quot; &amp;lt;&amp;lt; sum(1, 2, 3, 4, 5) &amp;lt;&amp;lt; std::endl; // 输出应该是15
    return 0;
}

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;折叠表达式&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;折叠表达式&lt;/strong&gt;是C++17引入的一种特性，用于在变参模板中简化对参数包的处理。在上述例子中，&lt;code&gt;(std::cout &amp;lt;&amp;lt; ... &amp;lt;&amp;lt; args)&lt;/code&gt;就是一个折叠表达式。&lt;/p&gt;
&lt;h4&gt;折叠表达式的语法&lt;/h4&gt;
&lt;p&gt;折叠表达式的语法如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;(op ...)&lt;/code&gt;：一元右折叠&lt;/li&gt;
&lt;li&gt;&lt;code&gt;(... op)&lt;/code&gt;：一元左折叠&lt;/li&gt;
&lt;li&gt;&lt;code&gt;(init op ...)&lt;/code&gt;：二元右折叠&lt;/li&gt;
&lt;li&gt;&lt;code&gt;(... op init)&lt;/code&gt;：二元左折叠
其中：&lt;/li&gt;
&lt;li&gt;&lt;code&gt;op&lt;/code&gt; 是一个二元操作符，如&lt;code&gt;+&lt;/code&gt;、&lt;code&gt;&amp;lt;&amp;lt;&lt;/code&gt;等。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;...&lt;/code&gt; 表示参数包。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;init&lt;/code&gt; 是一个初始值。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;折叠表达式的工作原理&lt;/h4&gt;
&lt;p&gt;折叠表达式会将参数包中的每个参数依次与操作符&lt;code&gt;op&lt;/code&gt;结合，最终得到一个结果。具体来说：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;一元右折叠&lt;/strong&gt;：从右向左结合，例如&lt;code&gt;(a + ... + z)&lt;/code&gt;等价于&lt;code&gt;a + (b + (c + ... + z))&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;一元左折叠&lt;/strong&gt;：从左向右结合，例如&lt;code&gt;(... + a + b + c)&lt;/code&gt;等价于&lt;code&gt;((a + b) + c)&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;二元右折叠&lt;/strong&gt;：从右向左结合，每个参数都与&lt;code&gt;init&lt;/code&gt;结合，例如&lt;code&gt;(init + ... + z)&lt;/code&gt;等价于&lt;code&gt;init + (a + (b + (c + ... + z)))&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;二元左折叠&lt;/strong&gt;：从左向右结合，每个参数都与&lt;code&gt;init&lt;/code&gt;结合，例如&lt;code&gt;(... + a + b + c + init)&lt;/code&gt;等价于&lt;code&gt;(((a + b) + c) + init)&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;在例子中的应用&lt;/h4&gt;
&lt;p&gt;在上述例子中，&lt;code&gt;(std::cout &amp;lt;&amp;lt; ... &amp;lt;&amp;lt; args)&lt;/code&gt;是一个一元左折叠表达式。它的工作原理如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;从左向右开始，首先将&lt;code&gt;args&lt;/code&gt;中的第一个参数与&lt;code&gt;std::cout&lt;/code&gt;结合，使用&lt;code&gt;&amp;lt;&amp;lt;&lt;/code&gt;操作符输出。&lt;/li&gt;
&lt;li&gt;然后将结果（即更新后的&lt;code&gt;std::cout&lt;/code&gt;）与&lt;code&gt;args&lt;/code&gt;中的第二个参数结合，再次使用&lt;code&gt;&amp;lt;&amp;lt;&lt;/code&gt;操作符输出。&lt;/li&gt;
&lt;li&gt;重复上述过程，直到所有参数都被输出。
最终，这个折叠表达式会依次将所有参数输出到标准输出流&lt;code&gt;std::cout&lt;/code&gt;中。&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;优点&lt;/h4&gt;
&lt;p&gt;使用折叠表达式处理变参模板参数具有以下优点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;简洁&lt;/strong&gt;：避免了冗长的递归函数或循环。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;直观&lt;/strong&gt;：直接表达了参数的处理方式。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;高效&lt;/strong&gt;：编译器可以优化折叠表达式的实现。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;以下是一些使用C17折叠表达式处理可变参数的例子：&lt;/p&gt;
&lt;h4&gt;例子1：打印多个值&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;string&amp;gt;
template&amp;lt;typename... Args&amp;gt;
void print(Args... args) {
    (std::cout &amp;lt;&amp;lt; ... &amp;lt;&amp;lt; args) &amp;lt;&amp;lt; std::endl;
}
int main() {
    print(&quot;Hello, &quot;, &quot;world!&quot;, &quot; The answer is &quot;, 42);
    // 输出: Hello, world! The answer is 42
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;例子2：计算多个数的和&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
template&amp;lt;typename... Args&amp;gt;
auto sum(Args... args) {
    return (args + ...);
}
int main() {
    std::cout &amp;lt;&amp;lt; &quot;The sum is: &quot; &amp;lt;&amp;lt; sum(1, 2, 3, 4, 5) &amp;lt;&amp;lt; std::endl;
    // 输出: The sum is: 15
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;例子3：连接字符串&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;string&amp;gt;
template&amp;lt;typename... Args&amp;gt;
std::string concatenate(Args... args) {
    return (args + ...);
}
int main() {
    std::cout &amp;lt;&amp;lt; &quot;Concatenated string: &quot; &amp;lt;&amp;lt; concatenate(std::string(&quot;Hello, &quot;), &quot;world&quot;, &quot;!&quot;)
              &amp;lt;&amp;lt; std::endl;
    // 输出: Concatenated string: Hello, world!
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;例子4：检查所有参数是否满足某个条件&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
template&amp;lt;typename... Args&amp;gt;
bool all(Args... args) {
    return (... &amp;amp;&amp;amp; args);
}
int main() {
    std::cout &amp;lt;&amp;lt; std::boolalpha; // 打印布尔值而非0或1
    std::cout &amp;lt;&amp;lt; &quot;All true: &quot; &amp;lt;&amp;lt; all(true, true, true) &amp;lt;&amp;lt; std::endl;
    // 输出: All true: true
    std::cout &amp;lt;&amp;lt; &quot;All true: &quot; &amp;lt;&amp;lt; all(true, false, true) &amp;lt;&amp;lt; std::endl;
    // 输出: All true: false
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;例子5：使用初始化列表和折叠表达式创建数组&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;array&amp;gt;
template&amp;lt;typename T, typename... Args&amp;gt;
auto createArray(T first, Args... args) {
    return std::array&amp;lt;T, 1 + sizeof...(Args)&amp;gt;{first, args...};
}
int main() {
    auto arr = createArray(1, 2, 3, 4, 5);
    std::cout &amp;lt;&amp;lt; &quot;Array elements: &quot;;
    for (auto elem : arr) {
        std::cout &amp;lt;&amp;lt; elem &amp;lt;&amp;lt; &quot; &quot;;
    }
    std::cout &amp;lt;&amp;lt; std::endl;
    // 输出: Array elements: 1 2 3 4 5
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这些例子展示了折叠表达式在处理不同类型和数量的参数时的灵活性和强大功能。通过折叠表达式，我们可以编写更简洁、更直观的代码来处理可变参数。&lt;/p&gt;
</content:encoded></item><item><title>C++类型别名</title><link>http://zaoweiceng.github.io/posts/cpp/%E7%B1%BB%E5%9E%8B%E5%88%AB%E5%90%8D/</link><guid isPermaLink="true">http://zaoweiceng.github.io/posts/cpp/%E7%B1%BB%E5%9E%8B%E5%88%AB%E5%90%8D/</guid><description>C++类型别名和typedef 的区别</description><pubDate>Sun, 16 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;在C++中，&lt;strong&gt;类型别名（Type Alias）&lt;/strong&gt; 和 &lt;strong&gt;typedef&lt;/strong&gt; 都用于为现有类型创建别名，但它们的语法、功能和使用场景有所不同。&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;&lt;strong&gt;1. 基本概念&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;typedef&lt;/strong&gt;：C++早期引入的机制，用于为类型创建别名，语法较为古老。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;类型别名（using）&lt;/strong&gt;：C++11引入的现代语法，功能更强大，语法更直观。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3&gt;&lt;strong&gt;2. 语法区别&lt;/strong&gt;&lt;/h3&gt;
&lt;h4&gt;&lt;strong&gt;typedef 的语法&lt;/strong&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;typedef 原类型 别名名;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;typedef int MyInt;          // 基本类型别名
typedef void (*FuncPtr)(int); // 函数指针别名
typedef std::vector&amp;lt;int&amp;gt; IntVec; // 标准库类型别名
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;&lt;strong&gt;using 的语法&lt;/strong&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;using 别名名 = 原类型;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;using MyInt = int;          // 基本类型别名
using FuncPtr = void (*)(int); // 函数指针别名
using IntVec = std::vector&amp;lt;int&amp;gt;; // 标准库类型别名
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;区别&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;using&lt;/code&gt; 的语法更接近赋值操作（&lt;code&gt;=&lt;/code&gt;），可读性更高。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;typedef&lt;/code&gt; 的语法更冗长，尤其在复杂类型（如函数指针、模板）中更难理解。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3&gt;&lt;strong&gt;3. 复杂类型的处理&lt;/strong&gt;&lt;/h3&gt;
&lt;h4&gt;&lt;strong&gt;函数指针&lt;/strong&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;typedef&lt;/strong&gt;：&lt;pre&gt;&lt;code&gt;typedef void (*FuncPtr)(int); // 需要写成 `(*FuncPtr)(int)`
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;using&lt;/strong&gt;：&lt;pre&gt;&lt;code&gt;using FuncPtr = void (*)(int); // 直接声明，语法更清晰
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;&lt;strong&gt;多维数组&lt;/strong&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;typedef&lt;/strong&gt;：&lt;pre&gt;&lt;code&gt;typedef int (*Array2D)[10]; // 二维数组指针
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;using&lt;/strong&gt;：&lt;pre&gt;&lt;code&gt;using Array2D = int (*)[10]; // 更直观
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;&lt;strong&gt;模板类型&lt;/strong&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;typedef&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;typedef std::map&amp;lt;std::string, std::vector&amp;lt;int&amp;gt;&amp;gt;::iterator StrVecIter;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;（嵌套模板时，语法复杂，容易出错）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;using&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;using StrVecIter = std::map&amp;lt;std::string, std::vector&amp;lt;int&amp;gt;&amp;gt;::iterator;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;（语法更简洁）&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3&gt;&lt;strong&gt;4. 模板别名（Template Aliases）&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;using&lt;/strong&gt; 支持定义&lt;strong&gt;模板别名&lt;/strong&gt;，而 &lt;strong&gt;typedef&lt;/strong&gt; 无法直接实现这一点。&lt;/p&gt;
&lt;h4&gt;&lt;strong&gt;using 的模板别名&lt;/strong&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;template&amp;lt;typename T&amp;gt;
using MyVector = std::vector&amp;lt;T&amp;gt;; // 定义模板别名

MyVector&amp;lt;int&amp;gt; vec; // 等价于 std::vector&amp;lt;int&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;&lt;strong&gt;typedef 的局限性&lt;/strong&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;无法直接定义模板别名&lt;/strong&gt;：&lt;pre&gt;&lt;code&gt;// 错误！typedef 无法直接定义模板别名
template&amp;lt;typename T&amp;gt;
typedef std::vector&amp;lt;T&amp;gt; MyVector; // 编译报错
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3&gt;&lt;strong&gt;5. 匿名类型&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;两者均不能为&lt;strong&gt;匿名类型&lt;/strong&gt;创建别名：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 错误！匿名结构体无法通过 typedef 或 using 创建别名
typedef struct { int x; } Point; // 必须命名结构体
using Point = struct { int x; }; // 同样错误
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h3&gt;&lt;strong&gt;6. 可读性和现代性&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;using 更符合现代C++风格&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;语法更直观（&lt;code&gt;=&lt;/code&gt; 赋值操作）。&lt;/li&gt;
&lt;li&gt;在复杂类型（如函数指针、模板）中更易读。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;typedef&lt;/strong&gt; 语法更冗长，尤其在复杂类型中难以一眼看出类型结构。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3&gt;&lt;strong&gt;7. 其他特性&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;using 支持模板参数推导&lt;/strong&gt;：&lt;pre&gt;&lt;code&gt;template&amp;lt;typename T&amp;gt;
using Pair = std::pair&amp;lt;const T, T&amp;gt;;

Pair&amp;lt;int&amp;gt; p(1, 2); // 等价于 std::pair&amp;lt;const int, int&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;typedef 无法直接支持模板参数推导&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3&gt;&lt;strong&gt;总结：何时使用？&lt;/strong&gt;&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;场景&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;推荐使用&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;原因&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;基本类型或简单类型别名&lt;/td&gt;
&lt;td&gt;&lt;code&gt;using&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;语法更简洁直观。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;函数指针或复杂类型&lt;/td&gt;
&lt;td&gt;&lt;code&gt;using&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;typedef&lt;/code&gt; 的语法易混淆，&lt;code&gt;using&lt;/code&gt; 更清晰。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;模板别名&lt;/td&gt;
&lt;td&gt;&lt;code&gt;using&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;typedef&lt;/code&gt; 无法直接定义模板别名。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;向后兼容旧代码&lt;/td&gt;
&lt;td&gt;&lt;code&gt;typedef&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;如果代码需要兼容C或旧C++标准，可能需要保留 &lt;code&gt;typedef&lt;/code&gt;。&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h3&gt;&lt;strong&gt;示例对比&lt;/strong&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 传统 typedef
typedef std::map&amp;lt;std::string, int&amp;gt;::iterator StringIntIter;

// 现代 using
using StringIntIter = std::map&amp;lt;std::string, int&amp;gt;::iterator;

// 模板别名（仅 using 支持）
template&amp;lt;typename Key, typename Value&amp;gt;
using MyMap = std::map&amp;lt;Key, Value&amp;gt;;

MyMap&amp;lt;std::string, int&amp;gt; myMap;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通过使用 &lt;code&gt;using&lt;/code&gt;，代码的可读性和现代性显著提升，尤其在处理复杂类型时。而 &lt;code&gt;typedef&lt;/code&gt; 更适合兼容旧代码或简单类型别名。&lt;/p&gt;
</content:encoded></item><item><title>explicit关键字</title><link>http://zaoweiceng.github.io/posts/cpp/explicit/</link><guid isPermaLink="true">http://zaoweiceng.github.io/posts/cpp/explicit/</guid><description>explicit是C++中的一个关键字，用于修饰构造函数或转换函数，以防止隐式类型转换。</description><pubDate>Sat, 15 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;code&gt;explicit&lt;/code&gt;是C++中的一个关键字，用于修饰构造函数或转换函数，以防止隐式类型转换。它的主要目的是增加代码的清晰性和避免潜在的错误。&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;explicit&lt;/code&gt;与构造函数&lt;/h3&gt;
&lt;p&gt;在C++中，当某个类有一个单参数的构造函数时，这个构造函数通常会被用作从参数类型到该类的隐式类型转换。有时这种隐式转换可能会导致意外的结果或错误。
&lt;strong&gt;示例：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class MyClass {
public:
    MyClass(int value) { /* ... */ }
};
void func(MyClass obj) { /* ... */ }
int main() {
    func(10);  // 隐式地将int转换为MyClass
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在上面的代码中，&lt;code&gt;func(10)&lt;/code&gt;会隐式地调用&lt;code&gt;MyClass&lt;/code&gt;的构造函数，将&lt;code&gt;int&lt;/code&gt;类型的&lt;code&gt;10&lt;/code&gt;转换为&lt;code&gt;MyClass&lt;/code&gt;类型的对象。
&lt;strong&gt;使用&lt;code&gt;explicit&lt;/code&gt;：&lt;/strong&gt;
为了防止这种隐式转换，可以使用&lt;code&gt;explicit&lt;/code&gt;关键字来修饰构造函数：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class MyClass {
public:
    explicit MyClass(int value) { /* ... */ }
};
void func(MyClass obj) { /* ... */ }
int main() {
    func(10);  // 错误：无法隐式地将int转换为MyClass
    func(MyClass(10));  // 正确：显式地调用构造函数
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这个修改后的版本中，&lt;code&gt;func(10)&lt;/code&gt;会导致编译错误，因为&lt;code&gt;MyClass&lt;/code&gt;的构造函数被声明为&lt;code&gt;explicit&lt;/code&gt;，从而阻止了隐式类型转换。但你可以通过显式地调用构造函数来创建一个&lt;code&gt;MyClass&lt;/code&gt;对象，如&lt;code&gt;func(MyClass(10))&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;explicit&lt;/code&gt;与转换函数&lt;/h3&gt;
&lt;p&gt;除了构造函数，&lt;code&gt;explicit&lt;/code&gt;还可以用于修饰转换函数（即类型转换运算符）。
&lt;strong&gt;示例：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class MyClass {
public:
    operator int() const { /* ... */ return 0; }
};
void func(int value) { /* ... */ }
int main() {
    MyClass obj;
    func(obj);  // 隐式地将MyClass转换为int
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在上面的代码中，&lt;code&gt;func(obj)&lt;/code&gt;会隐式地调用&lt;code&gt;MyClass&lt;/code&gt;的转换函数，将&lt;code&gt;MyClass&lt;/code&gt;类型的&lt;code&gt;obj&lt;/code&gt;转换为&lt;code&gt;int&lt;/code&gt;类型。
&lt;strong&gt;使用&lt;code&gt;explicit&lt;/code&gt;：&lt;/strong&gt;
为了防止这种隐式转换，可以使用&lt;code&gt;explicit&lt;/code&gt;关键字来修饰转换函数：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class MyClass {
public:
    explicit operator int() const { /* ... */ return 0; }
};
void func(int value) { /* ... */ }
int main() {
    MyClass obj;
    func(obj);  // 错误：无法隐式地将MyClass转换为int
    func(static_cast&amp;lt;int&amp;gt;(obj));  // 正确：显式地调用转换函数
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这个修改后的版本中，&lt;code&gt;func(obj)&lt;/code&gt;会导致编译错误，因为&lt;code&gt;MyClass&lt;/code&gt;的转换函数被声明为&lt;code&gt;explicit&lt;/code&gt;，从而阻止了隐式类型转换。但你可以通过显式地使用&lt;code&gt;static_cast&lt;/code&gt;来执行类型转换，如&lt;code&gt;func(static_cast&amp;lt;int&amp;gt;(obj))&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;为什么使用&lt;code&gt;explicit&lt;/code&gt;？&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;避免意外行为&lt;/strong&gt;：隐式类型转换有时会导致意外的行为，特别是当转换不是程序员预期的时候。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;提高代码可读性&lt;/strong&gt;：显式类型转换使得代码的意图更明确，其他阅读代码的人可以更容易地理解类型转换的目的是什么。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;调试更简单&lt;/strong&gt;：当出现类型转换相关的问题时，如果使用了&lt;code&gt;explicit&lt;/code&gt;，编译器通常会提供更明确的错误信息，从而使得调试更简单。
总之，&lt;code&gt;explicit&lt;/code&gt;关键字是C++中一个非常有用的工具，可以帮助程序员编写更清晰、更健壮的代码。&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>JUnit</title><link>http://zaoweiceng.github.io/posts/java_web/junit/</link><guid isPermaLink="true">http://zaoweiceng.github.io/posts/java_web/junit/</guid><description>JUnit是一个Java语言的单元测试框架。它广泛用于测试小型到大型代码库，以确保代码的正确性和稳定性。</description><pubDate>Sat, 15 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;JUnit是一个Java语言的单元测试框架。它广泛用于测试小型到大型代码库，以确保代码的正确性和稳定性。以下是JUnit的详细用法：&lt;/p&gt;
&lt;h3&gt;JUnit 基本用法&lt;/h3&gt;
&lt;h4&gt;添加依赖&lt;/h4&gt;
&lt;p&gt;首先，你需要在项目的&lt;code&gt;pom.xml&lt;/code&gt;文件中添加JUnit的依赖：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.junit.jupiter&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;unit-jupiter&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;5.9.1&amp;lt;/version&amp;gt;
    &amp;lt;scope&amp;gt;test&amp;lt;/scope&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;编写测试类&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;使用&lt;code&gt;@Test&lt;/code&gt;注解标记测试方法。&lt;/li&gt;
&lt;li&gt;使用&lt;code&gt;assertEquals&lt;/code&gt;、&lt;code&gt;assertTrue&lt;/code&gt;、&lt;code&gt;assertFalse&lt;/code&gt;等断言方法验证结果。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;import org.junit.Test;
import static org.junit.Assert.*;
public class CalculatorTest {
    @Test
    public void testAdd() {
        Calculator calculator = new Calculator();
        int result = calculator.add(2, 3);
        assertEquals(5, result);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;命名规则：
&lt;ul&gt;
&lt;li&gt;类名：XXXXTest&lt;/li&gt;
&lt;li&gt;方法名：testXXXX&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;运行测试&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;使用IDE的测试运行器。&lt;/li&gt;
&lt;li&gt;使用构建工具（如Maven或Gradle）运行测试。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;JUnit 高级特性&lt;/h3&gt;
&lt;h4&gt;注解&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;注解&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;th&gt;备注&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;@Test&lt;/td&gt;
&lt;td&gt;测试类中的方法用它修饰才能成为测试方法，才能启动执行&lt;/td&gt;
&lt;td&gt;单元测试&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;@BeforeEach&lt;/td&gt;
&lt;td&gt;用来修饰一个实例方法，该方法会在每一个测试方法执行之前执行一次。&lt;/td&gt;
&lt;td&gt;初始化资源(准备工作)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;@AfterEach&lt;/td&gt;
&lt;td&gt;用来修饰一个实例方法，该方法会在每一个测试方法执行之后执行一次。&lt;/td&gt;
&lt;td&gt;释放资源(清理工作)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;@BeforeAll&lt;/td&gt;
&lt;td&gt;用来修饰一个静态方法，该方法会在所有测试方法之前只执行一次。&lt;/td&gt;
&lt;td&gt;初始化资源(准备工作)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;@AfterAll&lt;/td&gt;
&lt;td&gt;用来修饰一个静态方法，该方法会在所有测试方法之后只执行一次。&lt;/td&gt;
&lt;td&gt;释放资源(清理工作)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;@ParameterizedTest&lt;/td&gt;
&lt;td&gt;参数化测试的注解 (可以让单个测试运行多次，每次运行时仅参数不同)&lt;/td&gt;
&lt;td&gt;用了该注解，就不需要@Test注解了&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;@ValueSource&lt;/td&gt;
&lt;td&gt;参数化测试的参数来源，赋予测试方法参数&lt;/td&gt;
&lt;td&gt;与参数化测试注解配合使用&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;@DisplayName&lt;/td&gt;
&lt;td&gt;指定测试类、测试方法显示的名称 （默认为类名、方法名）&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;pre&gt;&lt;code&gt;public class CalculatorTest {
    @Before
    public void setUp() {
        // 初始化代码
    }
    @Test
    public void testAdd() {
        // 测试代码
    }
    @After
    public void tearDown() {
        // 清理代码
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;断言&lt;/h4&gt;
&lt;p&gt;Assertions下的静态方法：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;assertEquals(expected, actual)&lt;/code&gt;: 检查两个值是否相等。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;assertTrue(condition)&lt;/code&gt;: 检查条件是否为真。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;assertFalse(condition)&lt;/code&gt;: 检查条件是否为假。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;assertNull(object)&lt;/code&gt;: 检查对象是否为null。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;assertNotNull(object)&lt;/code&gt;: 检查对象是否不为null。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;assertSame(expected, actual)&lt;/code&gt;: 检查两个对象是否引用相同。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;assertNotSame(expected, actual)&lt;/code&gt;: 检查两个对象是否引用不同。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;每个方法最后都可以传入一个String类型的message用于错误的提示&lt;/p&gt;
&lt;h3&gt;JUnit 的新特性&lt;/h3&gt;
&lt;h4&gt;JUnit 5架构&lt;/h4&gt;
&lt;p&gt;JUnit 5由三个主要模块组成：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;JUnit Platform&lt;/strong&gt;: 为在JVM上启动测试提供基础设施。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;JUnit Jupiter&lt;/strong&gt;: JUnit 5的新测试引擎，提供注解、断言等。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;JUnit Vintage&lt;/strong&gt;: 允许在JUnit 5中运行JUnit 3和JUnit 4的测试。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;注解改进&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;@BeforeEach&lt;/code&gt;: 替代了JUnit 4的&lt;code&gt;@Before&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@AfterEach&lt;/code&gt;: 替代了JUnit 4的&lt;code&gt;@After&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@BeforeAll&lt;/code&gt;: 替代了JUnit 4的&lt;code&gt;@BeforeClass&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@AfterAll&lt;/code&gt;: 替代了JUnit 4的&lt;code&gt;@AfterClass&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@DisplayName&lt;/code&gt;: 为测试类或方法提供显示名称。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@Nested&lt;/code&gt;: 允许编写嵌套的测试类。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;断言改进&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;assertAll()&lt;/code&gt;: 组合多个断言，即使某个断言失败，也能执行所有断言。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;assertThrows()&lt;/code&gt;: 检查代码是否抛出特定的异常。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;public class CalculatorTest {
    @Test
    public void testAdd() {
        Calculator calculator = new Calculator();
        assertAll(
            () -&amp;gt; assertEquals(2, calculator.add(1, 1)),
            () -&amp;gt; assertEquals(4, calculator.add(2, 2))
        );
    }
    @Test
    public void testDivide() {
        Calculator calculator = new Calculator();
        assertThrows(ArithmeticException.class, () -&amp;gt; calculator.divide(1, 0));
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;参数化测试改进&lt;/h4&gt;
&lt;p&gt;JUnit 5提供了更简洁的参数化测试方式。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class ParameterizedTest {
    @ParameterizedTest
    @CsvSource({
        &quot;1, 1, 2&quot;,
        &quot;2, 2, 4&quot;,
        &quot;3, 3, 6&quot;
    })
    public void testAdd(int numberA, int numberB, int expected) {
        Calculator calculator = new Calculator();
        assertEquals(expected, calculator.add(numberA, numberB));
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;示例&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;package com.auroralilis;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.ValueSource;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class demoTest {

    @Test
    public void testDemo(){
        int a = 1;
    }
    @Test
    public void testAsert(){
        int a = 1;
        assertEquals(a,1, &quot;这是一个提示信息&quot;);
    }
    @Test
    public void testException(){
        Assertions.assertThrows(ArithmeticException.class, () -&amp;gt; {
            int i = 1, b = 0;
            int i1 = i / b;
        });
    }
    @ParameterizedTest
    @CsvSource({
            &quot;1, 1, 2&quot;,
            &quot;2, 2, 4&quot;,
            &quot;3, 3, 6&quot;
    })
    public void testAdd(int numberA, int numberB, int expected) {
        assertEquals(expected, (numberA + numberB));
    }
    @DisplayName(&quot;输出输入数字&quot;)
    @ParameterizedTest
    @ValueSource(ints = {1, 2, 3})
    public void testSub(int numberA) {
        System.out.println(numberA);
    }
}

&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>C++右值及相关用法</title><link>http://zaoweiceng.github.io/posts/cpp/%E7%A7%BB%E5%8A%A8%E8%AF%AD%E4%B9%89/</link><guid isPermaLink="true">http://zaoweiceng.github.io/posts/cpp/%E7%A7%BB%E5%8A%A8%E8%AF%AD%E4%B9%89/</guid><description>C++右值、右值引用、移动语义、完美转发</description><pubDate>Fri, 14 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;右值和右值引用&lt;/h2&gt;
&lt;h3&gt;基本概念&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;左值（Lvalue）&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;可以出现在赋值语句的左边。&lt;/li&gt;
&lt;li&gt;有持久性的内存地址，可以取地址。&lt;/li&gt;
&lt;li&gt;例如：变量、对象等。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;右值（Rvalue）&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;只能出现在赋值语句的右边。&lt;/li&gt;
&lt;li&gt;没有持久性的内存地址，不能取地址。&lt;/li&gt;
&lt;li&gt;例如：字面量、临时对象、表达式结果等。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;右值引用&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;右值引用&lt;/strong&gt;是C++11引入的新特性，使用&lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt;表示。它允许我们引用即将被销毁的临时对象，从而避免不必要的拷贝，提高程序性能。
&lt;strong&gt;语法&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Type&amp;amp;&amp;amp; 右值引用变量 = 右值;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;示例&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int a = 10;
int&amp;amp;&amp;amp; rref = 20; // 20是右值，rref是右值引用
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;右值引用的作用&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;避免不必要的拷贝&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在函数参数传递、返回值等场景中，使用右值引用可以避免临时对象的拷贝，提高效率。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;实现移动语义&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;通过右值引用，可以定义移动构造函数和移动赋值运算符，实现资源的移动而不是拷贝。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;完美转发&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在模板函数中，使用右值引用可以实现参数的完美转发，保持参数的原始类型（左值或右值）。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;移动语义&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;移动构造函数&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用于初始化新对象，将另一个对象的资源“移动”到新对象中。&lt;/li&gt;
&lt;li&gt;语法：&lt;code&gt;ClassName::ClassName(ClassName&amp;amp;&amp;amp; other)&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;移动赋值运算符&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;用于将一个对象的资源“移动”到另一个对象中。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;语法：&lt;code&gt;ClassName&amp;amp; ClassName::operator=(ClassName&amp;amp;&amp;amp; other)&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;示例&lt;/strong&gt;：&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;class MyString {
public:
    MyString(MyString&amp;amp;&amp;amp; other) { // 移动构造函数
        // 移动资源
    }
    MyString&amp;amp; operator=(MyString&amp;amp;&amp;amp; other) { // 移动赋值运算符
        // 移动资源
        return *this;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;完美转发&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;完美转发&lt;/strong&gt;是指在进行函数参数传递时，保持参数的原始类型（左值或右值）。
&lt;strong&gt;语法&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用&lt;code&gt;std::forward&lt;/code&gt;函数模板实现完美转发。
&lt;strong&gt;示例&lt;/strong&gt;：&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;template&amp;lt;typename T&amp;gt;
void func(T&amp;amp;&amp;amp; param) {
    anotherFunc(std::forward&amp;lt;T&amp;gt;(param));
}
void anotherFunc(int&amp;amp; a) { // 接收左值
    // ...
}
void anotherFunc(int&amp;amp;&amp;amp; a) { // 接收右值
    // ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;注意事项&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;右值引用本身是左值，可以取地址。&lt;/li&gt;
&lt;li&gt;右值引用延长了临时对象的生命周期。&lt;/li&gt;
&lt;li&gt;避免对同一右值引用进行多次移动操作。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;应用场景&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;容器操作（如&lt;code&gt;std::vector&lt;/code&gt;的&lt;code&gt;push_back&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;智能指针（如&lt;code&gt;std::unique_ptr&lt;/code&gt;、&lt;code&gt;std::shared_ptr&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;函数返回值优化（RVO）和命名返回值优化（NRVO）。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;完美转发&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;完美转发&lt;/strong&gt;（Perfect Forwarding）是C++11引入的一种技术，它允许我们在函数模板中传递参数时，保持参数的原始类型（无论是左值还是右值），同时避免不必要的拷贝。这种技术主要利用了右值引用和&lt;code&gt;std::forward&lt;/code&gt;函数模板。&lt;/p&gt;
&lt;h3&gt;为什么需要完美转发？&lt;/h3&gt;
&lt;p&gt;在C++中，函数参数默认是按值传递的，这意味着无论传递的是左值还是右值，都会发生拷贝。但是，有时候我们希望传递参数时能够保持其原始类型，以便能够利用移动语义（对于右值）来避免不必要的拷贝，从而提高性能。&lt;/p&gt;
&lt;h3&gt;如何实现完美转发？&lt;/h3&gt;
&lt;p&gt;完美转发主要通过以下两个关键点实现：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;右值引用&lt;/strong&gt;：使用&lt;code&gt;T&amp;amp;&amp;amp;&lt;/code&gt;类型的参数来接收传递进来的参数。这种类型的参数可以绑定到任何类型的实参上，包括左值和右值。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;std::forward&lt;/code&gt;&lt;/strong&gt;：这是一个函数模板，用于将参数“完美”地转发给另一个函数。它能够保持参数的原始类型（左值或右值）。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;示例&lt;/h3&gt;
&lt;p&gt;以下是一个简单的示例，展示了如何使用完美转发：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;utility&amp;gt; // for std::forward
void process(int&amp;amp; x) {
    std::cout &amp;lt;&amp;lt; &quot;Processing lvalue: &quot; &amp;lt;&amp;lt; x &amp;lt;&amp;lt; std::endl;
}
void process(int&amp;amp;&amp;amp; x) {
    std::cout &amp;lt;&amp;lt; &quot;Processing rvalue: &quot; &amp;lt;&amp;lt; x &amp;lt;&amp;lt; std::endl;
}
template&amp;lt;typename T&amp;gt;
void wrapper(T&amp;amp;&amp;amp; param) {
    process(std::forward&amp;lt;T&amp;gt;(param)); // 完美转发
}
int main() {
    int a = 10;
    wrapper(a);  // 传递左值
    wrapper(20); // 传递右值
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这个示例中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;wrapper&lt;/code&gt;函数模板接收一个类型为&lt;code&gt;T&amp;amp;&amp;amp;&lt;/code&gt;的参数，这意味着它可以接收任何类型的实参。&lt;/li&gt;
&lt;li&gt;在&lt;code&gt;wrapper&lt;/code&gt;内部，我们使用&lt;code&gt;std::forward&amp;lt;T&amp;gt;(param)&lt;/code&gt;将参数转发给&lt;code&gt;process&lt;/code&gt;函数。&lt;code&gt;std::forward&lt;/code&gt;会根据&lt;code&gt;param&lt;/code&gt;的原始类型（左值或右值）来决定是进行左值转发还是右值转发。
输出结果如下：&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;Processing lvalue: 10
Processing rvalue: 20
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这表明&lt;code&gt;wrapper&lt;/code&gt;函数成功地保持了参数的原始类型，并将它们“完美”地转发给了&lt;code&gt;process&lt;/code&gt;函数。&lt;/p&gt;
&lt;p&gt;完美转发在C++中有很多具体的应用场景，它允许开发者编写更通用、更高效的代码。以下是一些常见的应用场景：&lt;/p&gt;
&lt;h3&gt;泛型编程&lt;/h3&gt;
&lt;p&gt;在泛型编程中，我们经常需要编写可以接受任何类型参数的函数模板。完美转发允许这些函数模板在接收参数时保持参数的原始类型，从而可以正确地处理左值和右值。
&lt;strong&gt;示例：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;template&amp;lt;typename T&amp;gt;
void universalFunction(T&amp;amp;&amp;amp; arg) {
    // 处理arg...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;封装转发函数&lt;/h3&gt;
&lt;p&gt;有时我们需要封装一个函数，使其能够接受任意类型的参数，并将这些参数转发给另一个函数。完美转发可以实现这一点，而不丢失参数的原始类型信息。
&lt;strong&gt;示例：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;void underlyingFunction(int&amp;amp; x) {
    // 处理左值...
}
void underlyingFunction(int&amp;amp;&amp;amp; x) {
    // 处理右值...
}
template&amp;lt;typename T&amp;gt;
void wrapperFunction(T&amp;amp;&amp;amp; arg) {
    underlyingFunction(std::forward&amp;lt;T&amp;gt;(arg));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;智能指针&lt;/h3&gt;
&lt;p&gt;智能指针（如&lt;code&gt;std::unique_ptr&lt;/code&gt;和&lt;code&gt;std::shared_ptr&lt;/code&gt;）在内部使用完美转发来传递资源所有权。这允许智能指针在构造时接受左值或右值，从而实现资源的移动或拷贝。
&lt;strong&gt;示例：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;std::unique_ptr&amp;lt;int&amp;gt; ptr1 = std::make_unique&amp;lt;int&amp;gt;(10);
std::unique_ptr&amp;lt;int&amp;gt; ptr2 = std::move(ptr1); // 移动所有权
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;容器操作&lt;/h3&gt;
&lt;p&gt;标准库容器（如&lt;code&gt;std::vector&lt;/code&gt;）在插入元素时使用完美转发来避免不必要的拷贝。例如，&lt;code&gt;push_back&lt;/code&gt;和&lt;code&gt;emplace_back&lt;/code&gt;方法可以利用完美转发来插入元素。
&lt;strong&gt;示例：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;std::vector&amp;lt;std::string&amp;gt; vec;
vec.push_back(&quot;Hello&quot;); // 拷贝
vec.emplace_back(std::string(&quot;World&quot;)); // 移动
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;绑定器和解引用器&lt;/h3&gt;
&lt;p&gt;在函数绑定（如&lt;code&gt;std::bind&lt;/code&gt;）和延迟调用（如&lt;code&gt;std::function&lt;/code&gt;）的场景中，完美转发可以确保传递给绑定函数的参数保持其原始类型。
&lt;strong&gt;示例：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;auto func = std::bind(underlyingFunction, std::placeholders::_1);
func(10); // 传递右值
int x = 20;
func(x); // 传递左值
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;高级工厂函数&lt;/h3&gt;
&lt;p&gt;在工厂模式中，完美转发可以用于创建对象，并根据传递的参数类型（左值或右值）来决定是拷贝还是移动资源。
&lt;strong&gt;示例：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;template&amp;lt;typename T, typename... Args&amp;gt;
std::unique_ptr&amp;lt;T&amp;gt; make_unique(Args&amp;amp;&amp;amp;... args) {
    return std::unique_ptr&amp;lt;T&amp;gt;(new T(std::forward&amp;lt;Args&amp;gt;(args)...));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;自定义控制结构&lt;/h3&gt;
&lt;p&gt;在编写自定义的控制结构（如循环、条件语句等）时，完美转发可以确保传递给这些结构的参数保持其原始类型。
&lt;strong&gt;示例：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;template&amp;lt;typename Func, typename... Args&amp;gt;
void customControlStructure(Func&amp;amp;&amp;amp; func, Args&amp;amp;&amp;amp;... args) {
    if (/* some condition */) {
        func(std::forward&amp;lt;Args&amp;gt;(args)...);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;“完美转发允许这些函数模板在接收参数时保持参数的原始类型”&lt;/strong&gt; 这句话的意思是，在使用完美转发的情况下，当函数模板接收参数时，无论这些参数是左值还是右值，它们都能保持其原始的类型信息。这样，函数模板就可以根据参数的原始类型来决定如何处理这些参数，例如，对于右值参数，可以采用移动语义来避免不必要的拷贝，从而提高性能。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;不使用完美转发会丢失原始类型吗？&lt;/strong&gt;
是的，如果不使用完美转发，那么在函数模板中接收参数时，可能会丢失参数的原始类型信息。具体来说：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;按值传递&lt;/strong&gt;：如果函数模板的参数是按值传递的，那么无论传递的是左值还是右值，都会被拷贝成一个新的左值。这样，原始的右值类型信息就会丢失，无法利用移动语义。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;按引用传递&lt;/strong&gt;：如果函数模板的参数是按引用传递的，那么可以保持参数的左值或右值属性，但需要编写两个重载版本（一个用于左值引用，一个用于右值引用），这会增加代码的复杂性。.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;完美转发的优势&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;保持原始类型&lt;/strong&gt;：完美转发可以保持参数的原始类型信息，无论是左值还是右值。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;通用性&lt;/strong&gt;：通过使用函数模板和完美转发，可以编写通用的代码，处理各种类型的参数。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;性能优化&lt;/strong&gt;：利用移动语义，可以避免不必要的拷贝，提高程序的性能。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;移动语义&lt;/h2&gt;
&lt;h3&gt;背景&lt;/h3&gt;
&lt;p&gt;在 C++11 之前，资源管理主要依赖于拷贝构造函数和拷贝赋值运算符。然而，这种机制在处理一些资源（如动态内存、文件句柄等）时，效率并不高。为了解决这个问题，C++11 引入了移动语义。&lt;/p&gt;
&lt;h3&gt;移动语义的基本概念&lt;/h3&gt;
&lt;p&gt;移动语义允许将资源从一个对象“移动”到另一个对象，而不是复制。这样做的好处是可以提高资源管理的效率，减少不必要的内存分配和释放。&lt;/p&gt;
&lt;h3&gt;移动构造函数&lt;/h3&gt;
&lt;p&gt;移动构造函数是一种特殊的构造函数，用于初始化新对象，使其接管另一个对象的资源。移动构造函数的声明形式如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ClassName(ClassName&amp;amp;&amp;amp; other);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里，&lt;code&gt;ClassName&amp;amp;&amp;amp;&lt;/code&gt; 表示右值引用。右值引用可以绑定到即将被销毁的临时对象上，从而允许移动构造函数“窃取”这些资源。&lt;/p&gt;
&lt;h3&gt;移动赋值运算符&lt;/h3&gt;
&lt;p&gt;移动赋值运算符与移动构造函数类似，但用于将一个对象的资源移动到另一个已存在的对象。其声明形式如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ClassName&amp;amp; operator=(ClassName&amp;amp;&amp;amp; other);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;右值引用&lt;/h3&gt;
&lt;p&gt;右值引用是移动语义的核心。它允许我们区分左值和右值，从而实现资源的移动。左值是具有持久性的对象，而右值是临时对象，如函数返回值或表达式结果。&lt;/p&gt;
&lt;h3&gt;std::move&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;std::move&lt;/code&gt; 是一个用于将左值转换为右值引用的函数。它并不真正移动任何资源，而是告诉编译器我们希望进行移动操作。例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ClassName obj1;
ClassName obj2 = std::move(obj1); // 调用移动构造函数
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;应用场景&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;动态内存管理&lt;/strong&gt;：当对象包含指向动态分配内存的指针时，使用移动语义可以避免不必要的内存复制。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;容器操作&lt;/strong&gt;：如 &lt;code&gt;std::vector&lt;/code&gt; 的 &lt;code&gt;push_back&lt;/code&gt; 方法，当插入右值时，可以避免复制元素。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;函数返回值&lt;/strong&gt;：当函数返回一个局部对象时，使用移动语义可以避免复制该对象。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;注意事项&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;资源管理&lt;/strong&gt;：确保在移动构造函数和移动赋值运算符中正确管理资源，避免内存泄漏。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;自赋值安全&lt;/strong&gt;：移动赋值运算符应处理自赋值情况，以避免破坏对象状态。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;兼容性&lt;/strong&gt;：确保移动构造函数和移动赋值运算符与拷贝构造函数和拷贝赋值运算符兼容。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;示例&lt;/h3&gt;
&lt;p&gt;以下是一个简单的示例，展示如何为包含动态内存的对象实现移动语义：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;string&amp;gt;
class MyString {
public:
    // 构造函数
    MyString(const char* str) {
        size = strlen(str);
        data = new char[size + 1];
        strcpy(data, str);
    }
    // 移动构造函数
    MyString(MyString&amp;amp;&amp;amp; other) noexcept : data(other.data), size(other.size) {
        other.data = nullptr;
        other.size = 0;
    }
    // 移动赋值运算符
    MyString&amp;amp; operator=(MyString&amp;amp;&amp;amp; other) noexcept {
        if (this != &amp;amp;other) {
            delete[] data;
            data = other.data;
            size = other.size;
            other.data = nullptr;
            other.size = 0;
        }
        return *this;
    }
    // 析构函数
    ~MyString() {
        delete[] data;
    }
    void print() const {
        std::cout &amp;lt;&amp;lt; data &amp;lt;&amp;lt; std::endl;
    }
private:
    char* data;
    size_t size;
};
int main() {
    MyString str1(&quot;Hello&quot;);
    MyString str2 = std::move(str1); // 调用移动构造函数
    str2.print(); // 输出 &quot;Hello&quot;
    MyString str3(&quot;World&quot;);
    str3 = std::move(str2); // 调用移动赋值运算符
    str3.print(); // 输出 &quot;Hello&quot;
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这个示例中，&lt;code&gt;MyString&lt;/code&gt; 类实现了移动构造函数和移动赋值运算符，以高效地管理动态内存。&lt;/p&gt;
&lt;h2&gt;&lt;code&gt;std::move&lt;/code&gt;的应用示例&lt;/h2&gt;
&lt;h3&gt;如何在类对象中使用移动语义来优化性能&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;vector&amp;gt;
#include &amp;lt;utility&amp;gt; // for std::move

using namespace std;

class MyClass {
public:
    vector&amp;lt;int&amp;gt; data;

    // 默认构造函数
    MyClass() {
        cout &amp;lt;&amp;lt; &quot;Default constructor&quot; &amp;lt;&amp;lt; endl;
    }

    // 移动构造函数
    MyClass(MyClass&amp;amp;&amp;amp; other) noexcept : data(std::move(other.data)) {
        cout &amp;lt;&amp;lt; &quot;Move constructor&quot; &amp;lt;&amp;lt; endl;
    }

    // 移动赋值运算符
    MyClass&amp;amp; operator=(MyClass&amp;amp;&amp;amp; other) noexcept {
        cout &amp;lt;&amp;lt; &quot;Move assignment operator&quot; &amp;lt;&amp;lt; endl;
        if (this != &amp;amp;other) {
            data = std::move(other.data);
        }
        return *this;
    }
};

MyClass createObject() {
    MyClass obj;
    obj.data.push_back(1);
    obj.data.push_back(2);
    obj.data.push_back(3);
    return obj;
}

int main() {
    MyClass obj1 = createObject(); // 使用移动构造函数
    MyClass obj2 = std::move(obj1); // 使用移动赋值运算符

    cout &amp;lt;&amp;lt; &quot;obj1 data size: &quot; &amp;lt;&amp;lt; obj1.data.size() &amp;lt;&amp;lt; endl; // obj1 的数据已被转移
    cout &amp;lt;&amp;lt; &quot;obj2 data size: &quot; &amp;lt;&amp;lt; obj2.data.size() &amp;lt;&amp;lt; endl; // obj2 拥有 obj1 的数据

    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;std::move实现一个高效的swap函数&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;utility&amp;gt; // for std::move

template &amp;lt;typename T&amp;gt;
void swap(T&amp;amp; a, T&amp;amp; b) {
    T temp = std::move(a); // 将a的状态转移到temp
    a = std::move(b);      // 将b的状态转移到a
    b = std::move(temp);   // 将temp的状态转移到b
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这种方式和传统使用临时变量（temp）的方式的主要区别在于性能和资源管理：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;性能：使用std::move的swap函数可以避免不必要的复制操作。在传统的方式中，对象的复制可能涉及到深拷贝，特别是当对象管理着资源（如动态分配的内存）时。而std::move通过转移资源的所有权来避免复制，这通常是一个更快的操作。&lt;/li&gt;
&lt;li&gt;资源管理：当对象包含资源（如动态内存、文件句柄等）时，使用std::move可以确保资源正确地从一个对象转移到另一个对象，而不会发生资源的双重释放或者泄漏。在传统的方式中，资源的复制和释放可能会导致性能损失或者资源管理错误。&lt;/li&gt;
&lt;li&gt;异常安全：使用std::move的swap函数在异常安全方面通常更好。因为移动操作通常不会抛出异常（除非移动构造函数或移动赋值运算符被显式定义为可能抛出异常），所以这种方式提供的swap函数在异常情况下更不容易出错。&lt;/li&gt;
&lt;li&gt;适用性：使用std::move的swap函数适用于任何支持移动语义的类型，包括那些不支持拷贝语义但支持移动语义的类型（如某些只能移动不能复制的对象）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;从代码表面上看，使用&lt;code&gt;std::move&lt;/code&gt;的swap函数和传统使用临时变量的swap函数都定义了一个临时变量，并且似乎都有额外的内存开销。然而，这里的区别在于对象的状态转移方式，而不是是否使用了临时变量。&lt;/p&gt;
&lt;p&gt;在传统的方式中，每次赋值操作都可能涉及到对象的拷贝构造函数，如果对象包含大量数据或复杂资源，这将导致显著的性能开销。&lt;/p&gt;
&lt;p&gt;在使用&lt;code&gt;std::move&lt;/code&gt;的方式中，赋值操作涉及到的是对象的移动构造函数或移动赋值运算符。这些函数的设计目的是转移对象的状态而不是复制它，因此它们通常不涉及深拷贝，而是进行浅拷贝或资源所有权的转移。这意味着性能开销通常远小于深拷贝。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;关键区别：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;拷贝 vs. 移动&lt;/strong&gt;：传统方式中发生的是拷贝，而&lt;code&gt;std::move&lt;/code&gt;方式中发生的是移动。移动操作通常比拷贝操作更高效，尤其是对于资源密集型的对象。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;资源管理&lt;/strong&gt;：移动语义允许更安全的资源管理，因为它减少了资源复制和释放的次数，从而降低了出错的可能性。
&lt;strong&gt;关于临时变量的内存开销：&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;在两种方式中，临时变量的内存开销是相似的，但是这个开销通常很小，特别是与可能避免的深拷贝开销相比。&lt;/li&gt;
&lt;li&gt;对于某些类型，编译器可能能够优化临时变量的使用，例如通过使用返回值优化（RVO）或命名返回值优化（NRVO）。
总之，虽然两种方式都使用了临时变量，但使用&lt;code&gt;std::move&lt;/code&gt;的swap函数在性能和资源管理方面通常更优，因为它利用了移动语义来减少不必要的拷贝操作。&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Java多线程</title><link>http://zaoweiceng.github.io/posts/java/thread/</link><guid isPermaLink="true">http://zaoweiceng.github.io/posts/java/thread/</guid><description>Java的多线程的基本用法</description><pubDate>Fri, 14 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;线程&lt;/h2&gt;
&lt;h3&gt;实现多线程方式一：继承Thread类&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;方法介绍&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;void run()&lt;/td&gt;
&lt;td&gt;在线程开启后，此方法将被调用执行&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;void start()&lt;/td&gt;
&lt;td&gt;使此线程开始执行，Java虚拟机会调用run方法()&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;实现步骤&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;定义一个类MyThread继承Thread类&lt;/li&gt;
&lt;li&gt;在MyThread类中重写run()方法&lt;/li&gt;
&lt;li&gt;创建MyThread类的对象&lt;/li&gt;
&lt;li&gt;启动线程&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;代码演示&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class MyThread extends Thread {
    @Override
    public void run() {
        for(int i=0; i&amp;lt;100; i++) {
            System.out.println(i);
        }
    }
}
public class MyThreadDemo {
    public static void main(String[] args) {
        MyThread my1 = new MyThread();
        MyThread my2 = new MyThread();
      //start() 导致此线程开始执行; Java虚拟机调用此线程的run方法
        my1.start();
        my2.start();
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;两个小问题&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;为什么要重写run()方法？&lt;/p&gt;
&lt;p&gt;因为run()是用来封装被线程执行的代码&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;run()方法和start()方法的区别？&lt;/p&gt;
&lt;p&gt;run()：封装线程执行的代码，直接调用，相当于普通方法的调用&lt;/p&gt;
&lt;p&gt;start()：启动线程；然后由JVM调用此线程的run()方法&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;缺点：单继承，不能再继承其它类&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;实现多线程方式二：实现Runnable接口&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Thread构造方法&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Thread(Runnable target)&lt;/td&gt;
&lt;td&gt;分配一个新的Thread对象&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Thread(Runnable target, String name)&lt;/td&gt;
&lt;td&gt;分配一个新的Thread对象&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;实现步骤&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;定义一个类MyRunnable实现Runnable接口&lt;/li&gt;
&lt;li&gt;在MyRunnable类中重写run()方法&lt;/li&gt;
&lt;li&gt;创建MyRunnable类的对象&lt;/li&gt;
&lt;li&gt;创建Thread类的对象，把MyRunnable对象作为构造方法的参数&lt;/li&gt;
&lt;li&gt;启动线程&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;代码演示&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class MyRunnable implements Runnable {
    @Override
    public void run() {
        for(int i=0; i&amp;lt;100; i++) {
            System.out.println(Thread.currentThread().getName()+&quot;:&quot;+i);
        }
    }
}
public class MyRunnableDemo {
    public static void main(String[] args) {
        //创建MyRunnable类的对象
        MyRunnable my = new MyRunnable();

        //创建Thread类的对象，把MyRunnable对象作为构造方法的参数
        //Thread(Runnable target)
//        Thread t1 = new Thread(my);
//        Thread t2 = new Thread(my);
        //Thread(Runnable target, String name)
        Thread t1 = new Thread(my,&quot;坦克&quot;);
        Thread t2 = new Thread(my,&quot;飞机&quot;);

        //启动线程
        t1.start();
        t2.start();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在Java中，&lt;code&gt;Thread.yield()&lt;/code&gt; 是一个静态方法，用于使当前正在执行的线程让出CPU时间片，允许其他线程（如果有）在当前线程之前被调度执行。调用&lt;code&gt;yield()&lt;/code&gt;方法的线程会从运行状态（Running）转到可运行状态（Runnable），即就绪状态，并有可能在下一个线程调度周期中被再次选中执行。
&lt;code&gt;yield()&lt;/code&gt;方法的主要目的是提高线程之间的执行效率，它是一种非阻塞的线程调度方式。当当前线程认为它需要让出CPU以允许其他线程运行时，可以调用此方法。这通常用于实现某种程度的线程间协作，尤其是在多处理器系统中。
然而，需要注意的是，&lt;code&gt;yield()&lt;/code&gt;方法的行为依赖于具体的线程调度器实现，不同的操作系统和JVM实现可能会有不同的行为。因此，&lt;code&gt;yield()&lt;/code&gt;方法的效果可能并不总是如预期的那样明显或一致。
在实际应用中，&lt;code&gt;yield()&lt;/code&gt;方法的使用并不常见，因为它的行为可能难以预测，并且通常有更好的方法来实现线程间的协作和同步，例如使用&lt;code&gt;wait()&lt;/code&gt;/&lt;code&gt;notify()&lt;/code&gt;机制或&lt;code&gt;java.util.concurrent&lt;/code&gt;包中的高级并发工具。&lt;/p&gt;
&lt;h3&gt;实现多线程方式三: 实现Callable接口&lt;/h3&gt;
&lt;p&gt;第一、第二的方式没办法获取到多线程运行的结果，本方法可以。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;方法介绍&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;V call()&lt;/td&gt;
&lt;td&gt;计算结果，如果无法计算结果，则抛出一个异常&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FutureTask(Callable&amp;lt;V&amp;gt; callable)&lt;/td&gt;
&lt;td&gt;创建一个 FutureTask，一旦运行就执行给定的 Callable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;V get()&lt;/td&gt;
&lt;td&gt;如有必要，等待计算完成，然后获取其结果&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;实现步骤&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;定义一个类MyCallable实现Callable接口&lt;/li&gt;
&lt;li&gt;在MyCallable类中重写call()方法&lt;/li&gt;
&lt;li&gt;创建MyCallable类的对象&lt;/li&gt;
&lt;li&gt;创建Future的实现类FutureTask对象，把MyCallable对象作为构造方法的参数&lt;/li&gt;
&lt;li&gt;创建Thread类的对象，把FutureTask对象作为构造方法的参数&lt;/li&gt;
&lt;li&gt;启动线程&lt;/li&gt;
&lt;li&gt;再调用get方法，就可以获取线程结束之后的结果。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;代码演示&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class MyCallable implements Callable&amp;lt;Integer&amp;gt; {
    @Override
    public String call() throws Exception {
        int sum = 0;
        for (int i = 0; i &amp;lt; 100; i++) {
            sum += i;
        }
        //返回值就表示线程运行完毕之后的结果
        return sum;
    }
}
public class demoCallable {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //线程开启之后需要执行里面的call方法
      myCallable mc = new myCallable();
        //可以获取线程执行完毕之后的结果.也可以作为参数传递给Thread对象
      FutureTask&amp;lt;Integer&amp;gt; ft = new FutureTask&amp;lt;&amp;gt;(mc);
        //创建线程对象
        Thread t1 = new Thread(ft);
      //开启线程
        t1.start();
        int s = ft.get();
      System.out.println(s);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;三种实现方式的对比&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;实现Runnable、Callable接口
&lt;ul&gt;
&lt;li&gt;好处: 扩展性强，实现该接口的同时还可以继承其他的类&lt;/li&gt;
&lt;li&gt;缺点: 编程相对复杂，不能直接使用Thread类中的方法，如果需要使用，需要现获取到Thread的对象(通过静态方法)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;继承Thread类
&lt;ul&gt;
&lt;li&gt;好处: 编程比较简单，可以直接使用Thread类中的方法&lt;/li&gt;
&lt;li&gt;缺点: 可以扩展性较差，不能再继承其他的类&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;设置和获取线程名称&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;方法介绍&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;void  setName(String name)&lt;/td&gt;
&lt;td&gt;将此线程的名称更改为等于参数name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;String  getName()&lt;/td&gt;
&lt;td&gt;返回此线程的名称&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Thread  currentThread()&lt;/td&gt;
&lt;td&gt;返回对当前正在执行的线程对象的引用&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;代码演示&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class MyThread extends Thread {
    public MyThread() {}
    public MyThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        for (int i = 0; i &amp;lt; 100; i++) {
            System.out.println(getName()+&quot;:&quot;+i);
        }
    }
}
public class MyThreadDemo {
    public static void main(String[] args) {
        MyThread my1 = new MyThread();
        MyThread my2 = new MyThread();

        //void setName(String name)：将此线程的名称更改为等于参数 name
        my1.setName(&quot;高铁&quot;);
        my2.setName(&quot;飞机&quot;);

        //Thread(String name)
        MyThread my1 = new MyThread(&quot;高铁&quot;);
        MyThread my2 = new MyThread(&quot;飞机&quot;);

        my1.start();
        my2.start();

        //static Thread currentThread() 返回对当前正在执行的线程对象的引用
        System.out.println(Thread.currentThread().getName());
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Thread类中的常用方法&lt;/h3&gt;
&lt;p&gt;下面是一个表格，列出了Java中&lt;code&gt;Thread&lt;/code&gt;类的一些常用方法，包括方法的名称、描述和简单的用法示例：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名称&lt;/th&gt;
&lt;th&gt;描述&lt;/th&gt;
&lt;th&gt;用法示例&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;start()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;启动线程，调用线程的&lt;code&gt;run()&lt;/code&gt;方法。&lt;/td&gt;
&lt;td&gt;&lt;code&gt;thread.start();&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;run()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;线程的执行体，包含线程要执行的代码。&lt;/td&gt;
&lt;td&gt;&lt;code&gt;public void run() { // code to execute }&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;join()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;等待调用该方法的线程结束（进程插队）。&lt;/td&gt;
&lt;td&gt;&lt;code&gt;thread.join();&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;static void sleep(long millis)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;使当前线程暂停执行指定的时间（毫秒）。&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Thread.sleep(1000);&lt;/code&gt; // 暂停1秒&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;interrupt()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;中断线程，如果线程处于阻塞状态，会抛出&lt;code&gt;InterruptedException&lt;/code&gt;。&lt;/td&gt;
&lt;td&gt;&lt;code&gt;thread.interrupt();&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;isAlive()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;检查线程是否处于活动状态。&lt;/td&gt;
&lt;td&gt;&lt;code&gt;if (thread.isAlive()) { // code }&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;getName()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;获取线程的名称。&lt;/td&gt;
&lt;td&gt;&lt;code&gt;String name = thread.getName();&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;setName(String name)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;设置线程的名称。&lt;/td&gt;
&lt;td&gt;&lt;code&gt;thread.setName(&quot;MyThread&quot;);&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;getPriority()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;获取线程的优先级。&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int priority = thread.getPriority();&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;setPriority(int newPriority)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;设置线程的优先级。&lt;/td&gt;
&lt;td&gt;&lt;code&gt;thread.setPriority(Thread.MAX_PRIORITY);&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;currentThread()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;获取当前正在执行的线程对象。&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Thread currentThread = Thread.currentThread();&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;isDaemon()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;检查线程是否为守护线程。&lt;/td&gt;
&lt;td&gt;&lt;code&gt;if (thread.isDaemon()) { // code }&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;setDaemon(boolean on)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;将线程设置为守护线程或用户线程。&lt;/td&gt;
&lt;td&gt;&lt;code&gt;thread.setDaemon(true);&lt;/code&gt; // 设置为守护线程&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;yield()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;让出线程&lt;/td&gt;
&lt;td&gt;可以让线程的调度更加均衡&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;static Thread currentThread()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;获取到当前线程的对象&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;请注意，这个表格并不包含&lt;code&gt;Thread&lt;/code&gt;类中的所有方法，只是列出了一些常用的方法。此外，使用这些方法时需要考虑到线程安全、同步等问题，以避免潜在的并发问题。&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;线程优先级&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;线程调度&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;两种调度方式&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;分时调度模型：所有线程轮流使用 CPU 的使用权，平均分配每个线程占用 CPU 的时间片&lt;/li&gt;
&lt;li&gt;抢占式调度模型：优先让优先级高的线程使用 CPU，如果线程的优先级相同，那么会随机选择一个，优先级高的线程获取的 CPU 时间片相对多一些&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Java使用的是抢占式调度模型&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;随机性&lt;/p&gt;
&lt;p&gt;假如计算机只有一个 CPU，那么 CPU 在某一个时刻只能执行一条指令，线程只有得到CPU时间片，也就是使用权，才可以执行指令。所以说多线程程序的执行是有随机性，因为谁抢到CPU的使用权是不一定的&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;优先级相关方法&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;final int getPriority()&lt;/td&gt;
&lt;td&gt;返回此线程的优先级&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;final void setPriority(int newPriority)&lt;/td&gt;
&lt;td&gt;更改此线程的优先级线程默认优先级是5；线程优先级的范围是：1-10&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;代码演示&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class MyCallable implements Callable&amp;lt;String&amp;gt; {
    @Override
    public String call() throws Exception {
        for (int i = 0; i &amp;lt; 100; i++) {
            System.out.println(Thread.currentThread().getName() + &quot;---&quot; + i);
        }
        return &quot;线程执行完毕了&quot;;
    }
}
public class Demo {
    public static void main(String[] args) {
        //优先级: 1 - 10 默认值:5
        MyCallable mc = new MyCallable();

        FutureTask&amp;lt;String&amp;gt; ft = new FutureTask&amp;lt;&amp;gt;(mc);

        Thread t1 = new Thread(ft);
        t1.setName(&quot;飞机&quot;);
        t1.setPriority(10);
        //System.out.println(t1.getPriority());//5
        t1.start();

        MyCallable mc2 = new MyCallable();

        FutureTask&amp;lt;String&amp;gt; ft2 = new FutureTask&amp;lt;&amp;gt;(mc2);

        Thread t2 = new Thread(ft2);
        t2.setName(&quot;坦克&quot;);
        t2.setPriority(1);
        //System.out.println(t2.getPriority());//5
        t2.start();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;守护线程&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;相关方法&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;void setDaemon(boolean on)&lt;/td&gt;
&lt;td&gt;将此线程标记为守护线程，当运行的线程都是守护线程时，Java虚拟机将退出&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;代码演示&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class MyThread1 extends Thread {
    @Override
    public void run() {
        for (int i = 0; i &amp;lt; 10; i++) {
            System.out.println(getName() + &quot;---&quot; + i);
        }
    }
}
public class MyThread2 extends Thread {
    @Override
    public void run() {
        for (int i = 0; i &amp;lt; 100; i++) {
            System.out.println(getName() + &quot;---&quot; + i);
        }
    }
}
public class Demo {
    public static void main(String[] args) {
        MyThread1 t1 = new MyThread1();
        MyThread2 t2 = new MyThread2();

        t1.setName(&quot;A&quot;);
        t2.setName(&quot;B&quot;);

        //把第二个线程设置为守护线程
        //当普通线程执行完之后,那么守护线程也没有继续运行下去的必要了.
        t2.setDaemon(true);

        t1.start();
        t2.start();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;**守护线程（Daemon Thread）**是一种特殊的线程，它的主要作用是服务于其他线程，并且在程序中不会阻止JVM的退出。当所有的非守护线程（也称为用户线程）都执行完毕后，无论守护线程是否还在运行，JVM都会退出。
&lt;strong&gt;守护线程的用途：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;后台任务&lt;/strong&gt;：守护线程通常用于执行一些后台任务，如垃圾回收、内存管理等，这些任务不需要用户显式地干预。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;辅助功能&lt;/strong&gt;：它们可以用于实现一些辅助功能，比如在服务器应用程序中，守护线程可以用来监控内存使用情况或清理临时文件。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;非关键任务&lt;/strong&gt;：任何不需要在程序结束后继续执行的任务都可以作为守护线程运行。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;定时任务&lt;/strong&gt;：守护线程可以用于执行定时任务，比如定时备份、日志记录等。
&lt;strong&gt;守护线程的特点：&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;生命周期&lt;/strong&gt;：守护线程的生命周期依赖于用户线程，当所有的用户线程结束时，JVM会退出，无论守护线程是否完成。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;中断&lt;/strong&gt;：守护线程在JVM退出时会被中断，因此不应该在守护线程中执行重要的、不能中断的任务。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;资源释放&lt;/strong&gt;：由于守护线程可能在任何时候被中断，因此它们不应该持有任何需要显式释放的资源，如打开的文件句柄或数据库连接。
&lt;strong&gt;如何创建守护线程：&lt;/strong&gt;
在Java中，可以通过调用&lt;code&gt;Thread&lt;/code&gt;对象的&lt;code&gt;setDaemon(true)&lt;/code&gt;方法将线程设置为守护线程。这个方法必须在线程启动之前调用。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;Thread daemonThread = new Thread(new Runnable() {
    public void run() {
        // 守护线程的任务
    }
});
daemonThread.setDaemon(true);
daemonThread.start();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;注意事项：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;不要在守护线程中执行重要的业务逻辑，因为它们可能会在没有完成任务的情况下被终止。&lt;/li&gt;
&lt;li&gt;守护线程不应该访问共享资源，除非采取了适当的同步措施，以避免数据不一致或资源泄露。&lt;/li&gt;
&lt;li&gt;守护线程通常用于执行那些即使不完成也不会对程序造成重大影响的任务。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;线程同步&lt;/h2&gt;
&lt;p&gt;在Java中，多线程同步可以通过多种方式实现，主要包括使用synchronized关键字、Lock接口及其实现类（如ReentrantLock）、以及使用原子类等。&lt;/p&gt;
&lt;h3&gt;使用synchronized实现线程同步&lt;/h3&gt;
&lt;p&gt;假设有一个共享的资源，例如一个计数器，多个线程需要对其进行增加操作。为了保证线程安全，可以使用synchronized关键字来同步访问这个共享资源。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class syncDemo1 {
    public class Counter {
        private int count = 0;
        // 同步方法
        public synchronized void increment() {
            System.out.println(Thread.currentThread().getName() + &quot;: &quot; + ++count);
        }
        public int getCount() {
            return count;
        }
    }

    public static void main(String[] args) {
        syncDemo1 demo = new syncDemo1();
        Counter counter = demo.new Counter();
        // 创建多个线程
        for (int i = 0; i &amp;lt; 5; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j &amp;lt; 100; j++) {
                        counter.increment();
                    }
                }
            }).start();
        }
        // 等待所有线程执行完毕
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 输出最终的计数结果
        System.out.println(&quot;Final count is &quot; + counter.getCount());
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;使用synchronized代码块实现线程同步&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;public class Counter {
    static int count = 0;
    // 同步方法
    public void increment() {
        synchronized (this) {
            // 非静态使用this，静态使用ClassName.class
            System.out.println(Thread.currentThread().getName() + &quot;: &quot; + ++count);
        }
    }
    public int getCount() {
        return count;
    }
}

public class syncDemo2 {

    public static void main(String[] args) {
        syncDemo2 demo = new syncDemo2();
        // 创建多个线程
        for (int i = 0; i &amp;lt; 5; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    Counter counter = new Counter();
                    for (int j = 0; j &amp;lt; 100; j++) {
                        counter.increment();
                    }
                }
            }).start();
        }
        // 等待所有线程执行完毕
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;ReadWriteLock&lt;/code&gt; 是 Java 中的一种高级线程同步机制，它允许多个读线程同时访问资源，但在写线程访问时，所有读线程和其他写线程都会被阻塞。这种锁适用于读多写少的场景，可以提高并发性能。
&lt;code&gt;ReadWriteLock&lt;/code&gt; 接口的主要实现是 &lt;code&gt;ReentrantReadWriteLock&lt;/code&gt;。下面通过一个例子来展示如何使用 &lt;code&gt;ReadWriteLock&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;ReentrantLock&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;ReentrantLock&lt;/code&gt; 是 Java 中 &lt;code&gt;java.util.concurrent.locks&lt;/code&gt; 包下的一种可重入锁，它提供了与 &lt;code&gt;synchronized&lt;/code&gt; 关键字类似的同步功能，但功能更强大、更灵活。以下是 &lt;code&gt;ReentrantLock&lt;/code&gt; 的一些基本用法：&lt;/p&gt;
&lt;h4&gt;创建 &lt;code&gt;ReentrantLock&lt;/code&gt; 实例&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;ReentrantLock lock = new ReentrantLock();
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;尽量使用static，确保锁唯一&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;加锁&lt;/h4&gt;
&lt;p&gt;使用 &lt;code&gt;lock()&lt;/code&gt; 方法获取锁：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;lock.lock();
try {
    // 执行需要同步的代码
} finally {
    // 释放锁
    lock.unlock();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;解锁&lt;/h4&gt;
&lt;p&gt;使用 &lt;code&gt;unlock()&lt;/code&gt; 方法释放锁，通常放在 &lt;code&gt;finally&lt;/code&gt; 块中以确保锁被释放：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;finally {
    lock.unlock();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;可中断的锁获取&lt;/h4&gt;
&lt;p&gt;使用 &lt;code&gt;lockInterruptibly()&lt;/code&gt; 方法可以在获取锁的过程中响应中断：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;try {
    lock.lockInterruptibly();
    try {
        // 执行需要同步的代码
    } finally {
        lock.unlock();
    }
} catch (InterruptedException e) {
    // 当前线程被中断
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;尝试非阻塞获取锁&lt;/h4&gt;
&lt;p&gt;使用 &lt;code&gt;tryLock()&lt;/code&gt; 方法尝试获取锁，无论成功与否立即返回：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if (lock.tryLock()) {
    try {
        // 执行需要同步的代码
    } finally {
        lock.unlock();
    }
} else {
    // 获取锁失败
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;带有超时时间的锁获取&lt;/h5&gt;
&lt;p&gt;使用 &lt;code&gt;tryLock(long timeout, TimeUnit unit)&lt;/code&gt; 方法在指定时间内尝试获取锁：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;try {
    if (lock.tryLock(1, TimeUnit.SECONDS)) {
        try {
            // 执行需要同步的代码
        } finally {
            lock.unlock();
        }
    } else {
        // 获取锁失败
    }
} catch (InterruptedException e) {
    // 当前线程被中断
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;公平锁&lt;/h4&gt;
&lt;p&gt;创建 &lt;code&gt;ReentrantLock&lt;/code&gt; 时可以指定为公平锁，公平锁倾向于将锁赋予等待时间最长的线程：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ReentrantLock fairLock = new ReentrantLock(true);
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;条件变量&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;ReentrantLock&lt;/code&gt; 可以与 &lt;code&gt;Condition&lt;/code&gt; 对象一起使用，以实现线程之间的等待/通知机制：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Condition condition = lock.newCondition();
// 等待
lock.lock();
try {
    condition.await();
    // 执行后续操作
} finally {
    lock.unlock();
}



// 通知
lock.lock();
try {
    condition.signal();
    // 或者通知所有等待的线程：condition.signalAll();
} finally {
    lock.unlock();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;注意事项&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;务必在 &lt;code&gt;finally&lt;/code&gt; 块中释放锁&lt;/strong&gt;，以避免死锁。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不要在持有锁的情况下调用 &lt;code&gt;await()&lt;/code&gt;&lt;/strong&gt;，否则可能导致死锁。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;ReentrantLock&lt;/code&gt; 是可重入的&lt;/strong&gt;，即同一个线程可以多次获取同一把锁，而不会造成死锁。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;性能考虑&lt;/strong&gt;：&lt;code&gt;ReentrantLock&lt;/code&gt; 在某些情况下可能比 &lt;code&gt;synchronized&lt;/code&gt; 更高效，尤其是在高竞争环境下。
通过以上基本用法，可以灵活地控制多线程之间的同步和协作。&lt;code&gt;ReentrantLock&lt;/code&gt; 提供的强大功能使其在多线程编程中非常有用。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;code&gt;ReadWriteLock&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;假设我们有一个共享的资源，例如一个地图（&lt;code&gt;Map&lt;/code&gt;），多个线程可能同时读取这个地图，但写入操作需要独占访问。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class SharedMap {
    private final Map&amp;lt;String, String&amp;gt; map = new HashMap&amp;lt;&amp;gt;();
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    // 读操作
    public String get(String key) {
        lock.readLock().lock();
        try {
            return map.get(key);
        } finally {
            lock.readLock().unlock();
        }
    }
    // 写操作
    public void put(String key, String value) {
        lock.writeLock().lock();
        try {
            map.put(key, value);
        } finally {
            lock.writeLock().unlock();
        }
    }
    // 其他操作...
}
public class ReadWriteLockDemo {
    public static void main(String[] args) {
        SharedMap sharedMap = new SharedMap();
        // 创建多个读线程
        for (int i = 0; i &amp;lt; 5; i++) {
            new Thread(() -&amp;gt; {
                System.out.println(&quot;Reading: &quot; + sharedMap.get(&quot;key&quot;));
            }).start();
        }
        // 创建一个写线程
        new Thread(() -&amp;gt; {
            sharedMap.put(&quot;key&quot;, &quot;value&quot;);
            System.out.println(&quot;Written: value&quot;);
        }).start();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;解释&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ReadWriteLock&lt;/code&gt; 接口：定义了获取读锁和写锁的方法。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ReentrantReadWriteLock&lt;/code&gt;：&lt;code&gt;ReadWriteLock&lt;/code&gt; 接口的一个实现，提供了可重入的读写锁。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;readLock()&lt;/code&gt; 和 &lt;code&gt;writeLock()&lt;/code&gt;：分别用于获取读锁和写锁。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;lock()&lt;/code&gt; 和 &lt;code&gt;unlock()&lt;/code&gt;：用于锁定和解锁。
在 &lt;code&gt;SharedMap&lt;/code&gt; 类中，我们定义了 &lt;code&gt;get&lt;/code&gt; 和 &lt;code&gt;put&lt;/code&gt; 方法，分别用于读取和写入操作。读操作使用读锁，允许多个线程同时进行；写操作使用写锁，确保在写入时没有其他读或写操作。
在 &lt;code&gt;ReadWriteLockDemo&lt;/code&gt; 类的 &lt;code&gt;main&lt;/code&gt; 方法中，我们创建了多个读线程和一个写线程来演示 &lt;code&gt;ReadWriteLock&lt;/code&gt; 的使用。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;注意事项&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;在使用 &lt;code&gt;ReadWriteLock&lt;/code&gt; 时，要确保锁的获取和释放总是在 &lt;code&gt;try-finally&lt;/code&gt; 块中进行，以防止在发生异常时锁未被释放。&lt;/li&gt;
&lt;li&gt;读锁不支持条件变量，如果需要在读锁下使用条件变量，可以考虑其他同步机制。&lt;/li&gt;
&lt;li&gt;写锁是独占的，即使没有读线程，其他写线程也会被阻塞。
通过使用 &lt;code&gt;ReadWriteLock&lt;/code&gt;，可以有效地提高读多写少场景下的并发性能。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;死锁&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;概述&lt;/p&gt;
&lt;p&gt;线程死锁是指由于两个或者多个线程互相持有对方所需要的资源，导致这些线程处于等待状态，无法前往执行&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;什么情况下会产生死锁&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;资源有限&lt;/li&gt;
&lt;li&gt;同步嵌套&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;代码演示&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class Demo {
    public static void main(String[] args) {
        Object objA = new Object();
        Object objB = new Object();

        new Thread(()-&amp;gt;{
            while(true){
                synchronized (objA){
                    //线程一
                    synchronized (objB){
                        System.out.println(&quot;A&quot;);
                    }
                }
            }
        }).start();

        new Thread(()-&amp;gt;{
            while(true){
                synchronized (objB){
                    //线程二
                    synchronized (objA){
                        System.out.println(&quot;B&quot;);
                    }
                }
            }
        }).start();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;等待唤醒机制&lt;/h2&gt;
&lt;p&gt;Java多线程中的等待唤醒机制是一种用于线程间通信的重要机制，它允许线程在某些条件下挂起执行，并在条件满足时被唤醒继续执行。这种机制主要基于&lt;code&gt;Object&lt;/code&gt;类中的&lt;code&gt;wait()&lt;/code&gt;, &lt;code&gt;notify()&lt;/code&gt;, 和&lt;code&gt;notifyAll()&lt;/code&gt;方法实现。&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;wait()&lt;/code&gt;, &lt;code&gt;notify()&lt;/code&gt;, &lt;code&gt;notifyAll()&lt;/code&gt;方法&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;wait()&lt;/code&gt;方法&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;调用&lt;code&gt;wait()&lt;/code&gt;方法的线程会释放它持有的对象锁，并进入等待状态，直到其他线程调用同一个对象的&lt;code&gt;notify()&lt;/code&gt;或&lt;code&gt;notifyAll()&lt;/code&gt;方法。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;wait()&lt;/code&gt;方法可以带有一个超时参数，指定线程等待的最长时间。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;wait()&lt;/code&gt;方法必须在同步代码块或同步方法中调用，否则会抛出&lt;code&gt;IllegalMonitorStateException&lt;/code&gt;异常。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;notify()&lt;/code&gt;方法&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;调用&lt;code&gt;notify()&lt;/code&gt;方法的线程会随机唤醒一个正在等待该对象锁的线程。&lt;/li&gt;
&lt;li&gt;如果没有线程在等待，则&lt;code&gt;notify()&lt;/code&gt;方法不执行任何操作。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;notify()&lt;/code&gt;方法也必须在同步代码块或同步方法中调用。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;notifyAll()&lt;/code&gt;方法&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;调用&lt;code&gt;notifyAll()&lt;/code&gt;方法的线程会唤醒所有正在等待该对象锁的线程。&lt;/li&gt;
&lt;li&gt;这些线程将竞争对象锁，获得锁的线程将继续执行。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;等待唤醒机制的工作原理&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;线程进入等待状态&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;线程在执行过程中遇到&lt;code&gt;wait()&lt;/code&gt;方法调用时，会释放它持有的对象锁，并进入等待状态。&lt;/li&gt;
&lt;li&gt;其他线程可以获取该对象锁并执行同步代码块或同步方法。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;线程被唤醒&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;当其他线程调用同一个对象的&lt;code&gt;notify()&lt;/code&gt;或&lt;code&gt;notifyAll()&lt;/code&gt;方法时，等待的线程有机会被唤醒。&lt;/li&gt;
&lt;li&gt;被唤醒的线程将重新竞争对象锁，获得锁的线程将继续执行。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;线程继续执行&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;获得对象锁的线程从&lt;code&gt;wait()&lt;/code&gt;方法返回，继续执行后续代码。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;示例代码&lt;/h3&gt;
&lt;p&gt;以下是一个简单的示例，展示了等待唤醒机制的使用：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class WaitNotifyExample {
    private static final Object lock = new Object();
    private static boolean condition = false;
    public static void main(String[] args) {
        Thread waiter = new Thread(new Waiter());
        Thread notifier = new Thread(new Notifier());
        waiter.start();
        notifier.start();
    }
    static class Waiter implements Runnable {
        @Override
        public void run() {
            synchronized (lock) {
                while (!condition) {
                    try {
                        System.out.println(&quot;Waiter is waiting...&quot;);
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(&quot;Waiter is awake!&quot;);
            }
        }
    }
    static class Notifier implements Runnable {
        @Override
        public void run() {
            synchronized (lock) {
                System.out.println(&quot;Notifier is notifying...&quot;);
                condition = true;
                lock.notifyAll();
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这个示例中，&lt;code&gt;Waiter&lt;/code&gt;线程在条件不满足时进入等待状态，&lt;code&gt;Notifier&lt;/code&gt;线程在改变条件后唤醒等待的线程。&lt;/p&gt;
&lt;h3&gt;注意事项&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;正确使用条件变量&lt;/strong&gt;：在&lt;code&gt;wait()&lt;/code&gt;方法的使用中，通常需要配合一个条件变量来判断是否满足继续执行的条件。在上面的示例中，&lt;code&gt;condition&lt;/code&gt;就是这样一个变量。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;避免虚假唤醒&lt;/strong&gt;：&lt;code&gt;wait()&lt;/code&gt;方法可能会因为意外的原因被唤醒（虚假唤醒），因此推荐使用&lt;code&gt;while&lt;/code&gt;循环来检查条件，而不是&lt;code&gt;if&lt;/code&gt;语句。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;同步代码块&lt;/strong&gt;：&lt;code&gt;wait()&lt;/code&gt;, &lt;code&gt;notify()&lt;/code&gt;, &lt;code&gt;notifyAll()&lt;/code&gt;方法必须在同步代码块或同步方法中调用，以确保线程安全。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;与其他并发机制的对比&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;与&lt;code&gt;sleep()&lt;/code&gt;方法的区别&lt;/strong&gt;：&lt;code&gt;sleep()&lt;/code&gt;方法不会释放对象锁，只是让当前线程暂停执行一段时间。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;与&lt;code&gt;Lock&lt;/code&gt;和&lt;code&gt;Condition&lt;/code&gt;的区别&lt;/strong&gt;：&lt;code&gt;Lock&lt;/code&gt;和&lt;code&gt;Condition&lt;/code&gt;提供了更灵活的锁定和等待唤醒机制，但使用起来相对复杂。
理解并正确使用Java多线程中的等待唤醒机制是开发高效、线程安全的应用程序的关键。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;阻塞队列实现&lt;/h3&gt;
&lt;p&gt;阻塞队列（BlockingQueue）是Java并发包（java.util.concurrent）中提供的一种线程安全的队列，它支持多线程间的阻塞式数据交换。使用阻塞队列可以简化多线程编程中的等待唤醒机制，因为它内部已经实现了这些机制。下面是如何使用阻塞队列来实现等待唤醒机制的示例：&lt;/p&gt;
&lt;h4&gt;阻塞队列的基本操作&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;put(E e)&lt;/code&gt;：将元素插入队列，如果队列已满，则阻塞直到有空间可用。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;take()&lt;/code&gt;：从队列中移除并返回头部元素，如果队列为空，则阻塞直到有元素可用。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;offer(E e)&lt;/code&gt;：将元素插入队列，如果队列已满，则返回false，不会阻塞。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;poll()&lt;/code&gt;：从队列中移除并返回头部元素，如果队列为空，则返回null，不会阻塞。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;peek()&lt;/code&gt;：返回队列头部元素，但不移除，如果队列为空，则返回null。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;使用阻塞队列实现生产者-消费者模式&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
public class ProducerConsumerExample {
    public static void main(String[] args) {
        BlockingQueue&amp;lt;Integer&amp;gt; queue = new LinkedBlockingQueue&amp;lt;&amp;gt;(5); // 创建容量为5的阻塞队列
        Thread producer = new Thread(new Producer(queue));
        Thread consumer = new Thread(new Consumer(queue));
        producer.start();
        consumer.start();
    }
}
class Producer implements Runnable {
    private BlockingQueue&amp;lt;Integer&amp;gt; queue;
    public Producer(BlockingQueue&amp;lt;Integer&amp;gt; queue) {
        this.queue = queue;
    }
    @Override
    public void run() {
        for (int i = 0; i &amp;lt; 10; i++) {
            try {
                System.out.println(&quot;Produced: &quot; + i);
                queue.put(i); // 如果队列满，会阻塞等待
                Thread.sleep(1000); // 模拟生产时间
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
class Consumer implements Runnable {
    private BlockingQueue&amp;lt;Integer&amp;gt; queue;
    public Consumer(BlockingQueue&amp;lt;Integer&amp;gt; queue) {
        this.queue = queue;
    }
    @Override
    public void run() {
        while (true) {
            try {
                Integer taken = queue.take(); // 如果队列空，会阻塞等待
                System.out.println(&quot;Consumed: &quot; + taken);
                Thread.sleep(1000); // 模拟消费时间
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;工作原理&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;生产者线程&lt;/strong&gt;：生产者尝试将产品放入阻塞队列。如果队列已满，&lt;code&gt;put()&lt;/code&gt;方法会阻塞生产者线程，直到队列中有可用空间。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;消费者线程&lt;/strong&gt;：消费者尝试从阻塞队列中取出产品。如果队列为空，&lt;code&gt;take()&lt;/code&gt;方法会阻塞消费者线程，直到队列中有产品可取。
通过使用阻塞队列，我们不需要显式地使用wait(), notify(), notifyAll()方法来管理线程的等待和唤醒，因为阻塞队列内部已经帮我们实现了这些复杂的同步控制。&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;优点&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;简化代码&lt;/strong&gt;：使用阻塞队列可以大大简化多线程编程的复杂性，避免直接处理线程同步和唤醒的细节。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;线程安全&lt;/strong&gt;：阻塞队列是线程安全的，可以在多线程环境中安全使用。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;灵活性和可扩展性&lt;/strong&gt;：Java提供了多种类型的阻塞队列，如&lt;code&gt;ArrayBlockingQueue&lt;/code&gt;, &lt;code&gt;LinkedBlockingQueue&lt;/code&gt;, &lt;code&gt;PriorityBlockingQueue&lt;/code&gt;等，可以根据具体需求选择合适的队列实现。
通过阻塞队列实现的生产者-消费者模式是并发编程中的经典模式，它能够有效地解耦生产者和消费者，提高程序的并发性能和可维护性。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;线程池&lt;/h2&gt;
&lt;p&gt;Java的线程池是一种用于管理和复用线程的机制，它可以有效地提高程序的性能和响应速度。线程池的核心思想是预先创建一定数量的线程，这些线程在需要执行任务时被分配任务，执行完毕后并不销毁，而是返回线程池中等待下一次的任务分配。&lt;/p&gt;
&lt;h3&gt;线程池的好处&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;降低资源消耗&lt;/strong&gt;：通过复用已存在的线程，减少线程创建和销毁所带来的开销。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;提高响应速度&lt;/strong&gt;：任务到达时，无需等待线程创建即可立即执行。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;提高线程的可管理性&lt;/strong&gt;：线程池可以统一管理、分配、调优和监控线程。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;提供更多更强大的功能&lt;/strong&gt;：线程池支持定时、周期性执行任务等功能。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Java中的线程池实现&lt;/h3&gt;
&lt;p&gt;在Java中，线程池主要通过&lt;code&gt;java.util.concurrent&lt;/code&gt;包中的&lt;code&gt;Executor&lt;/code&gt;框架实现。&lt;code&gt;Executor&lt;/code&gt;是一个接口，它定义了执行任务的单一方法&lt;code&gt;execute(Runnable command)&lt;/code&gt;。其子接口&lt;code&gt;ExecutorService&lt;/code&gt;添加了更多管理线程池的方法，如&lt;code&gt;shutdown()&lt;/code&gt;、&lt;code&gt;submit()&lt;/code&gt;等。
&lt;code&gt;ThreadPoolExecutor&lt;/code&gt;是&lt;code&gt;ExecutorService&lt;/code&gt;的一个实现，它提供了线程池的具体实现。另外，&lt;code&gt;Executors&lt;/code&gt;工厂类提供了几种常见配置的线程池的快速实现方法。&lt;/p&gt;
&lt;h3&gt;常见的线程池类型&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;FixedThreadPool&lt;/strong&gt;：固定数量的线程池。适用于负载较重的服务器。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CachedThreadPool&lt;/strong&gt;：一个根据需要创建新线程的线程池。适用于执行大量短生命周期的异步任务（最多到int上限的线程）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SingleThreadExecutor&lt;/strong&gt;：单线程的Executor。保证所有任务都在同一线程中按顺序执行。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ScheduledThreadPool&lt;/strong&gt;：用于执行定时任务和周期性任务。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;ThreadPoolExecutor的重要参数&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;corePoolSize&lt;/strong&gt;：核心线程数，即线程池中始终存活的线程数。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;maximumPoolSize&lt;/strong&gt;：最大线程数，线程池中允许的最大线程数。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;keepAliveTime&lt;/strong&gt;：非核心线程的空闲存活时间。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;unit&lt;/strong&gt;：keepAliveTime的时间单位。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;workQueue&lt;/strong&gt;：工作队列，用于存放任务。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;threadFactory&lt;/strong&gt;：线程工厂，用于创建线程。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;rejectedExecutionHandler&lt;/strong&gt;：拒绝策略，当任务太多无法处理时，如何拒绝新任务。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;拒绝策略&lt;/h3&gt;
&lt;p&gt;以下均为&lt;code&gt;ThreadPoolExecutor&lt;/code&gt;的子类。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;AbortPolicy&lt;/strong&gt;：直接抛出异常，默认策略。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CallerRunsPolicy&lt;/strong&gt;：调用线程自己执行该任务。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DiscardPolicy&lt;/strong&gt;：直接丢弃任务，不做任何处理。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DiscardOldestPolicy&lt;/strong&gt;：丢弃队列中最老的任务。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;使用示例&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolDemo {
    public static void main(String[] args) {
        // 创建一个固定大小的线程池
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
        
        // 提交任务
        for (int i = 0; i &amp;lt; 10; i++) {
            int finalI = i;
            fixedThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + &quot; is running-&quot; + i);
                }
            });
        }
        
        // 关闭线程池
        fixedThreadPool.shutdown();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;自定义线程池：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class MyThreadPoolDemo3 {
//    参数一：核心线程数量
//    参数二：最大线程数
//    参数三：空闲线程最大存活时间
//    参数四：时间单位
//    参数五：任务队列
//    参数六：创建线程工厂
//    参数七：任务的拒绝策略
    public static void main(String[] args) {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
            2,
            5,
            2,
            TimeUnit.SECONDS,
            new ArrayBlockingQueue&amp;lt;&amp;gt;(10), // LinkedBlockingQueue等阻塞队列
            Executors.defaultThreadFactory(),
            new ThreadPoolExecutor.AbortPolicy()
        );
        pool.submit(new MyRunnable());
        pool.submit(new MyRunnable());

        pool.shutdown();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;注意事项&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;合理配置线程池大小&lt;/strong&gt;：根据任务的性质和系统资源进行配置。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;注意任务与线程的匹配&lt;/strong&gt;：避免线程空闲或过度竞争。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;正确关闭线程池&lt;/strong&gt;：避免程序无法正常结束。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;手动实现线程池&lt;/h3&gt;
&lt;p&gt;实现思路：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;创建一个线程池类(ThreadPool)&lt;/li&gt;
&lt;li&gt;在该类中定义两个成员变量poolSize(线程池初始化线程的个数) , BlockingQueue&amp;lt;Runnable&amp;gt;(任务容器)&lt;/li&gt;
&lt;li&gt;通过构造方法来创建两个线程对象(消费者线程)，并且启动&lt;/li&gt;
&lt;li&gt;使用内部类的方式去定义一个线程类(TaskThread),可以提供一个构造方法用来初始化线程名称&lt;/li&gt;
&lt;li&gt;两个消费者线程需要不断的从任务容器中获取任务，如果没有任务，则线程处于阻塞状态。&lt;/li&gt;
&lt;li&gt;提供一个方法(submit)向任务容器中添加任务&lt;/li&gt;
&lt;li&gt;定义测试类进行测试&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;线程池类&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class ThreadPool {

    // 初始化线程个数
    private static final int DEFAULT_POOL_SIZE = 2 ;

    // 在该类中定义两个成员变量poolSize(线程池初始化线程的个数) , BlockingQueue&amp;lt;Runnable&amp;gt;(任务容器)
    private int poolSize = DEFAULT_POOL_SIZE ;
    private BlockingQueue&amp;lt;Runnable&amp;gt; blockingQueue = new LinkedBlockingQueue&amp;lt;Runnable&amp;gt;() ;

    // 无参构造方法
    public ThreadPool(){
        this.initThread();
    }

    // 有参构造方法，通过构造方法来创建两个线程对象(消费者线程)，并且启动
    public ThreadPool(int poolSize) {
        if(poolSize &amp;gt; 0) {
            this.poolSize = poolSize ;
        }
        this.initThread();
    }

    // 初始化线程方法
    public void initThread(){
        for(int x = 0 ; x &amp;lt; poolSize ; x++) {
            new TaskThread(&quot;线程---&amp;gt;&quot; + x).start();
        }
    }

    // 提供一个方法(submit)向任务容器中添加任务
    public void submit(Runnable runnable) {

        try {
            blockingQueue.put(runnable);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

    // 使用内部类的方式去定义一个线程类
    public class TaskThread extends Thread {

        // 提供一个构造方法，用来初始化线程名称
        public TaskThread(String name) {
            super(name);
        }

        @Override
        public void run() {

            while(true) {

                try {

                    // 两个消费者线程需要不断的从任务容器中获取任务，如果没有任务，则线程处于阻塞状态。
                    Runnable task = blockingQueue.take();
                    task.run();

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }

        }
    }

}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;测试类&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class ThreadPoolDemo01 {

    public static void main(String[] args) {

        // 创建线程池对象,无参构造方法创建
        // ThreadPool threadPool = new ThreadPool();
        ThreadPool threadPool = new ThreadPool(5);

        // 提交任务
        for(int x = 0 ; x &amp;lt; 10 ; x++) {
            threadPool.submit( () -&amp;gt; {
                System.out.println(Thread.currentThread().getName() + &quot;----&amp;gt;&amp;gt;&amp;gt;处理了任务&quot;);
            });
        }

    }

}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;线程数量设置&lt;/h3&gt;
&lt;p&gt;获取操作系统分配资源：最大并行数(Maximum parallel quantity, MPQ)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int count = Runtime.getRuntime().availableProcessors();
System.out.println(count);
// 16
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;CPU密集型：$$MPQ+1 $$（某个出问题，后面的可以补上，所以+1）&lt;/li&gt;
&lt;li&gt;IO密集型：$$ MPQ * CPU_{utilization} * \frac{Computation_{time} +Waiting_{time}}{Computation_{time}}$$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;例如：4核8线程：$$8 * 100% * \frac{4_s}{2_s} = 16 $$&lt;/p&gt;
&lt;p&gt;（需要通过其它工具计算得到计算时间和IO的等待时间）&lt;/p&gt;
&lt;h2&gt;线程的状态&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;线程状态&lt;/th&gt;
&lt;th&gt;具体含义&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;NEW&lt;/td&gt;
&lt;td&gt;一个尚未启动的线程的状态。也称之为初始状态、开始状态。线程刚被创建，但是并未启动。还没调用start方法。MyThread t = new MyThread()只有线程象，没有线程特征。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RUNNABLE&lt;/td&gt;
&lt;td&gt;当我们调用线程对象的start方法，那么此时线程对象进入了RUNNABLE状态。那么此时才是真正的在JVM进程中创建了一个线程，线程一经启动并不是立即得到执行，线程的运行与否要听令与CPU的调度，那么我们把这个中间状态称之为可执行状态(RUNNABLE)也就是说它具备执行的资格，但是并没有真正的执行起来而是在等待CPU的度。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BLOCKED&lt;/td&gt;
&lt;td&gt;当一个线程试图获取一个对象锁，而该对象锁被其他的线程持有，则该线程进入Blocked状态；当该线程持有锁时，该线程将变成Runnable状态。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WAITING&lt;/td&gt;
&lt;td&gt;一个正在等待的线程的状态。也称之为等待状态。造成线程等待的原因有两种，分别是调用Object.wait()、join()方法。处于等待状态的线程，正在等待其他线程去执行一个特定的操作。例如：因为wait()而等待的线程正在等待另一个线程去调用notify()或notifyAll()；一个因为join()而等待的线程正在等待另一个线程结束。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TIMED_WAITING&lt;/td&gt;
&lt;td&gt;一个在限定时间内等待的线程的状态。也称之为限时等待状态。造成线程限时等待状态的原因有三种，分别是：Thread.sleep(long)，Object.wait(long)、join(long)。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TERMINATED&lt;/td&gt;
&lt;td&gt;一个完全运行完成的线程的状态。也称之为终止状态、结束状态&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;volatile关键字&lt;/h2&gt;
&lt;h3&gt;基本概念&lt;/h3&gt;
&lt;p&gt;在Java中，&lt;code&gt;volatile&lt;/code&gt;是一个用于修饰变量的关键字。当一个变量被声明为&lt;code&gt;volatile&lt;/code&gt;时，它具备了两层主要含义：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;可见性&lt;/strong&gt;：对一个&lt;code&gt;volatile&lt;/code&gt;变量的读写操作，总是能直接反映到主内存中。换句话说，一个线程对&lt;code&gt;volatile&lt;/code&gt;变量的修改，对其他线程是立即可见的。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;有序性&lt;/strong&gt;：&lt;code&gt;volatile&lt;/code&gt;可以防止指令重排序。在多线程环境中，为了提高执行效率，编译器和处理器可能会对指令进行重排序。但使用&lt;code&gt;volatile&lt;/code&gt;后，可以保证其前后操作的执行顺序。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;工作原理&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;volatile&lt;/code&gt;的工作原理主要基于内存屏障（Memory Barrier）：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;写屏障&lt;/strong&gt;：在写入&lt;code&gt;volatile&lt;/code&gt;变量后，会插入一个写屏障，确保该变量的新值对其他线程立即可见。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;读屏障&lt;/strong&gt;：在读取&lt;code&gt;volatile&lt;/code&gt;变量前，会插入一个读屏障，确保读取的是该变量的最新值。
通过这两道屏障，&lt;code&gt;volatile&lt;/code&gt;实现了其可见性和有序性的保证。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;使用场景&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;volatile&lt;/code&gt;主要适用于以下场景：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;状态标记&lt;/strong&gt;：用于标记某个状态，如线程的启动、停止等。&lt;pre&gt;&lt;code&gt;volatile boolean running = true;
while (running) {
    // do something
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;双重检查锁定&lt;/strong&gt;：在单例模式中，用于确保只创建一个实例。&lt;pre&gt;&lt;code&gt;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();
                }
            }
        }
        return instance;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;局限性&lt;/h3&gt;
&lt;p&gt;尽管&lt;code&gt;volatile&lt;/code&gt;很有用，但它也有局限性：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;不保证原子性&lt;/strong&gt;：&lt;code&gt;volatile&lt;/code&gt;不能保证复合操作（如自增、自减）的原子性。&lt;pre&gt;&lt;code&gt;volatile int count = 0;
count++; // 这个操作不是原子性的
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不适合复杂的状态管理&lt;/strong&gt;：对于复杂的状态管理，使用&lt;code&gt;volatile&lt;/code&gt;可能会变得复杂且难以维护。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;与synchronized的区别&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;volatile&lt;/code&gt;和&lt;code&gt;synchronized&lt;/code&gt;都是Java中用于多线程同步的关键字，但它们有明显的区别：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;功能&lt;/strong&gt;：&lt;code&gt;volatile&lt;/code&gt;主要保证可见性和有序性，而&lt;code&gt;synchronized&lt;/code&gt;还可以保证原子性。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;使用方式&lt;/strong&gt;：&lt;code&gt;volatile&lt;/code&gt;用于修饰变量，而&lt;code&gt;synchronized&lt;/code&gt;可以用于修饰方法或代码块。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;性能&lt;/strong&gt;：通常情况下，&lt;code&gt;volatile&lt;/code&gt;的性能优于&lt;code&gt;synchronized&lt;/code&gt;，因为它不会引入线程阻塞。&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item><item><title>Java的动态代理</title><link>http://zaoweiceng.github.io/posts/java/%E5%8A%A8%E6%80%81%E4%BB%A3%E7%90%86/</link><guid isPermaLink="true">http://zaoweiceng.github.io/posts/java/%E5%8A%A8%E6%80%81%E4%BB%A3%E7%90%86/</guid><description>Java中的动态代理是一种强大的机制，允许在运行时创建接口的代理实例。这种代理实例能够动态地拦截对接口方法的调用，从而可以在调用前后添加自定义的逻辑，而不需要修改原有的代码。</description><pubDate>Fri, 14 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Java中的动态代理是一种强大的机制，允许在运行时创建接口的代理实例。这种代理实例能够动态地拦截对接口方法的调用，从而可以在调用前后添加自定义的逻辑，而不需要修改原有的代码。动态代理在AOP（面向切面编程）、远程方法调用、事务管理等领域有着广泛的应用。&lt;/p&gt;
&lt;h3&gt;动态代理的基本概念&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;代理对象&lt;/strong&gt;：在运行时创建的用于替代真实对象的代理。
&lt;strong&gt;InvocationHandler&lt;/strong&gt;：一个接口，其实现类负责处理代理对象的方法调用。每个代理实例都有一个关联的InvocationHandler。
&lt;strong&gt;Proxy类&lt;/strong&gt;：Java提供的用于创建动态代理的类，它有一个静态方法&lt;code&gt;newProxyInstance&lt;/code&gt;，用于生成代理对象。&lt;/p&gt;
&lt;h3&gt;创建动态代理的步骤&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;定义接口&lt;/strong&gt;：首先定义一个或多个接口，代理类将会实现这些接口。&lt;pre&gt;&lt;code&gt;public interface MyInterface {
    void doSomething();
}

public class MyInterfaceImpl implements MyInterface{
    ...
        void doSomething();
    ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;实现InvocationHandler&lt;/strong&gt;：创建一个实现&lt;code&gt;InvocationHandler&lt;/code&gt;接口的类，该类中的&lt;code&gt;invoke&lt;/code&gt;方法将会在代理对象的方法被调用时执行。&lt;pre&gt;&lt;code&gt;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class MyInvocationHandler implements InvocationHandler {
    private Object target; // 真实对象
    public MyInvocationHandler(Object target) {
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            /*
             * 参数一：代理的对象
             * 参数二：要运行的方法,如果有多个,可以都写在数组中
             * 参数三：调用方法时，传递的实参
             * */
        // 在调用真实对象的方法前可以添加自定义逻辑
        System.out.println(&quot;Before method execution&quot;);
        // 调用真实对象的方法
        Object result = method.invoke(target, args);
        // 在调用真实对象的方法后可以添加自定义逻辑
        System.out.println(&quot;After method execution&quot;);
        return result;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;创建代理对象&lt;/strong&gt;：使用&lt;code&gt;Proxy.newProxyInstance&lt;/code&gt;方法创建代理对象。&lt;pre&gt;&lt;code&gt;public class ProxyDemo {
    public static void main(String[] args) {
        MyInterface realObject = new MyInterfaceImpl();
        MyInvocationHandler handler = new MyInvocationHandler(realObject);
        // 创建代理对象
        MyInterface proxyObject = (MyInterface) Proxy.newProxyInstance(
            MyInterface.class.getClassLoader(),
            new Class&amp;lt;?&amp;gt;[] {MyInterface.class},
            handler // 用来指定生成的代理对象要干什么事情, 可以直接写匿名类new InvocationHandler(){}
        );
        // 通过代理对象调用方法
        proxyObject.doSomething();
    }
}
&lt;/code&gt;&lt;/pre&gt;
在这个例子中，&lt;code&gt;MyInterfaceImpl&lt;/code&gt;是&lt;code&gt;MyInterface&lt;/code&gt;的一个实现类。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;动态代理的工作原理&lt;/h3&gt;
&lt;p&gt;当通过代理对象调用方法时，会调用InvocationHandler的&lt;code&gt;invoke&lt;/code&gt;方法。在这个方法中，可以添加自定义的逻辑，然后调用真实对象的方法。&lt;code&gt;invoke&lt;/code&gt;方法的参数包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;proxy&lt;/code&gt;：代理对象本身。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;method&lt;/code&gt;：被调用的方法对象。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;args&lt;/code&gt;：方法调用时传入的参数数组。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;动态代理的优势&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;动态创建&lt;/strong&gt;：代理类在运行时动态创建，不需要手动编写代理类。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;解耦&lt;/strong&gt;：通过代理可以添加额外的逻辑，而不需要修改原有的代码，实现了关注点的分离。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;灵活&lt;/strong&gt;：可以在运行时决定代理的行为。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;动态代理的局限性&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;只能代理接口&lt;/strong&gt;：Java的动态代理只能为接口创建代理，不能为具体的类创建代理。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;性能开销&lt;/strong&gt;：相比于直接调用，通过代理调用会有额外的性能开销。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;应用场景&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;AOP&lt;/strong&gt;：在Spring等框架中，动态代理用于实现AOP，例如事务管理、日志记录等。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;远程方法调用&lt;/strong&gt;：在RMI（远程方法调用）中，动态代理用于代表远程对象。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数据绑定和校验&lt;/strong&gt;：在Web应用中，动态代理可以用于数据绑定和校验。
总之，Java中的动态代理是一种非常灵活和强大的机制，能够在不修改原有代码的情况下，动态地添加额外的逻辑。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;黑马的案例&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;public class Test {
    public static void main(String[] args) {
    /*
        需求：
            外面的人想要大明星唱一首歌
             1. 获取代理的对象
                代理对象 = ProxyUtil.createProxy(大明星的对象);
             2. 再调用代理的唱歌方法
                代理对象.唱歌的方法(&quot;只因你太美&quot;);
     */
        //1. 获取代理的对象
        BigStar bigStar = new BigStar(&quot;鸡哥&quot;);
        Star proxy = ProxyUtil.createProxy(bigStar);

        //2. 调用唱歌的方法
        String result = proxy.sing(&quot;只因你太美&quot;);
        System.out.println(result);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;/*
*
* 类的作用：
*       创建一个代理
*
* */
public class ProxyUtil {
    /*
    *
    * 方法的作用：
    *       给一个明星的对象，创建一个代理
    *
    *  形参：
    *       被代理的明星对象
    *
    *  返回值：
    *       给明星创建的代理
    *
    *
    *
    * 需求：
    *   外面的人想要大明星唱一首歌
    *   1. 获取代理的对象
    *      代理对象 = ProxyUtil.createProxy(大明星的对象);
    *   2. 再调用代理的唱歌方法
    *      代理对象.唱歌的方法(&quot;只因你太美&quot;);
    * */
    public static Star createProxy(BigStar bigStar){
       /* java.lang.reflect.Proxy类：提供了为对象产生代理对象的方法：
        public static Object newProxyInstance(ClassLoader loader, Class&amp;lt;?&amp;gt;[] interfaces, InvocationHandler h)
        参数一：用于指定用哪个类加载器，去加载生成的代理类
        参数二：指定接口，这些接口用于指定生成的代理长什么，也就是有哪些方法
        参数三：用来指定生成的代理对象要干什么事情
        */
        
        Star star = (Star) Proxy.newProxyInstance(
                ProxyUtil.class.getClassLoader(),//参数一：用于指定用哪个类加载器，去加载生成的代理类
                new Class[]{Star.class},//参数二：指定接口，这些接口用于指定生成的代理长什么，也就是有哪些方法
                //参数三：用来指定生成的代理对象要干什么事情
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        /*
                        * 参数一：代理的对象
                        * 参数二：要运行的方法 sing
                        * 参数三：调用sing方法时，传递的实参
                        * */
                        if(&quot;sing&quot;.equals(method.getName())){
                            System.out.println(&quot;准备话筒，收钱&quot;);
                        }else if(&quot;dance&quot;.equals(method.getName())){
                            System.out.println(&quot;准备场地，收钱&quot;);
                        }
                        //去找大明星开始唱歌或者跳舞
                        //代码的表现形式：调用大明星里面唱歌或者跳舞的方法
                        return method.invoke(bigStar,args);
                    }
                }
        );
        return star;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;public interface Star {
    //我们可以把所有想要被代理的方法定义在接口当中
    //唱歌
    public abstract String sing(String name);
    //跳舞
    public abstract void dance();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;public class BigStar implements Star {
    private String name;


    public BigStar() {
    }

    public BigStar(String name) {
        this.name = name;
    }

    //唱歌
    @Override
    public String sing(String name){
        System.out.println(this.name + &quot;正在唱&quot; + name);
        return &quot;谢谢&quot;;
    }

    //跳舞
    @Override
    public void dance(){
        System.out.println(this.name + &quot;正在跳舞&quot;);
    }

    /**
     * 获取
     * @return name
     */
    public String getName() {
        return name;
    }

    /**
     * 设置
     * @param name
     */
    public void setName(String name) {
        this.name = name;
    }

    public String toString() {
        return &quot;BigStar{name = &quot; + name + &quot;}&quot;;
    }
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Java反射</title><link>http://zaoweiceng.github.io/posts/java/%E5%8F%8D%E5%B0%84/</link><guid isPermaLink="true">http://zaoweiceng.github.io/posts/java/%E5%8F%8D%E5%B0%84/</guid><description>Java反射是Java语言的一个重要特性，它允许程序在运行时获取任意类的内部信息，并可以操作这些信息。这种机制使得Java具有了动态获取类信息以及动态调用对象方法的能力。</description><pubDate>Fri, 14 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Java反射是Java语言的一个重要特性，它允许程序在运行时获取任意类的内部信息，并可以操作这些信息。这种机制使得Java具有了动态获取类信息以及动态调用对象方法的能力。&lt;/p&gt;
&lt;h3&gt;反射的基本概念&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;反射&lt;/strong&gt;（Reflection）是Java被视为动态（或准动态）语言的一个关键性质。这个机制允许程序在运行时透过类名来获取任意一个类的内部信息，包括成员变量、构造方法、成员方法等，并可以操作这些成员。&lt;/p&gt;
&lt;h3&gt;反射的主要用途&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;动态加载类&lt;/strong&gt;：在运行时根据需要加载类。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;动态创建对象&lt;/strong&gt;：通过反射来创建对象，无需知道具体的类名。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;访问和修改私有成员&lt;/strong&gt;：可以访问和修改类的私有成员变量和方法。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;获取类型信息&lt;/strong&gt;：在运行时获取类的类型信息。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;反射的核心类&lt;/h3&gt;
&lt;p&gt;Java反射主要涉及以下核心类：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Class&lt;/code&gt;：代表一个类或接口的类型信息。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Field&lt;/code&gt;：代表类的成员变量（字段）。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Method&lt;/code&gt;：代表类的方法。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Constructor&lt;/code&gt;：代表类的构造方法。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;获取&lt;code&gt;Class&lt;/code&gt;对象&lt;/h3&gt;
&lt;p&gt;获取&lt;code&gt;Class&lt;/code&gt;对象有三种方式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;通过对象获取&lt;/strong&gt;：&lt;code&gt;Class clazz = obj.getClass();&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;通过类名获取&lt;/strong&gt;：&lt;code&gt;Class clazz = ClassName.class;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;通过全限定类名获取&lt;/strong&gt;：&lt;code&gt;Class clazz = Class.forName(&quot;com.example.ClassName&quot;);&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;使用反射操作类&lt;/h3&gt;
&lt;h4&gt;获取成员变量&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;Field field = clazz.getDeclaredField(&quot;fieldName&quot;); // 获取私有成员变量
field.setAccessible(true); // 取消访问权限检查
Object value = field.get(obj); // 获取成员变量的值
field.set(obj, newValue); // 设置成员变量的值
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;获取方法&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;Method method = clazz.getDeclaredMethod(&quot;methodName&quot;, paramClass1, paramClass2, ...); // 获取私有方法
// getMthods();会获取包含父类在内的所有方法
method.setAccessible(true); // 取消访问权限检查
Object result = method.invoke(obj, param1, param2, ...); // 调用方法
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;获取构造方法&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;Constructor constructor = clazz.getDeclaredConstructor(paramClass1, paramClass2, ...); // 获取私有构造方法
constructor.setAccessible(true); // 取消访问权限检查
Object instance = constructor.newInstance(param1, param2, ...); // 创建对象
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;反射的优缺点&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;优点&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;增加程序的灵活性和扩展性。&lt;/li&gt;
&lt;li&gt;可以实现动态创建对象和动态调用方法。
&lt;strong&gt;缺点&lt;/strong&gt;：&lt;/li&gt;
&lt;li&gt;性能开销大：反射操作需要类型检查和解析，因此比直接代码慢。&lt;/li&gt;
&lt;li&gt;安全性问题：反射可以访问和修改私有成员，可能会破坏封装性。&lt;/li&gt;
&lt;li&gt;代码可读性差：使用反射的代码通常难以理解。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;反射的使用场景&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;框架开发：如Spring、Hibernate等框架大量使用反射来实现依赖注入、对象关系映射等功能。&lt;/li&gt;
&lt;li&gt;动态代理：如AOP（面向切面编程）中使用的动态代理。&lt;/li&gt;
&lt;li&gt;编译时无法确定类型的场景。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;注意事项&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;反射会破坏类的封装性，应谨慎使用。&lt;/li&gt;
&lt;li&gt;反射操作可能会抛出&lt;code&gt;ClassNotFoundException&lt;/code&gt;、&lt;code&gt;NoSuchFieldException&lt;/code&gt;、&lt;code&gt;NoSuchMethodException&lt;/code&gt;、&lt;code&gt;IllegalAccessException&lt;/code&gt;、&lt;code&gt;InvocationTargetException&lt;/code&gt;等异常，需要进行异常处理。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;反射与泛型的关系&lt;/h3&gt;
&lt;p&gt;Java 5引入了泛型，但泛型信息在运行时会被擦除，即所谓的类型擦除。然而，可以通过反射获取到泛型的类型信息，这通常用于框架开发中。&lt;/p&gt;
&lt;h3&gt;反射的性能优化&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;尽量缓存&lt;code&gt;Class&lt;/code&gt;、&lt;code&gt;Field&lt;/code&gt;、&lt;code&gt;Method&lt;/code&gt;、&lt;code&gt;Constructor&lt;/code&gt;等反射对象。&lt;/li&gt;
&lt;li&gt;减少反射的使用，只在必要的时候使用。&lt;/li&gt;
&lt;li&gt;使用&lt;code&gt;setAccessible(true)&lt;/code&gt;来提高访问私有成员的速度。
通过以上讲解，相信您对Java反射有了更深入的理解。反射是Java中一个非常强大的工具，但使用时需要谨慎，以避免带来不必要的性能问题和安全问题。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;示例1：获取Class对象&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;public class ReflectionExample {
    public static void main(String[] args) {
        // 通过对象获取Class对象
        Person person = new Person();
        Class&amp;lt;?&amp;gt; clazz1 = person.getClass();
        // 通过类名获取Class对象
        Class&amp;lt;?&amp;gt; clazz2 = Person.class;
        // 通过全限定类名获取Class对象
        try {
            Class&amp;lt;?&amp;gt; clazz3 = Class.forName(&quot;com.example.Person&quot;);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        System.out.println(clazz1 == clazz2); // 输出true，表示是同一个Class对象
    }
}
class Person {
    // Person类的定义
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;示例2：创建对象&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;public class ReflectionCreateObject {
    public static void main(String[] args) {
        try {
            Class&amp;lt;?&amp;gt; clazz = Class.forName(&quot;com.example.Person&quot;);
            // 调用无参构造方法创建对象
            Person person = (Person) clazz.newInstance();
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}
class Person {
    public Person() {
        // 无参构造方法
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;示例3：访问和修改字段&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;public class ReflectionFieldAccess {
    public static void main(String[] args) {
        try {
            Class&amp;lt;?&amp;gt; clazz = Class.forName(&quot;com.example.Person&quot;);
            Person person = (Person) clazz.newInstance();
            // 获取字段
            Field nameField = clazz.getDeclaredField(&quot;name&quot;);
            // 设置可访问性
            nameField.setAccessible(true);
            // 修改字段值
            nameField.set(person, &quot;Alice&quot;);
            // 获取字段值
            String name = (String) nameField.get(person);
            System.out.println(name); // 输出Alice
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchFieldException e) {
            e.printStackTrace();
        }
    }
}
class Person {
    private String name;
    public Person() {
        this.name = &quot;Unknown&quot;;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;示例4：调用方法&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;public class ReflectionMethodInvoke {
    public static void main(String[] args) {
        try {
            Class&amp;lt;?&amp;gt; clazz = Class.forName(&quot;com.example.Person&quot;);
            Person person = (Person) clazz.newInstance();
            // 获取方法
            Method sayHelloMethod = clazz.getDeclaredMethod(&quot;sayHello&quot;, String.class);
            // 设置可访问性
            sayHelloMethod.setAccessible(true);
            // 调用方法
            sayHelloMethod.invoke(person, &quot;Bob&quot;);
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}
class Person {
    public void sayHello(String name) {
        System.out.println(&quot;Hello, &quot; + name);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;示例5：获取构造方法并创建对象&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;public class ReflectionConstructor {
    public static void main(String[] args) {
        try {
            Class&amp;lt;?&amp;gt; clazz = Class.forName(&quot;com.example.Person&quot;);
            // 获取有参构造方法
            Constructor&amp;lt;?&amp;gt; constructor = clazz.getConstructor(String.class, int.class);
            // 创建对象
            Person person = (Person) constructor.newInstance(&quot;Charlie&quot;, 30);
        } catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}
class Person {
    private String name;
    private int age;
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;示例6: 获取类所有的方法&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.lang.reflect.Type;

public class ReflectionAPITable {

    public static void printAPITable(Object obj) {
        Class&amp;lt;?&amp;gt; clazz = obj.getClass();
        Method[] methods = clazz.getMethods(); // 获取所有公共方法

        // 打印表头
        System.out.printf(&quot;%-20s %-50s %-20s %-10s%n&quot;, &quot;方法名称&quot;, &quot;参数列表&quot;, &quot;返回类型&quot;, &quot;修饰符&quot;);

        // 遍历所有方法
        for (Method method : methods) {
            // 获取方法名称
            String methodName = method.getName();

            // 获取参数类型
            Parameter[] parameters = method.getParameters();
            StringBuilder paramsBuilder = new StringBuilder();
            for (Parameter parameter : parameters) {
                if (paramsBuilder.length() &amp;gt; 0) paramsBuilder.append(&quot;, &quot;);
                paramsBuilder.append(parameter.getType().getSimpleName()).append(&quot; &quot;).append(parameter.getName());
            }
            String params = paramsBuilder.toString();

            // 获取返回类型
            Type returnType = method.getGenericReturnType();
            String returnTypeName = returnType.getTypeName();

            // 获取修饰符
            String modifiers = Modifier.toString(method.getModifiers());

            // 打印方法信息
            System.out.printf(&quot;%-20s %-50s %-20s %-10s%n&quot;, methodName, params, returnTypeName, modifiers);
        }
    }

    public static void main(String[] args) {
        // 示例对象
        Person person = new Person(&quot;Alice&quot;, 30);

        // 打印API表格
        printAPITable(person);
    }
}

class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void sayHello() {
        System.out.println(&quot;Hello, my name is &quot; + name);
    }
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Java网络</title><link>http://zaoweiceng.github.io/posts/java/%E7%BD%91%E7%BB%9C/</link><guid isPermaLink="true">http://zaoweiceng.github.io/posts/java/%E7%BD%91%E7%BB%9C/</guid><description>Java的网络的相关用法</description><pubDate>Fri, 14 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;InetAddress&lt;/h2&gt;
&lt;p&gt;InetAddress：此类表示Internet协议（IP）地址&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;相关方法&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;static InetAddress getByName(String host)&lt;/td&gt;
&lt;td&gt;确定主机名称的IP地址。主机名称可以是机器名称，也可以是IP地址&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;String getHostName()&lt;/td&gt;
&lt;td&gt;获取此IP地址的主机名&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;String getHostAddress()&lt;/td&gt;
&lt;td&gt;返回文本显示中的IP地址字符串&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;代码演示&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class InetAddressDemo {
    public static void main(String[] args) throws UnknownHostException {
		//InetAddress address = InetAddress.getByName(&quot;itheima&quot;);
        InetAddress address = InetAddress.getByName(&quot;192.168.1.66&quot;);

        //public String getHostName()：获取此IP地址的主机名
        String name = address.getHostName();
        //public String getHostAddress()：返回文本显示中的IP地址字符串
        String ip = address.getHostAddress();

        System.out.println(&quot;主机名：&quot; + name);
        System.out.println(&quot;IP地址：&quot; + ip);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;UDP&lt;/h2&gt;
&lt;h3&gt;UDP发送数据&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Java中的UDP通信&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;UDP协议是一种不可靠的网络协议，它在通信的两端各建立一个Socket对象，但是这两个Socket只是发送，接收数据的对象，因此对于基于UDP协议的通信双方而言，没有所谓的客户端和服务器的概念&lt;/li&gt;
&lt;li&gt;Java提供了DatagramSocket类作为基于UDP协议的Socket&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;构造方法&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;DatagramSocket()&lt;/td&gt;
&lt;td&gt;创建数据报套接字并将其绑定到本机地址上的任何可用端口&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DatagramPacket(byte[] buf,int len,InetAddress add,int port)&lt;/td&gt;
&lt;td&gt;创建数据包,发送长度为len的数据包到指定主机的指定端口&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;相关方法&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;void send(DatagramPacket p)&lt;/td&gt;
&lt;td&gt;发送数据报包&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;void close()&lt;/td&gt;
&lt;td&gt;关闭数据报套接字&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;void receive(DatagramPacket p)&lt;/td&gt;
&lt;td&gt;从此套接字接受数据报包&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;发送数据的步骤&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;创建发送端的Socket对象(DatagramSocket)&lt;/li&gt;
&lt;li&gt;创建数据，并把数据打包&lt;/li&gt;
&lt;li&gt;调用DatagramSocket对象的方法发送数据&lt;/li&gt;
&lt;li&gt;关闭发送端&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;代码演示&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class udpSend {
    public static void main(String[] args) throws IOException {
        //创建发送端的Socket对象(DatagramSocket)
        // DatagramSocket() 构造数据报套接字并将其绑定到本地主机上的任何可用端口
        // 空参: 在所有可用端口中随机选择一个使用
        DatagramSocket ds = new DatagramSocket();
        //创建数据，并把数据打包
        //DatagramPacket(byte[] buf, int length, InetAddress address, int port)
        //构造一个数据包，发送长度为 length的数据包到指定主机上的指定端口号。
        byte[] bys = &quot;hello,udp,我来了&quot;.getBytes();
        DatagramPacket dp = new DatagramPacket(
                bys,bys.length, InetAddress.getByName(&quot;127.0.0.1&quot;),12345);
        //调用DatagramSocket对象的方法发送数据
        //void send(DatagramPacket p) 从此套接字发送数据报包
        ds.send(dp);
        //关闭发送端
        //void close() 关闭此数据报套接字
        ds.close();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;UDP接收数据&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;接收数据的步骤&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;创建接收端的Socket对象(DatagramSocket)&lt;/li&gt;
&lt;li&gt;创建一个数据包，用于接收数据&lt;/li&gt;
&lt;li&gt;调用DatagramSocket对象的方法接收数据&lt;/li&gt;
&lt;li&gt;解析数据包，并把数据在控制台显示&lt;/li&gt;
&lt;li&gt;关闭接收端&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;构造方法&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;DatagramPacket(byte[] buf, int len)&lt;/td&gt;
&lt;td&gt;创建一个DatagramPacket用于接收长度为len的数据包&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;相关方法&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;byte[]  getData()&lt;/td&gt;
&lt;td&gt;返回数据缓冲区&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;int  getLength()&lt;/td&gt;
&lt;td&gt;返回要发送的数据的长度或接收的数据的长度&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;示例代码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class updRecv {
    public static void main(String[] args) throws IOException {
        DatagramSocket ds = new DatagramSocket(12345);
        //创建一个数据包，用于接收数据
        byte[] bys = new byte[1024];
        DatagramPacket dp = new DatagramPacket(bys, bys.length);
        //调用DatagramSocket对象的方法接收数据
        ds.receive(dp);
        //解析数据包，并把数据在控制台显示
        System.out.println(&quot;数据是：&quot; +
                new String(dp.getData(), 0, dp.getLength()));
        System.out.println(&quot;&quot; + dp.getAddress()+dp.getPort());
        ds.close();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;UDP通信程序练习&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;案例需求&lt;/p&gt;
&lt;p&gt;UDP发送数据：数据来自于键盘录入，直到输入的数据是886，发送数据结束&lt;/p&gt;
&lt;p&gt;UDP接收数据：因为接收端不知道发送端什么时候停止发送，故采用死循环接收&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;代码实现&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/*
    UDP发送数据：
        数据来自于键盘录入，直到输入的数据是886，发送数据结束
 */
public class SendDemo {
    public static void main(String[] args) throws IOException {
        //创建发送端的Socket对象(DatagramSocket)
        DatagramSocket ds = new DatagramSocket();
        //键盘录入数据
        Scanner sc = new Scanner(System.in);
        while (true) {
          	String s = sc.nextLine();
            //输入的数据是886，发送数据结束
            if (&quot;886&quot;.equals(s)) {
                break;
            }
            //创建数据，并把数据打包
            byte[] bys = s.getBytes();
            DatagramPacket dp = new DatagramPacket(bys, bys.length, InetAddress.getByName(&quot;192.168.1.66&quot;), 12345);

            //调用DatagramSocket对象的方法发送数据
            ds.send(dp);
        }
        //关闭发送端
        ds.close();
    }
}

/*
    UDP接收数据：
        因为接收端不知道发送端什么时候停止发送，故采用死循环接收
 */
public class ReceiveDemo {
    public static void main(String[] args) throws IOException {
        //创建接收端的Socket对象(DatagramSocket)
        DatagramSocket ds = new DatagramSocket(12345);
        while (true) {
            //创建一个数据包，用于接收数据
            byte[] bys = new byte[1024];
            DatagramPacket dp = new DatagramPacket(bys, bys.length);
            //调用DatagramSocket对象的方法接收数据
            ds.receive(dp);
            //解析数据包，并把数据在控制台显示
            System.out.println(&quot;数据是：&quot; + new String(dp.getData(), 0, dp.getLength()));
        }
        //关闭接收端
//        ds.close();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;UDP三种通讯方式&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;单播&lt;/p&gt;
&lt;p&gt;单播用于两个主机之间的端对端通信&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;组播&lt;/p&gt;
&lt;p&gt;组播用于对一组特定的主机进行通信&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;广播&lt;/p&gt;
&lt;p&gt;广播用于一个主机对整个局域网上所有主机上的数据通信&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;UDP组播实现&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;实现步骤&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;发送端
&lt;ol&gt;
&lt;li&gt;创建发送端的Socket对象(DatagramSocket)&lt;/li&gt;
&lt;li&gt;创建数据，并把数据打包(DatagramPacket)&lt;/li&gt;
&lt;li&gt;调用DatagramSocket对象的方法发送数据(在单播中,这里是发给指定IP的电脑但是在组播当中,这里是发给组播地址)&lt;/li&gt;
&lt;li&gt;释放资源&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;接收端
&lt;ol&gt;
&lt;li&gt;创建接收端Socket对象(MulticastSocket)&lt;/li&gt;
&lt;li&gt;创建一个箱子,用于接收数据&lt;/li&gt;
&lt;li&gt;把当前计算机绑定一个组播地址&lt;/li&gt;
&lt;li&gt;将数据接收到箱子中&lt;/li&gt;
&lt;li&gt;解析数据包,并打印数据&lt;/li&gt;
&lt;li&gt;释放资源&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;代码实现&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 发送端
public class ClinetDemo {
    public static void main(String[] args) throws IOException {
        // 1. 创建发送端的Socket对象(DatagramSocket)
        DatagramSocket ds = new DatagramSocket();
        String s = &quot;hello 组播&quot;;
        byte[] bytes = s.getBytes();
        InetAddress address = InetAddress.getByName(&quot;224.0.1.0&quot;); //指定主播地址
        int port = 10000;
        // 2. 创建数据，并把数据打包(DatagramPacket)
        DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);
        // 3. 调用DatagramSocket对象的方法发送数据(在单播中,这里是发给指定IP的电脑但是在组播当中,这里是发给组播地址)
        ds.send(dp);
        // 4. 释放资源
        ds.close();
    }
}
// 接收端
public class ServerDemo {
    public static void main(String[] args) throws IOException {
        // 1. 创建接收端Socket对象(MulticastSocket)
        MulticastSocket ms = new MulticastSocket(10000);
        // 2. 创建一个箱子,用于接收数据
        DatagramPacket dp = new DatagramPacket(new byte[1024],1024);
        // 3. 把当前计算机绑定一个组播地址,表示添加到这一组中.
        ms.joinGroup(InetAddress.getByName(&quot;224.0.1.0&quot;));
        // 4. 将数据接收到箱子中
        ms.receive(dp);
        // 5. 解析数据包,并打印数据
        byte[] data = dp.getData();
        int length = dp.getLength();
        System.out.println(new String(data,0,length));
        // 6. 释放资源
        ms.close();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;UDP广播实现&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;实现步骤&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;发送端
&lt;ol&gt;
&lt;li&gt;创建发送端Socket对象(DatagramSocket)&lt;/li&gt;
&lt;li&gt;创建存储数据的箱子,将广播地址封装进去&lt;/li&gt;
&lt;li&gt;发送数据&lt;/li&gt;
&lt;li&gt;释放资源&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;接收端
&lt;ol&gt;
&lt;li&gt;创建接收端的Socket对象(DatagramSocket)&lt;/li&gt;
&lt;li&gt;创建一个数据包，用于接收数据&lt;/li&gt;
&lt;li&gt;调用DatagramSocket对象的方法接收数据&lt;/li&gt;
&lt;li&gt;解析数据包，并把数据在控制台显示&lt;/li&gt;
&lt;li&gt;关闭接收端&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;代码实现&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 发送端
public class ClientDemo {
    public static void main(String[] args) throws IOException {
      	// 1. 创建发送端Socket对象(DatagramSocket)
        DatagramSocket ds = new DatagramSocket();
		// 2. 创建存储数据的箱子,将广播地址封装进去
        String s = &quot;广播 hello&quot;;
        byte[] bytes = s.getBytes();
        InetAddress address = InetAddress.getByName(&quot;255.255.255.255&quot;);
        int port = 10000;
        DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);
		// 3. 发送数据
        ds.send(dp);
		// 4. 释放资源
        ds.close();
    }
}

// 接收端
public class ServerDemo {
    public static void main(String[] args) throws IOException {
        // 1. 创建接收端的Socket对象(DatagramSocket)
        DatagramSocket ds = new DatagramSocket(10000);
        // 2. 创建一个数据包，用于接收数据
        DatagramPacket dp = new DatagramPacket(new byte[1024],1024);
        // 3. 调用DatagramSocket对象的方法接收数据
        ds.receive(dp);
        // 4. 解析数据包，并把数据在控制台显示
        byte[] data = dp.getData();
        int length = dp.getLength();
        System.out.println(new String(data,0,length));
        // 5. 关闭接收端
        ds.close();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;TCP&lt;/h2&gt;
&lt;h3&gt;TCP发送数据&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Java中的TCP通信&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Java对基于TCP协议的的网络提供了良好的封装，使用Socket对象来代表两端的通信端口，并通过Socket产生IO流来进行网络通信。&lt;/li&gt;
&lt;li&gt;Java为客户端提供了Socket类，为服务器端提供了ServerSocket类&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;构造方法&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Socket(InetAddress address,int port)&lt;/td&gt;
&lt;td&gt;创建流套接字并将其连接到指定IP指定端口号&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Socket(String host, int port)&lt;/td&gt;
&lt;td&gt;创建流套接字并将其连接到指定主机上的指定端口号&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;相关方法&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;InputStream  getInputStream()&lt;/td&gt;
&lt;td&gt;返回此套接字的输入流&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OutputStream getOutputStream()&lt;/td&gt;
&lt;td&gt;返回此套接字的输出流&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;示例代码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class Client {
    public static void main(String[] args) throws IOException {
        //TCP协议，发送数据
        //1.创建Socket对象
        //细节：在创建对象的同时会连接服务端
        //      如果连接不上，代码会报错
        Socket socket = new Socket(&quot;127.0.0.1&quot;,10000);
        //2.可以从连接通道中获取输出流
        OutputStream os = socket.getOutputStream();
        //写出数据
        os.write(&quot;aaa&quot;.getBytes());
        //3.释放资源
        os.close();
        socket.close();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;TCP接收数据&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;构造方法&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ServletSocket(int port)&lt;/td&gt;
&lt;td&gt;创建绑定到指定端口的服务器套接字&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;相关方法&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Socket accept()&lt;/td&gt;
&lt;td&gt;监听要连接到此的套接字并接受它&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;注意事项&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;accept方法是阻塞的,作用就是等待客户端连接&lt;/li&gt;
&lt;li&gt;客户端创建对象并连接服务器,此时是通过三次握手协议,保证跟服务器之间的连接&lt;/li&gt;
&lt;li&gt;针对客户端来讲,是往外写的,所以是输出流
针对服务器来讲,是往里读的,所以是输入流&lt;/li&gt;
&lt;li&gt;read方法也是阻塞的&lt;/li&gt;
&lt;li&gt;客户端在关流的时候,还多了一个往服务器写结束标记的动作&lt;/li&gt;
&lt;li&gt;最后一步断开连接,通过四次挥手协议保证连接终止&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;三次握手和四次挥手&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;三次握手确保连接建立&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;四次挥手确保连接通道里面的数据已经处理完毕&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;示例代码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class Server {
    public static void main(String[] args) throws IOException {
        //TCP协议，接收数据

        //1.创建对象ServerSocker
        ServerSocket ss = new ServerSocket(10000);

        //2.监听客户端的链接
        Socket socket = ss.accept();

        //3.从连接通道中获取输入流读取数据
        InputStream is = socket.getInputStream();
        int b;
        while ((b = is.read()) != -1){
            System.out.println((char) b);
        }

        //4.释放资源
        socket.close();
        ss.close();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;TCP程序练习（传输中文）&lt;/h3&gt;
&lt;p&gt;发送端：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class Client {
    public static void main(String[] args) throws IOException {
        //TCP协议，发送数据

        //1.创建Socket对象
        //细节：在创建对象的同时会连接服务端
        //      如果连接不上，代码会报错
        Socket socket = new Socket(&quot;127.0.0.1&quot;,10000);


        //2.可以从连接通道中获取输出流
        OutputStream os = socket.getOutputStream();
        //写出数据
        os.write(&quot;你好你好&quot;.getBytes());//12字节

        //3.释放资源
        os.close();
        socket.close();

    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;接收端：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class Server {
    public static void main(String[] args) throws IOException {
        //TCP协议，接收数据

        //1.创建对象ServerSocker
        ServerSocket ss = new ServerSocket(10000);

        //2.监听客户端的链接
        Socket socket = ss.accept();

        //3.从连接通道中获取输入流读取数据
        InputStream is = socket.getInputStream();
        // 转换流,转为字符流
        InputStreamReader isr = new InputStreamReader(is);
        // 通过Buffer读取,提高效率
        BufferedReader br = new BufferedReader(isr);

        // BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));

        int b;
        while ((b = br.read()) != -1){
            System.out.print((char) b);
        }

        //4.释放资源
        socket.close();
        ss.close();

    }
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>C++的构造函数</title><link>http://zaoweiceng.github.io/posts/cpp/%E6%9E%84%E9%80%A0%E5%87%BD%E6%95%B0/</link><guid isPermaLink="true">http://zaoweiceng.github.io/posts/cpp/%E6%9E%84%E9%80%A0%E5%87%BD%E6%95%B0/</guid><description>C++中的构造函数是一种特殊的成员函数，用于在创建对象时初始化对象。构造函数在对象被创建时自动调用，确保对象在使用前处于有效状态。</description><pubDate>Thu, 13 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;C++中的构造函数是一种特殊的成员函数，用于在创建对象时初始化对象。构造函数在对象被创建时自动调用，确保对象在使用前处于有效状态。C++支持多种形式的构造函数，以满足不同的初始化需求。以下是一些常见的构造函数类型：&lt;/p&gt;
&lt;h3&gt;默认构造函数&lt;/h3&gt;
&lt;p&gt;默认构造函数是没有参数的构造函数，或者所有参数都有默认值的构造函数。如果类中没有显式定义任何构造函数，编译器会自动生成一个默认构造函数。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class MyClass {
public:
    MyClass() { // 默认构造函数
        // 初始化代码
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;参数化构造函数&lt;/h3&gt;
&lt;p&gt;参数化构造函数是带有参数的构造函数，允许在创建对象时传递初始化值。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class MyClass {
public:
    MyClass(int value) { // 参数化构造函数
        // 使用value初始化成员变量
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;拷贝构造函数&lt;/h3&gt;
&lt;p&gt;拷贝构造函数用于创建一个新对象，作为另一个同类型对象的副本。拷贝构造函数的参数是同类型的引用。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class MyClass {
public:
    MyClass(const MyClass&amp;amp; other) { // 拷贝构造函数
        // 从other对象复制数据
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;移动构造函数&lt;/h3&gt;
&lt;p&gt;移动构造函数用于转移另一个同类型对象的资源，而不是复制资源。它通常用于优化包含动态内存分配的类的性能。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class MyClass {
public:
    MyClass(MyClass&amp;amp;&amp;amp; other) noexcept { // 移动构造函数
        // 转移other对象的数据
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;委托构造函数&lt;/h3&gt;
&lt;p&gt;委托构造函数是一种构造函数，它调用同一个类中的另一个构造函数来初始化对象。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class MyClass {
public:
    MyClass() : MyClass(0) { // 委托给参数化构造函数
        // 其他初始化代码
    }
    MyClass(int value) {
        // 使用value初始化成员变量
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;列表初始化构造函数&lt;/h3&gt;
&lt;p&gt;列表初始化构造函数使用初始化列表来初始化成员变量，这通常比在构造函数体内赋值更高效。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class MyClass {
private:
    int a;
    double b;
public:
    MyClass(int aVal, double bVal) : a(aVal), b(bVal) { // 列表初始化
        // 构造函数体可以留空或包含其他代码
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;显式构造函数&lt;/h3&gt;
&lt;p&gt;显式构造函数使用&lt;code&gt;explicit&lt;/code&gt;关键字，防止构造函数被用于隐式类型转换。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class MyClass {
public:
    explicit MyClass(int value) { // 显式构造函数
        // 使用value初始化成员变量
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;虚构造函数&lt;/h3&gt;
&lt;p&gt;在C++中，构造函数不能是虚函数，但可以调用虚函数。虚构造函数的概念通常用于指代在构造函数中调用虚函数的情况，这可能导致未定义行为，因为对象在构造过程中其虚表可能尚未完全初始化。&lt;/p&gt;
&lt;h3&gt;禁止赋值和按值传递&lt;/h3&gt;
&lt;p&gt;在C++中，如果你想要禁止一个类的对象被复制或按值传递，你需要删除拷贝构造函数和拷贝赋值运算符。此外，为了防止移动操作，你还可能需要删除移动构造函数和移动赋值运算符。这样可以确保类的对象不会被复制或移动。
以下是如何实现这一点的示例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class NonCopyable {
public:
    // 删除拷贝构造函数
    NonCopyable(const NonCopyable&amp;amp;) = delete;
    
    // 删除拷贝赋值运算符
    NonCopyable&amp;amp; operator=(const NonCopyable&amp;amp;) = delete;
    // 删除移动构造函数（可选，但推荐）
    NonCopyable(NonCopyable&amp;amp;&amp;amp;) = delete;
    
    // 删除移动赋值运算符（可选，但推荐）
    NonCopyable&amp;amp; operator=(NonCopyable&amp;amp;&amp;amp;) = delete;
    // 默认构造函数
    NonCopyable() {
        // 初始化代码
    }
    // 其他成员函数
    // ...
};
// 使用示例
int main() {
    NonCopyable obj1; // 正确：调用默认构造函数
    // NonCopyable obj2 = obj1; // 错误：拷贝构造函数被删除
    // NonCopyable obj3(obj1); // 错误：拷贝构造函数被删除
    // obj1 = obj2; // 错误：拷贝赋值运算符被删除
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这个示例中，&lt;code&gt;NonCopyable&lt;/code&gt;类的拷贝构造函数和拷贝赋值运算符都被标记为&lt;code&gt;delete&lt;/code&gt;，这意味着它们不能被使用，尝试复制&lt;code&gt;NonCopyable&lt;/code&gt;对象将会导致编译错误。
同样，移动构造函数和移动赋值运算符也被删除，这是为了防止通过移动语义来复制对象。虽然移动操作通常不涉及深拷贝，但在某些情况下，你可能仍然想要禁止移动以保持对象的唯一性。
通过这种方式，你可以确保类的对象不会被复制或按值传递，这对于管理资源（如文件句柄、网络连接等）的类来说是非常有用的，因为这些资源通常不应该被共享或复制。&lt;/p&gt;
&lt;p&gt;如果继承自一个已经删除了拷贝构造函数和拷贝赋值运算符的类（如&lt;code&gt;NonCopyable&lt;/code&gt;），那么派生类也会继承这些特性，即派生类的对象同样不能被复制和按值传递。
在C++中，如果基类中的某个成员函数被删除或声明为私有，那么在派生类中也无法使用该成员函数，除非派生类提供了自己的实现。由于拷贝构造函数和拷贝赋值运算符是特殊的成员函数，它们遵循同样的规则。
以下是一个示例，展示了如何继承&lt;code&gt;NonCopyable&lt;/code&gt;类，并说明派生类的对象也不能被复制和按值传递：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class NonCopyable {
public:
    // 删除拷贝构造函数
    NonCopyable(const NonCopyable&amp;amp;) = delete;
    // 删除拷贝赋值运算符
    NonCopyable&amp;amp; operator=(const NonCopyable&amp;amp;) = delete;
    // 默认构造函数
    NonCopyable() {
        // 初始化代码
    }
    // 其他成员函数
    // ...
};
// 派生类
class Derived : public NonCopyable {
public:
    // Derived类的构造函数
    Derived() {
        // 初始化代码
    }
    // 其他成员函数
    // ...
};
// 使用示例
int main() {
    Derived obj1; // 正确：调用默认构造函数
    // Derived obj2 = obj1; // 错误：拷贝构造函数被删除
    // obj1 = obj2; // 错误：拷贝赋值运算符被删除
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这个示例中，&lt;code&gt;Derived&lt;/code&gt;类继承自&lt;code&gt;NonCopyable&lt;/code&gt;类。由于&lt;code&gt;NonCopyable&lt;/code&gt;类已经删除了拷贝构造函数和拷贝赋值运算符，因此&lt;code&gt;Derived&lt;/code&gt;类的对象也不能被复制和按值传递。尝试这样做会导致编译错误。
这种设计模式在C++中很常见，特别是当你想要确保类的对象是唯一的，或者当你想要防止资源的意外复制时。通过继承一个非拷贝able的基类，你可以很容易地传播这个特性到整个类层次结构中。&lt;/p&gt;
&lt;h3&gt;注意事项：&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;构造函数不能有返回类型，即使是&lt;code&gt;void&lt;/code&gt;也不行。&lt;/li&gt;
&lt;li&gt;构造函数可以重载，即可以定义多个构造函数，只要它们的参数列表不同。&lt;/li&gt;
&lt;li&gt;如果类中没有显式定义构造函数，编译器会提供默认的构造函数、拷贝构造函数和移动构造函数（如果适用）。&lt;/li&gt;
&lt;li&gt;如果类中定义了任何构造函数，编译器就不会提供默认构造函数。
了解和正确使用这些构造函数类型是C++编程中的重要部分，它们有助于创建灵活、高效且易于维护的类。&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>java File类</title><link>http://zaoweiceng.github.io/posts/java/file/</link><guid isPermaLink="true">http://zaoweiceng.github.io/posts/java/file/</guid><description>Java的文件操作</description><pubDate>Thu, 13 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;构造函数&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;public File(String pathname)&lt;/td&gt;
&lt;td&gt;根据文件路径创建文件对象&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;public File(String parent, String child)&lt;/td&gt;
&lt;td&gt;根据父路径和子路径创建文件对象&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;public File(File parent, String child)&lt;/td&gt;
&lt;td&gt;根据父路径对应的文件对象和子路径创建文件对象&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;常用方法&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;public String getAbsolutePath() &lt;/code&gt; ：返回此File的绝对路径名字符串。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;public String getPath()&lt;/code&gt; ：将此File转换为路径名字符串。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;public String getName()&lt;/code&gt;  ：返回由此File表示的文件或目录的名称。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;public long length()&lt;/code&gt;  ：返回由此File表示的文件的长度（字节）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;public long lastModified()&lt;/code&gt;: 返回文件的最后修改时间。&lt;/p&gt;
&lt;p&gt;方法演示，代码如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class FileGet {
    public static void main(String[] args) {
        File f = new File(&quot;d:/aaa/bbb.java&quot;);     
        System.out.println(&quot;文件绝对路径:&quot;+f.getAbsolutePath());
        System.out.println(&quot;文件构造路径:&quot;+f.getPath());
        System.out.println(&quot;文件名称:&quot;+f.getName());
        System.out.println(&quot;文件长度:&quot;+f.length()+&quot;字节&quot;);

        File f2 = new File(&quot;d:/aaa&quot;);     
        System.out.println(&quot;目录绝对路径:&quot;+f2.getAbsolutePath());
        System.out.println(&quot;目录构造路径:&quot;+f2.getPath());
        System.out.println(&quot;目录名称:&quot;+f2.getName());
        System.out.println(&quot;目录长度:&quot;+f2.length());
    }
}
输出结果：
文件绝对路径:d:\aaa\bbb.java
文件构造路径:d:\aaa\bbb.java
文件名称:bbb.java
文件长度:636字节

目录绝对路径:d:\aaa
目录构造路径:d:\aaa
目录名称:aaa
目录长度:4096
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;API中说明：length()，表示文件的长度。但是File对象表示目录，则返回值未指定。若需要获取某个文件夹的大小，需要遍历文件夹下每个文件的大小，然后累计。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;绝对路径&lt;/strong&gt;：从盘符开始的路径，这是一个完整的路径。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;相对路径&lt;/strong&gt;：相对于项目目录的路径，这是一个便捷的路径，开发中经常使用。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;public class FilePath {
    public static void main(String[] args) {
      	// D盘下的bbb.java文件
        File f = new File(&quot;D:\\bbb.java&quot;);
        System.out.println(f.getAbsolutePath());
      	
		// 项目下的bbb.java文件
        File f2 = new File(&quot;bbb.java&quot;);
        System.out.println(f2.getAbsolutePath());
    }
}
输出结果：
D:\bbb.java
D:\idea_project_test4\bbb.java
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;public boolean exists()&lt;/code&gt; ：此File表示的文件或目录是否实际存在。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;public boolean isDirectory()&lt;/code&gt; ：此File表示的是否为目录。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;public boolean isFile()&lt;/code&gt; ：此File表示的是否为文件。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;方法演示，代码如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class FileIs {
    public static void main(String[] args) {
        File f = new File(&quot;d:\\aaa\\bbb.java&quot;);
        File f2 = new File(&quot;d:\\aaa&quot;);
      	// 判断是否存在
        System.out.println(&quot;d:\\aaa\\bbb.java 是否存在:&quot;+f.exists());
        System.out.println(&quot;d:\\aaa 是否存在:&quot;+f2.exists());
      	// 判断是文件还是目录
        System.out.println(&quot;d:\\aaa 文件?:&quot;+f2.isFile());
        System.out.println(&quot;d:\\aaa 目录?:&quot;+f2.isDirectory());
    }
}
输出结果：
d:\aaa\bbb.java 是否存在:true
d:\aaa 是否存在:true
d:\aaa 文件?:false
d:\aaa 目录?:true
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;public boolean createNewFile()&lt;/code&gt; ：当且仅当具有该名称的文件尚不存在时，创建一个新的空文件。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;public boolean delete()&lt;/code&gt; ：删除由此File表示的文件或目录。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;public boolean mkdir()&lt;/code&gt; ：创建由此File表示的目录。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;public boolean mkdirs()&lt;/code&gt; ：创建由此File表示的目录，包括任何必需但不存在的父目录。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;方法演示，代码如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class FileCreateDelete {
    public static void main(String[] args) throws IOException {
        // 文件的创建
        File f = new File(&quot;aaa.txt&quot;);
        System.out.println(&quot;是否存在:&quot;+f.exists()); // false
        System.out.println(&quot;是否创建:&quot;+f.createNewFile()); // true
        System.out.println(&quot;是否存在:&quot;+f.exists()); // true
		
     	// 目录的创建
      	File f2= new File(&quot;newDir&quot;);	
        System.out.println(&quot;是否存在:&quot;+f2.exists());// false
        System.out.println(&quot;是否创建:&quot;+f2.mkdir());	// true
        System.out.println(&quot;是否存在:&quot;+f2.exists());// true

		// 创建多级目录
      	File f3= new File(&quot;newDira\\newDirb&quot;);
        System.out.println(f3.mkdir());// false
        File f4= new File(&quot;newDira\\newDirb&quot;);
        System.out.println(f4.mkdirs());// true
      
      	// 文件的删除
       	System.out.println(f.delete());// true
      
      	// 目录的删除
        System.out.println(f2.delete());// true
        System.out.println(f4.delete());// false
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;API中说明：delete方法，如果此File表示目录，则目录必须为空才能删除。不走回收站。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;目录的遍历&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;public String[] list()&lt;/code&gt; ：返回一个String数组，表示该File目录中的所有子文件或目录。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;public File[] listFiles()&lt;/code&gt; ：返回一个File数组，表示该File目录中的所有的子文件或目录。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;public class FileFor {
    public static void main(String[] args) {
        File dir = new File(&quot;d:\\java_code&quot;);
      
      	//获取当前目录下的文件以及文件夹的名称。
		String[] names = dir.list();
		for(String name : names){
			System.out.println(name);
		}
        //获取当前目录下的文件以及文件夹对象，只要拿到了文件对象，那么就可以获取更多信息
        File[] files = dir.listFiles();
        for (File file : files) {
            System.out.println(file);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;调用listFiles方法的File对象，表示的必须是实际存在的目录，否则返回null，无法进行遍历。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;public static File[] listRoots()&lt;/code&gt;: 获取系统中所有的盘符。&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>IO流</title><link>http://zaoweiceng.github.io/posts/java/io%E6%B5%81/</link><guid isPermaLink="true">http://zaoweiceng.github.io/posts/java/io%E6%B5%81/</guid><description>Java的IO流的基本用法</description><pubDate>Thu, 13 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;字节流&lt;/h2&gt;
&lt;h3&gt;字节输出流：OutputStream&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;java.io.OutputStream &lt;/code&gt;抽象类是表示字节输出流的所有类的超类，将指定的字节信息写出到目的地。它定义了字节输出流的基本共性功能方法。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;public void close()&lt;/code&gt; ：关闭此输出流并释放与此流相关联的任何系统资源。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;public void flush() &lt;/code&gt; ：刷新此输出流并强制任何缓冲的输出字节被写出。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;public void write(byte[] b)&lt;/code&gt;：将 b.length字节从指定的字节数组写入此输出流。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;public void write(byte[] b, int off, int len)&lt;/code&gt; ：从指定的字节数组写入 len字节，从偏移量 off开始输出到此输出流。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;public abstract void write(int b)&lt;/code&gt; ：将指定的字节输出流。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;close方法，当完成流的操作时，必须调用此方法，释放系统资源。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;FileOutputStream类&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;java.io.FileOutputStream &lt;/code&gt;类是文件输出流，用于将数据写出到文件。&lt;/p&gt;
&lt;h4&gt;构造方法&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;public FileOutputStream(File file)&lt;/code&gt;：创建文件输出流以写入由指定的 File对象表示的文件。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;public FileOutputStream(String name)&lt;/code&gt;： 创建文件输出流以指定的名称写入文件。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;当你创建一个流对象时，必须传入一个文件路径。该路径下，如果没有这个文件，会创建该文件，但父级文件夹必须存在。如果有这个文件，会&lt;strong&gt;清空&lt;/strong&gt;这个文件的数据。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;构造举例，代码如下：&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;public class FileOutputStreamConstructor throws IOException {
    public static void main(String[] args) {
   	 	// 使用File对象创建流对象
        File file = new File(&quot;a.txt&quot;);
        FileOutputStream fos = new FileOutputStream(file);
        // 使用文件名称创建流对象
        FileOutputStream fos = new FileOutputStream(&quot;b.txt&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;写出字节数据&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;void write(int b)&lt;/code&gt;：一次写一个数据&lt;/li&gt;
&lt;li&gt;&lt;code&gt;void write(byte[] b)&lt;/code&gt;：写入数组的数据&lt;/li&gt;
&lt;li&gt;&lt;code&gt;void write(byte[] b, int off, int len)&lt;/code&gt;：写入数组数据带偏移量(是写入数组中的偏移量，不是目标的偏移量)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;数据追加续写&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;public FileOutputStream(File file, boolean append)&lt;/code&gt;： 创建文件输出流以写入由指定的 File对象表示的文件。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;public FileOutputStream(String name, boolean append)&lt;/code&gt;： 创建文件输出流以指定的名称写入文件。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这两个构造方法，参数中都需要传入一个boolean类型的值，&lt;code&gt;true&lt;/code&gt; 表示追加数据，&lt;code&gt;false&lt;/code&gt; 表示清空原有数据。这样创建的输出流对象，就可以指定是否追加续写了，代码使用演示：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class FOSWrite {
    public static void main(String[] args) throws IOException {
        // 使用文件名称创建流对象
        FileOutputStream fos = new FileOutputStream(&quot;fos.txt&quot;，true);     
      	// 字符串转换为字节数组
      	byte[] b = &quot;abcde&quot;.getBytes();
		// 写出从索引2开始，2个字节。索引2是c，两个字节，也就是cd。
        fos.write(b);
      	// 关闭资源
        fos.close();
    }
}
文件操作前：cd
文件操作后：cdabcde
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;写出换行&lt;/h4&gt;
&lt;p&gt;Windows系统里，换行符号是&lt;code&gt;\r\n&lt;/code&gt; 。以指定是否追加续写了，代码使用演示：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class FOSWrite {
    public static void main(String[] args) throws IOException {
        // 使用文件名称创建流对象
        FileOutputStream fos = new FileOutputStream(&quot;fos.txt&quot;);  
      	// 定义字节数组
      	byte[] words = {97,98,99,100,101};
      	// 遍历数组
        for (int i = 0; i &amp;lt; words.length; i++) {
          	// 写出一个字节
            fos.write(words[i]);
          	// 写出一个换行, 换行符号转成数组写出
            fos.write(&quot;\r\n&quot;.getBytes());
        }
      	// 关闭资源
        fos.close();
    }
}

输出结果：
a
b
c
d
e
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;回车符&lt;code&gt;\r&lt;/code&gt;和换行符&lt;code&gt;\n&lt;/code&gt; ：
&lt;ul&gt;
&lt;li&gt;回车符：回到一行的开头（return）。&lt;/li&gt;
&lt;li&gt;换行符：下一行（newline）。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;系统中的换行：
&lt;ul&gt;
&lt;li&gt;Windows系统里，每行结尾是 &lt;code&gt;回车+换行&lt;/code&gt; ，即&lt;code&gt;\r\n&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;Unix系统里，每行结尾只有 &lt;code&gt;换行&lt;/code&gt; ，即&lt;code&gt;\n&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;Mac系统里，每行结尾是 &lt;code&gt;回车&lt;/code&gt; ，即&lt;code&gt;\r&lt;/code&gt;。从 Mac OS X开始与Linux统一。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h3&gt;字节输入流：InputStream&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;java.io.InputStream &lt;/code&gt;抽象类是表示字节输入流的所有类的超类，可以读取字节信息到内存中。它定义了字节输入流的基本共性功能方法。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;public void close()&lt;/code&gt; ：关闭此输入流并释放与此流相关联的任何系统资源。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;public abstract int read()&lt;/code&gt;： 从输入流读取数据的下一个字节。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;public int read(byte[] b)&lt;/code&gt;： 从输入流中读取一些字节数，并将它们存储到字节数组 b中 。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;close方法，当完成流的操作时，必须调用此方法，释放系统资源。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;FileInputStream类&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;java.io.FileInputStream &lt;/code&gt;类是文件输入流，从文件中读取字节。&lt;/p&gt;
&lt;h4&gt;构造方法&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;FileInputStream(File file)&lt;/code&gt;： 通过打开与实际文件的连接来创建一个 FileInputStream ，该文件由文件系统中的 File对象 file命名。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;FileInputStream(String name)&lt;/code&gt;： 通过打开与实际文件的连接来创建一个 FileInputStream ，该文件由文件系统中的路径名 name命名。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;当你创建一个流对象时，必须传入一个文件路径。该路径下，如果没有该文件,会抛出&lt;code&gt;FileNotFoundException&lt;/code&gt; 。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;构造举例，代码如下：&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;public class FileInputStreamConstructor throws IOException{
    public static void main(String[] args) {
   	 	// 使用File对象创建流对象
        File file = new File(&quot;a.txt&quot;);
        FileInputStream fos = new FileInputStream(file);
        // 使用文件名称创建流对象
        FileInputStream fos = new FileInputStream(&quot;b.txt&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;读取字节数据&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;读取字节&lt;/strong&gt;：&lt;code&gt;read&lt;/code&gt;方法，每次可以读取一个字节的数据，提升为int类型，读取到文件末尾，返回&lt;code&gt;-1&lt;/code&gt;，代码使用演示：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;public class FISRead {
    public static void main(String[] args) throws IOException{
      	// 使用文件名称创建流对象
       	FileInputStream fis = new FileInputStream(&quot;read.txt&quot;);
      	// 读取数据，返回一个字节
        int read = fis.read();
        System.out.println((char) read);
      	// 读取到末尾,返回-1
       	read = fis.read();
        System.out.println( read);
		// 关闭资源
        fis.close();
    }
}
输出结果：
a
-1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;循环改进读取方式，代码使用演示：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class FISRead {
    public static void main(String[] args) throws IOException{
      	// 使用文件名称创建流对象
       	FileInputStream fis = new FileInputStream(&quot;read.txt&quot;);
      	// 定义变量，保存数据
        int b ；
        // 循环读取
        while ((b = fis.read())!=-1) {
            System.out.println((char)b);
        }
		// 关闭资源
        fis.close();
    }
}
输出结果：
a
b
c
d
e
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;虽然读取了一个字节，但是会自动提升为int类型。&lt;/li&gt;
&lt;li&gt;流操作完毕后，必须释放系统资源，调用close方法。&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;使用字节数组读取&lt;/strong&gt;：&lt;code&gt;read(byte[] b)&lt;/code&gt;，每次读取b的长度个字节到数组中，返回读取到的有效字节个数，读取到末尾时，返回&lt;code&gt;-1&lt;/code&gt; ，代码使用演示：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;public class FISRead {
    public static void main(String[] args) throws IOException{
      	// 使用文件名称创建流对象.
       	FileInputStream fis = new FileInputStream(&quot;read.txt&quot;); // 文件中为abcde
      	// 定义变量，作为有效个数
        int len ；
        // 定义字节数组，作为装字节数据的容器   
        byte[] b = new byte[2];
        // 循环读取
        while (( len= fis.read(b))!=-1) {
           	// 每次读取后,把数组变成字符串打印
            System.out.println(new String(b));
        }
		// 关闭资源
        fis.close();
    }
}

输出结果：
ab
cd
ed
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;错误数据&lt;code&gt;d&lt;/code&gt;，是由于最后一次读取时，只读取一个字节&lt;code&gt;e&lt;/code&gt;，数组中，上次读取的数据没有被完全替换，所以要通过&lt;code&gt;len&lt;/code&gt; ，获取有效的字节，代码使用演示：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class FISRead {
    public static void main(String[] args) throws IOException{
      	// 使用文件名称创建流对象.
       	FileInputStream fis = new FileInputStream(&quot;read.txt&quot;); // 文件中为abcde
      	// 定义变量，作为有效个数
        int len ；
        // 定义字节数组，作为装字节数据的容器   
        byte[] b = new byte[2];
        // 循环读取
        while (( len= fis.read(b))!=-1) {
           	// 每次读取后,把数组的有效字节部分，变成字符串打印
            System.out.println(new String(b，0，len));//  len 每次读取的有效字节个数
        }
		// 关闭资源
        fis.close();
    }
}

输出结果：
ab
cd
e
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;使用数组读取，每次读取多个字节，减少了系统间的IO操作次数，从而提高了读写的效率，建议开发中使用。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;字符流&lt;/h2&gt;
&lt;p&gt;字符流 = 字节流 + 对字符集的特殊处理&lt;/p&gt;
&lt;p&gt;遇到中文时，一次会读入多个字节&lt;/p&gt;
&lt;h3&gt;FileReader&lt;/h3&gt;
&lt;p&gt;继承自Reader&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;public FileReader(File file)&lt;/code&gt;： 创建字符输入流，关联本地文件&lt;/li&gt;
&lt;li&gt;&lt;code&gt;public FileReader(String path)&lt;/code&gt;： 创建字符输入流，关联本地文件&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;读取数据&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;public int read()&lt;/code&gt;：读取数据，读到末尾返回-1&lt;/li&gt;
&lt;li&gt;&lt;code&gt;public int read(char[] buffer)&lt;/code&gt;：读取多个数据，读到末尾返回-1&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;按字节读取，遇到中文一次读取多个字节，读取后解码，返回一个整数，会转成十进制数据。如果需要字符数据，则需要使用&lt;code&gt;(char)&lt;/code&gt;强制转换。&lt;/p&gt;
&lt;p&gt;释放资源&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;public int close()&lt;/code&gt;：释放资源&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;FileReader fr = new FileReader(&quot;a.txt&quot;);
char[] chars = new char[2];
int len;
while((len = fr.read(chars))!=-1){
    System.out.print(new String(chars, 0, len));
}
fr.close();
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;FIleWriter&lt;/h3&gt;
&lt;p&gt;继承自Weiter&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;public FIleWriter(File file)&lt;/code&gt;： 创建字符输出流，关联本地文件&lt;/li&gt;
&lt;li&gt;&lt;code&gt;public FIleWriter(String path)&lt;/code&gt;： 创建字符输出流，关联本地文件&lt;/li&gt;
&lt;li&gt;&lt;code&gt;public FIleWriter(File file, boolean append)&lt;/code&gt;： 创建字符输出流，关联本地文件，续写&lt;/li&gt;
&lt;li&gt;&lt;code&gt;public FIleWriter(String path, boolean append)&lt;/code&gt;： 创建字符输出流，关联本地文件，续写&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;写入数据&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;public int write()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;``public int write(String str)`&lt;/li&gt;
&lt;li&gt;&lt;code&gt;public int write(String str, int off, int len)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;public int write(char[] buff)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;public int write(char[] buff, int off, int len)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;按字节读取，遇到中文一次读取多个字节，读取后解码，返回一个整数，会转成十进制数据。如果需要字符数据，则需要使用&lt;code&gt;(char)&lt;/code&gt;强制转换。&lt;/p&gt;
&lt;p&gt;释放资源&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;public int close()&lt;/code&gt;：释放资源&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;异常处理&lt;/h2&gt;
&lt;h3&gt;传统写法&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;public class HandleException1 {
    public static void main(String[] args) {
        FileWriter fw = null;
        try {
            fw = new FileWriter(&quot;fw.txt&quot;);
            fw.write(&quot;aaa&quot;); 
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (fw != null) {
                    fw.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;JDK7&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;public class HandleException2 {
    public static void main(String[] args) {
        try ( FileWriter fw = new FileWriter(&quot;fw.txt&quot;); ) {
            // 需要实现AutoCloseable之后才行
            fw.write(&quot;aaa&quot;);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;JDK9&lt;/h3&gt;
&lt;p&gt;JDK9中&lt;code&gt;try-with-resource&lt;/code&gt; 的改进，对于&lt;strong&gt;引入对象&lt;/strong&gt;的方式，支持的更加简洁。被引入的对象，同样可以自动关闭，无需手动close。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class TryDemo {
    public static void main(String[] args) throws IOException {
        final  FileReader fr  = new FileReader(&quot;in.txt&quot;);
        FileWriter fw = new FileWriter(&quot;out.txt&quot;);
        try (fr; fw) {
            int b;
          	while ((b = fr.read())!=-1) {
            	fw.write(b);
          	}
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;文件复制&lt;/h2&gt;
&lt;p&gt;每次读取一个字节，写入新文件。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;FileInputStream fis = new FileInputStream(&quot;read.txt&quot;);
FileOutputStream fos = new FileOutputStream(&quot;read.txt&quot;);
int b;
while((b = fis.read()) != -1){
    fos.write(b);
}
fis.close();
fos.close();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用字节数组来拷贝：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class Copy {
    public static void main(String[] args) throws IOException {
        FileInputStream fis = new FileInputStream(&quot;test.jpg&quot;);
        FileOutputStream fos = new FileOutputStream(&quot;test_copy.jpg&quot;);
        byte[] b = new byte[1024*1024*4];
        int len;
        while ((len = fis.read(b))!=-1) {
            fos.write(b, 0 , len);
        }
        fos.close();
        fis.close();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;随机读写&lt;/h2&gt;
&lt;p&gt;在Java中，要在文件的指定位置写入数据，可以使用&lt;code&gt;RandomAccessFile&lt;/code&gt;类，该类提供了对文件随机访问的功能，即可以读取或写入文件中的任意位置。下面是一个简单的示例，展示如何使用&lt;code&gt;RandomAccessFile&lt;/code&gt;在文件的指定位置写入数据：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
public class FileWriteAtPosition {
    public static void main(String[] args) {
        String filePath = &quot;example.txt&quot;; // 文件路径
        long position = 10; // 指定位置
        String dataToWrite = &quot;Hello, World!&quot;; // 要写入的数据
        try {
            // 创建File对象
            File file = new File(filePath);
            // 创建RandomAccessFile对象，模式为&quot;rw&quot;，即读写模式
            RandomAccessFile raf = new RandomAccessFile(file, &quot;rw&quot;);
            // 移动文件指针到指定位置
            raf.seek(position);
            // 写入数据
            raf.write(dataToWrite.getBytes());
            // 关闭RandomAccessFile
            raf.close();
            System.out.println(&quot;Data written at position &quot; + position);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这个示例中，我们首先创建了一个&lt;code&gt;File&lt;/code&gt;对象来表示要操作的文件。然后，我们创建了一个&lt;code&gt;RandomAccessFile&lt;/code&gt;对象，并指定了模式为&quot;rw&quot;，表示我们可以读写文件。
使用&lt;code&gt;seek(long pos)&lt;/code&gt;方法将文件指针移动到指定的位置。然后，使用&lt;code&gt;write(byte[] b)&lt;/code&gt;方法将数据写入文件。最后，不要忘记关闭&lt;code&gt;RandomAccessFile&lt;/code&gt;以释放资源。
请注意，如果指定的位置超出了文件当前的大小，那么在写入数据时，文件将会被扩展，并且在新写入数据与原有数据之间可能会出现未定义的数据。
还要注意，直接在文件的中间位置写入数据可能会覆盖原有数据，所以使用这种方法时要小心，确保不会意外地覆盖重要信息。如果只是想插入数据而不覆盖原有数据，那么需要先读取插入点后的数据，插入新数据后，再写回原有数据。&lt;/p&gt;
&lt;h2&gt;缓冲流&lt;/h2&gt;
&lt;p&gt;缓冲流,也叫高效流，缓冲流是对基本流进行的封装，对4个基本的&lt;code&gt;FileXxx&lt;/code&gt; 流的增强，所以也是4个流，按照数据类型分类：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;字节缓冲流&lt;/strong&gt;：&lt;code&gt;BufferedInputStream&lt;/code&gt;，&lt;code&gt;BufferedOutputStream&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;字符缓冲流&lt;/strong&gt;：&lt;code&gt;BufferedReader&lt;/code&gt;，&lt;code&gt;BufferedWriter&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;缓冲流的基本原理，是在创建流对象时，会创建一个内置的默认大小的缓冲区数组，通过缓冲区读写，减少系统IO次数，从而提高读写的效率。&lt;/p&gt;
&lt;h3&gt;字节缓冲流&lt;/h3&gt;
&lt;h4&gt;构造方法&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;public BufferedInputStream(InputStream in)&lt;/code&gt; ：创建一个 新的缓冲输入流。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;public BufferedOutputStream(OutputStream out)&lt;/code&gt;： 创建一个新的缓冲输出流。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;构造举例，代码如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 创建字节缓冲输入流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(&quot;bis.txt&quot;));
// 创建字节缓冲输出流
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(&quot;bos.txt&quot;));
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;效率测试&lt;/h4&gt;
&lt;p&gt;查询API，缓冲流读写方法与基本的流是一致的，我们通过复制大文件（375MB），测试它的效率。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;基本流，代码如下：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;public class BufferedDemo {
    public static void main(String[] args) throws FileNotFoundException {
        // 记录开始时间
      	long start = System.currentTimeMillis();
		// 创建流对象
        try (
        	FileInputStream fis = new FileInputStream(&quot;jdk9.exe&quot;);
        	FileOutputStream fos = new FileOutputStream(&quot;copy.exe&quot;)
        ){
        	// 读写数据
            int b;
            while ((b = fis.read()) != -1) {
                fos.write(b);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
		// 记录结束时间
        long end = System.currentTimeMillis();
        System.out.println(&quot;普通流复制时间:&quot;+(end - start)+&quot; 毫秒&quot;);
    }
}

十几分钟过去了...
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;缓冲流，代码如下：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;public class BufferedDemo {
    public static void main(String[] args) throws FileNotFoundException {
        // 记录开始时间
      	long start = System.currentTimeMillis();
		// 创建流对象
        try (
        	BufferedInputStream bis = new BufferedInputStream(new FileInputStream(&quot;jdk9.exe&quot;));
	     BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(&quot;copy.exe&quot;));
        ){
        // 读写数据
            int b;
            while ((b = bis.read()) != -1) {
                bos.write(b);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
		// 记录结束时间
        long end = System.currentTimeMillis();
        System.out.println(&quot;缓冲流复制时间:&quot;+(end - start)+&quot; 毫秒&quot;);
    }
}

缓冲流复制时间:8016 毫秒
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如何更快呢？&lt;/p&gt;
&lt;p&gt;使用数组的方式，代码如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class BufferedDemo {
    public static void main(String[] args) throws FileNotFoundException {
      	// 记录开始时间
        long start = System.currentTimeMillis();
		// 创建流对象
        try (
			BufferedInputStream bis = new BufferedInputStream(new FileInputStream(&quot;jdk9.exe&quot;));
		 BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(&quot;copy.exe&quot;));
        ){
          	// 读写数据
            int len;
            byte[] bytes = new byte[8*1024];
            while ((len = bis.read(bytes)) != -1) {
                bos.write(bytes, 0 , len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
		// 记录结束时间
        long end = System.currentTimeMillis();
        System.out.println(&quot;缓冲流使用数组复制时间:&quot;+(end - start)+&quot; 毫秒&quot;);
    }
}
缓冲流使用数组复制时间:666 毫秒
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;字符缓冲流&lt;/h3&gt;
&lt;h4&gt;构造方法&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;public BufferedReader(Reader in)&lt;/code&gt; ：创建一个 新的缓冲输入流。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;public BufferedWriter(Writer out)&lt;/code&gt;： 创建一个新的缓冲输出流。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;构造举例，代码如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 创建字符缓冲输入流
BufferedReader br = new BufferedReader(new FileReader(&quot;br.txt&quot;));
// 创建字符缓冲输出流
BufferedWriter bw = new BufferedWriter(new FileWriter(&quot;bw.txt&quot;));
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;特有方法&lt;/h4&gt;
&lt;p&gt;字符缓冲流的基本方法与普通字符流调用方式一致，不再阐述，我们来看它们具备的特有方法。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;BufferedReader：&lt;code&gt;public String readLine()&lt;/code&gt;: 读一行文字。&lt;/li&gt;
&lt;li&gt;BufferedWriter：&lt;code&gt;public void newLine()&lt;/code&gt;: 写一行行分隔符,由系统属性定义符号。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;readLine&lt;/code&gt;方法演示，代码如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class BufferedReaderDemo {
    public static void main(String[] args) throws IOException {
      	 // 创建流对象
        BufferedReader br = new BufferedReader(new FileReader(&quot;in.txt&quot;));
		// 定义字符串,保存读取的一行文字
        String line  = null;
      	// 循环读取,读取到最后返回null
        while ((line = br.readLine())!=null) {
            System.out.print(line);
            System.out.println(&quot;------&quot;);
        }
		// 释放资源
        br.close();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;newLine&lt;/code&gt;方法演示，代码如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class BufferedWriterDemo throws IOException {
  public static void main(String[] args) throws IOException  {
    	// 创建流对象
  	BufferedWriter bw = new BufferedWriter(new FileWriter(&quot;out.txt&quot;));
    	// 写出数据
      bw.write(&quot;黑马&quot;);
    	// 写出换行
      bw.newLine();
      bw.write(&quot;程序&quot;);
      bw.newLine();
      bw.write(&quot;员&quot;);
      bw.newLine();
  	// 释放资源
      bw.close();
  }
}
输出效果:
黑马
程序
员
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;打印流&lt;/h2&gt;
&lt;h3&gt;概述&lt;/h3&gt;
&lt;p&gt;平时我们在控制台打印输出，是调用&lt;code&gt;print&lt;/code&gt;方法和&lt;code&gt;println&lt;/code&gt;方法完成的，这两个方法都来自于&lt;code&gt;java.io.PrintStream&lt;/code&gt;类，该类能够方便地打印各种数据类型的值，是一种便捷的输出方式。&lt;/p&gt;
&lt;h3&gt;PrintStream类&lt;/h3&gt;
&lt;h4&gt;构造方法&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;public PrintStream(String fileName)  &lt;/code&gt;： 使用指定的文件名创建一个新的打印流。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;构造举例，代码如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PrintStream ps = new PrintStream(&quot;ps.txt&quot;)；
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;改变打印流向&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;System.out&lt;/code&gt;就是&lt;code&gt;PrintStream&lt;/code&gt;类型的，只不过它的流向是系统规定的，打印在控制台上。不过，既然是流对象，我们就可以玩一个&quot;小把戏&quot;，改变它的流向。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class PrintDemo {
    public static void main(String[] args) throws IOException {
		// 调用系统的打印流,控制台直接输出97
        System.out.println(97);
      
		// 创建打印流,指定文件的名称
        PrintStream ps = new PrintStream(&quot;ps.txt&quot;);
      	
      	// 设置系统的打印流流向,输出到ps.txt
        System.setOut(ps);
      	// 调用系统的打印流,ps.txt中输出97
        System.out.println(97);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;压缩流和解压缩流&lt;/h2&gt;
&lt;p&gt;压缩流：&lt;/p&gt;
&lt;p&gt;​	负责压缩文件或者文件夹&lt;/p&gt;
&lt;p&gt;解压缩流：&lt;/p&gt;
&lt;p&gt;​	负责把压缩包中的文件和文件夹解压出来&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class ZipStreamDemo1 {
    public static void main(String[] args) throws IOException {
        //1.创建一个File表示要解压的压缩包
        File src = new File(&quot;D:\\aaa.zip&quot;);
        //2.创建一个File表示解压的目的地
        File dest = new File(&quot;D:\\&quot;);
        //调用方法
        unzip(src,dest);
    }

    //定义一个方法用来解压
    public static void unzip(File src,File dest) throws IOException {
        //解压的本质：把压缩包里面的每一个文件或者文件夹读取出来，按照层级拷贝到目的地当中
        //创建一个解压缩流用来读取压缩包中的数据
        ZipInputStream zip = new ZipInputStream(new FileInputStream(src));
        //要先获取到压缩包里面的每一个zipentry对象
        //表示当前在压缩包中获取到的文件或者文件夹
        ZipEntry entry;
        while((entry = zip.getNextEntry()) != null){
            System.out.println(entry);
            if(entry.isDirectory()){
                //文件夹：需要在目的地dest处创建一个同样的文件夹
                File file = new File(dest,entry.toString());
                file.mkdirs();
            }else{
                //文件：需要读取到压缩包中的文件，并把他存放到目的地dest文件夹中（按照层级目录进行存放）
                FileOutputStream fos = new FileOutputStream(new File(dest,entry.toString()));
                int b;
                while((b = zip.read()) != -1){
                    //写到目的地
                    fos.write(b);
                }
                fos.close();
                //表示在压缩包中的一个文件处理完毕了。
                zip.closeEntry();
            }
        }
        zip.close();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;public class ZipStreamDemo2 {
    public static void main(String[] args) throws IOException {
        /*
         *   压缩流
         *      需求：
         *          把D:\\a.txt打包成一个压缩包
         * */
        //1.创建File对象表示要压缩的文件
        File src = new File(&quot;D:\\a.txt&quot;);
        //2.创建File对象表示压缩包的位置
        File dest = new File(&quot;D:\\&quot;);
        //3.调用方法用来压缩
        toZip(src,dest);
    }

    /*
    *   作用：压缩
    *   参数一：表示要压缩的文件
    *   参数二：表示压缩包的位置
    * */
    public static void toZip(File src,File dest) throws IOException {
        //1.创建压缩流关联压缩包
        ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(new File(dest,&quot;a.zip&quot;)));
        //2.创建ZipEntry对象，表示压缩包里面的每一个文件和文件夹
        //参数：压缩包里面的路径
        ZipEntry entry = new ZipEntry(&quot;aaa\\bbb\\a.txt&quot;);
        //3.把ZipEntry对象放到压缩包当中
        zos.putNextEntry(entry);
        //4.把src文件中的数据写到压缩包当中
        FileInputStream fis = new FileInputStream(src);
        int b;
        while((b = fis.read()) != -1){
            zos.write(b);
        }
        zos.closeEntry();
        zos.close();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;public class ZipStreamDemo3 {
    public static void main(String[] args) throws IOException {
        /*
         *   压缩流
         *      需求：
         *          把D:\\aaa文件夹压缩成一个压缩包
         * */
        //1.创建File对象表示要压缩的文件夹
        File src = new File(&quot;D:\\aaa&quot;);
        //2.创建File对象表示压缩包放在哪里（压缩包的父级路径）
        File destParent = src.getParentFile();//D:\\
        //3.创建File对象表示压缩包的路径
        File dest = new File(destParent,src.getName() + &quot;.zip&quot;);
        //4.创建压缩流关联压缩包
        ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(dest));
        //5.获取src里面的每一个文件，变成ZipEntry对象，放入到压缩包当中
        toZip(src,zos,src.getName());//aaa
        //6.释放资源
        zos.close();
    }

    /*
    *   作用：获取src里面的每一个文件，变成ZipEntry对象，放入到压缩包当中
    *   参数一：数据源
    *   参数二：压缩流
    *   参数三：压缩包内部的路径
    * */
    public static void toZip(File src,ZipOutputStream zos,String name) throws IOException {
        //1.进入src文件夹
        File[] files = src.listFiles();
        //2.遍历数组
        for (File file : files) {
            if(file.isFile()){
                //3.判断-文件，变成ZipEntry对象，放入到压缩包当中
                ZipEntry entry = new ZipEntry(name + &quot;\\&quot; + file.getName());//aaa\\no1\\a.txt
                zos.putNextEntry(entry);
                //读取文件中的数据，写到压缩包
                FileInputStream fis = new FileInputStream(file);
                int b;
                while((b = fis.read()) != -1){
                    zos.write(b);
                }
                fis.close();
                zos.closeEntry();
            }else{
                //4.判断-文件夹，递归
                toZip(file,zos,name + &quot;\\&quot; + file.getName());
                //     no1            aaa   \\   no1
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>java学习笔记（7）</title><link>http://zaoweiceng.github.io/posts/java/java%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0_7/</link><guid isPermaLink="true">http://zaoweiceng.github.io/posts/java/java%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0_7/</guid><description>Java学习之路</description><pubDate>Thu, 13 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;方法引用&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;引用处需要是&lt;strong&gt;函数式接口&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;被引用的方法已经存在（可以由java提供，也可以是第三方）&lt;/li&gt;
&lt;li&gt;被引用的方法的参数和返回值要和抽象方法的参数返回值一致&lt;/li&gt;
&lt;li&gt;核心：lambda表达式改写&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;例子：排序&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class sort {
    public static void main(String[] args) {
        List&amp;lt;Integer&amp;gt; list = new ArrayList&amp;lt;Integer&amp;gt;();
        Collections.addAll(list, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        Collections.sort(list, new Comparator&amp;lt;Integer&amp;gt;() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2 - o1;
            }
        });
        System.out.println(list);
        Collections.shuffle(list);
        Collections.sort(list, (o1, o2) -&amp;gt; o2 - o1);
        System.out.println(list);
        Collections.shuffle(list);
        Collections.sort(list, sort::sub);
        System.out.println(list);
    }
    public static int sub(int a, int b) {
        return b-a;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;引用静态方法&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;格式：ClassName::StaticMethodName&lt;/li&gt;
&lt;li&gt;例子：Integer::parseInt&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;// list:{&quot;1&quot;, &quot;2&quot;, &quot;3&quot;, &quot;4&quot;}
list.stream().map(Integer::parseInt).collect(Collectors.toList());
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;引用成员方法&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;格式：Object::MethodName&lt;/li&gt;
&lt;li&gt;this::name&lt;/li&gt;
&lt;li&gt;super::name&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;list.stream().filter(new Judge()::judge).collect(Collectors.toList());
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;引用构造方法&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;格式：ClassName::new&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;list.stream().map(Student::new).collect(Collectors.toList());
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;异常&lt;/h2&gt;
&lt;p&gt;异常继承自&lt;code&gt;Throwable&lt;/code&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;idea快捷键：Ctrl+Alt+T&lt;/p&gt;
&lt;/blockquote&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;public String getMessage()&lt;/td&gt;
&lt;td&gt;返回此throwable的详细消息的字符串&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;public String toString()&lt;/td&gt;
&lt;td&gt;返回此throwable的简短描述&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;public void printStackTrace()&lt;/td&gt;
&lt;td&gt;打印异常的错误信息&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;printStackTrace()在系统底层使用System.err.println(&quot;&quot;)输出&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;捕获异常：&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;try{
    ...
}catch{
	...    
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;抛出异常：&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;public void function() throws Exception{
    throw new Exception();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;throws: 告诉使用者，可能抛出哪些异常&lt;/li&gt;
&lt;li&gt;throw：抛出异常&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;自定义异常&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;public class MyException extends RuntimeException{
    public MyException(){};
    public MyException(String message){
        super(message);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;运行时异常：RuntimeException&lt;/li&gt;
&lt;li&gt;编译时异常：Exception&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;字符编码&lt;/h2&gt;
&lt;p&gt;在Java中，String的编解码主要涉及到字符集（Charset）的概念，字符集决定了如何将字符（例如字母、数字、符号等）映射为字节序列，以及如何将字节序列映射回字符。Java中的String是以UTF-16编码存储的，但是我们可以使用不同的字符集对其进行编码和解码。&lt;/p&gt;
&lt;h3&gt;编码（Encoding）&lt;/h3&gt;
&lt;p&gt;编码是将String对象转换为字节序列的过程。在Java中，可以使用&lt;code&gt;String&lt;/code&gt;类的&lt;code&gt;getBytes()&lt;/code&gt;方法来进行编码，这个方法可以指定使用的字符集。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;String str = &quot;你好，世界！&quot;;
byte[] bytes = str.getBytes(&quot;UTF-8&quot;); // 使用UTF-8编码
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在上面的例子中，&lt;code&gt;str&lt;/code&gt;字符串被转换为字节序列&lt;code&gt;bytes&lt;/code&gt;，使用的是UTF-8编码。如果不指定字符集，&lt;code&gt;getBytes()&lt;/code&gt;方法会使用平台的默认字符集。&lt;/p&gt;
&lt;h3&gt;解码（Decoding）&lt;/h3&gt;
&lt;p&gt;解码是将字节序列转换回String对象的过程。在Java中，可以使用&lt;code&gt;String&lt;/code&gt;类的构造方法来进行解码。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;byte[] bytes = ...; // 假设这是某种编码的字节序列
String str = new String(bytes, &quot;UTF-8&quot;); // 使用UTF-8解码
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在上面的例子中，&lt;code&gt;bytes&lt;/code&gt;字节序列被转换回字符串&lt;code&gt;str&lt;/code&gt;，使用的是UTF-8编码。同样，如果不指定字符集，会使用平台的默认字符集。&lt;/p&gt;
&lt;h3&gt;常见字符集&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;UTF-8&lt;/strong&gt;：一种变长的编码方式，可以用1到4个字节表示一个字符，兼容ASCII码。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;UTF-16&lt;/strong&gt;：Java内部使用的是UTF-16编码，用2或4个字节表示一个字符。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GBK&lt;/strong&gt;：中国的国家标准，兼容GB2312，用于中文编码。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ISO-8859-1&lt;/strong&gt;：西欧语言的编码字符集。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;编解码中的常见问题&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;乱码&lt;/strong&gt;：如果编码和解码时使用的&lt;strong&gt;字符集不一致&lt;/strong&gt;或者&lt;strong&gt;没有读完整个汉字&lt;/strong&gt;，就会出现乱码。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;字符集不支持&lt;/strong&gt;：尝试使用不支持的字符集进行编解码会抛出&lt;code&gt;UnsupportedEncodingException&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;示例&lt;/h3&gt;
&lt;p&gt;下面是一个完整的示例，展示了如何进行编码和解码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import java.io.UnsupportedEncodingException;
public class StringEncodingExample {
    public static void main(String[] args) {
        String originalString = &quot;你好，世界！&quot;;
        try {
            // 编码
            byte[] utf8Bytes = originalString.getBytes(&quot;UTF-8&quot;);
            byte[] gbkBytes = originalString.getBytes(&quot;GBK&quot;);
            // 解码
            String utf8String = new String(utf8Bytes, &quot;UTF-8&quot;);
            String gbkString = new String(gbkBytes, &quot;GBK&quot;);
            // 输出结果
            System.out.println(&quot;原始字符串: &quot; + originalString);
            System.out.println(&quot;UTF-8编码后解码: &quot; + utf8String);
            System.out.println(&quot;GBK编码后解码: &quot; + gbkString);
            // 错误的解码示例（会导致乱码）
            String wrongString = new String(utf8Bytes, &quot;GBK&quot;);
            System.out.println(&quot;错误的解码: &quot; + wrongString);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这个示例中，我们首先将原始字符串&lt;code&gt;originalString&lt;/code&gt;使用UTF-8和GBK编码，然后分别使用相同的字符集进行解码，最后输出结果。我们还展示了一个错误的解码示例，即使用错误的字符集解码，这会导致乱码。&lt;/p&gt;
</content:encoded></item><item><title>C++中的友元（Friend）概念、用法及使用场景</title><link>http://zaoweiceng.github.io/posts/cpp/%E5%8F%8B%E5%85%83/</link><guid isPermaLink="true">http://zaoweiceng.github.io/posts/cpp/%E5%8F%8B%E5%85%83/</guid><description>在C++中，友元是一种允许非成员函数或另一个类访问类的私有（private）和受保护（protected）成员的机制。友元可以打破类的封装性，使得特定的函数或类能够访问到类的内部实现细节。</description><pubDate>Wed, 12 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;概念&lt;/h2&gt;
&lt;p&gt;在C++中，友元是一种允许非成员函数或另一个类访问类的私有（private）和受保护（protected）成员的机制。友元可以打破类的封装性，使得特定的函数或类能够访问到类的内部实现细节。&lt;/p&gt;
&lt;h2&gt;用法&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;友元函数&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;定义：友元函数可以是普通的非成员函数，也可以是其他类的成员函数。&lt;/li&gt;
&lt;li&gt;声明：在类的内部使用&lt;code&gt;friend&lt;/code&gt;关键字来声明友元函数。&lt;/li&gt;
&lt;li&gt;访问：友元函数可以访问类的所有成员，包括私有和受保护成员。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;友元类&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;定义：一个类可以声明另一个类为它的友元，这样友元类的所有成员函数都可以访问第一个类的私有和受保护成员。&lt;/li&gt;
&lt;li&gt;声明：在类的内部使用&lt;code&gt;friend&lt;/code&gt;关键字来声明友元类。
&lt;strong&gt;示例代码&lt;/strong&gt;：&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
class Box {
private:
    double width;
public:
    Box() : width(0) {}
    // 声明友元函数
    friend void setWidth(Box&amp;amp; b, double w);
    // 声明友元类
    friend class BoxFriend;
};
// 定义友元函数
void setWidth(Box&amp;amp; b, double w) {
    b.width = w;
}
class BoxFriend {
public:
    void displayWidth(const Box&amp;amp; b) {
        std::cout &amp;lt;&amp;lt; &quot;Width of box: &quot; &amp;lt;&amp;lt; b.width &amp;lt;&amp;lt; std::endl;
    }
};
int main() {
    Box box;
    setWidth(box, 10.0); // 使用友元函数设置宽度
    BoxFriend bf;
    bf.displayWidth(box); // 使用友元类显示宽度
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;使用场景&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;操作符重载&lt;/strong&gt;：在实现操作符重载时，常常需要访问类的私有成员，此时可以将重载函数声明为友元函数。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;辅助函数&lt;/strong&gt;：某些辅助函数需要直接访问类的内部状态，而不需要成为类的成员，此时可以使用友元函数。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;类间协作&lt;/strong&gt;：当两个类需要紧密协作，且其中一个类需要频繁访问另一个类的内部成员时，可以将其中一个类声明为另一个类的友元。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;测试&lt;/strong&gt;：在单元测试中，有时需要访问类的私有成员以进行更彻底的测试，此时可以使用友元。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;注意事项&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;友元关系不是对称的，如果类A是类B的友元，并不意味着类B是类A的友元。&lt;/li&gt;
&lt;li&gt;友元关系不能传递，如果类A是类B的友元，类B是类C的友元，并不意味着类A是类C的友元。&lt;/li&gt;
&lt;li&gt;使用友元会破坏类的封装性，因此应谨慎使用，只有在确实需要时才使用。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;总结：&lt;/h2&gt;
&lt;p&gt;友元是C++中一种强大的机制，允许特定的函数或类访问类的私有和受保护成员。尽管它提供了灵活性，但也会破坏封装性，因此应谨慎使用。在操作符重载、辅助函数、类间协作和测试等场景中，友元可以发挥重要作用。&lt;/p&gt;
</content:encoded></item><item><title>const的用法</title><link>http://zaoweiceng.github.io/posts/cpp/const%E7%9A%84%E7%94%A8%E6%B3%95/</link><guid isPermaLink="true">http://zaoweiceng.github.io/posts/cpp/const%E7%9A%84%E7%94%A8%E6%B3%95/</guid><description>const是C++中的一个关键字，用于声明变量、函数参数、返回值或成员函数为常量，即它们的值在初始化后不能被修改。</description><pubDate>Wed, 12 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;code&gt;const&lt;/code&gt;是C++中的一个关键字，用于声明变量、函数参数、返回值或成员函数为常量，即它们的值在初始化后不能被修改。&lt;code&gt;const&lt;/code&gt;的用法非常灵活，可以用于多种场景，以下是一些主要的用法：&lt;/p&gt;
&lt;h3&gt;常量变量&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;const int MAX_SIZE = 100;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里，&lt;code&gt;MAX_SIZE&lt;/code&gt;被声明为一个常量，其值在初始化后不能改变。&lt;/p&gt;
&lt;h3&gt;指向常量的指针&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;const int *ptr = &amp;amp;MAX_SIZE;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;ptr&lt;/code&gt;是一个指向常量的指针，不能通过&lt;code&gt;ptr&lt;/code&gt;来修改&lt;code&gt;MAX_SIZE&lt;/code&gt;的值。&lt;/p&gt;
&lt;h3&gt;常量指针&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;int value = 10;
int *const ptr = &amp;amp;value;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;ptr&lt;/code&gt;是一个常量指针，它始终指向&lt;code&gt;value&lt;/code&gt;，但不能指向其他变量。&lt;/p&gt;
&lt;h3&gt;指向常量的常量指针&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;const int value = 10;
const int *const ptr = &amp;amp;value;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;ptr&lt;/code&gt;既不能改变指向的值，也不能改变指向的地址。&lt;/p&gt;
&lt;h3&gt;常量引用&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;const int &amp;amp;ref = value;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;ref&lt;/code&gt;是&lt;code&gt;value&lt;/code&gt;的常量引用，不能通过&lt;code&gt;ref&lt;/code&gt;来修改&lt;code&gt;value&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;常量成员函数&lt;/h3&gt;
&lt;p&gt;在类中，可以声明成员函数为&lt;code&gt;const&lt;/code&gt;，表示这个函数不会修改类的成员变量。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class MyClass {
public:
    int getValue() const {
        return value;
    }
private:
    int value;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;常量表达式&lt;/h3&gt;
&lt;p&gt;C++11引入了&lt;code&gt;constexpr&lt;/code&gt;，用于声明常量表达式，这些表达式在编译时就能计算出来。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;constexpr int factorial(int n) {
    return (n &amp;lt;= 1) ? 1 : n * factorial(n - 1);
}
constexpr int result = factorial(5); // 在编译时计算
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;对象常量&lt;/h3&gt;
&lt;p&gt;可以声明对象为常量，这样对象的成员就不能被修改。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const MyClass obj;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;常量与多态&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;const&lt;/code&gt;成员函数可以用于多态，因为它们可以被子类覆盖，并且保持&lt;code&gt;const&lt;/code&gt;属性。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Base {
public:
    virtual void display() const {
        // ...
    }
};
class Derived : public Base {
public:
    void display() const override {
        // ...
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;常量与模板&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;const&lt;/code&gt;可以与模板一起使用，以创建通用的常量类型。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;template&amp;lt;typename T&amp;gt;
void print(const T&amp;amp; value) {
    // ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;保护参数&lt;/h3&gt;
&lt;p&gt;函数的参数可以加上const修饰，以避免无意识的修改const变量。&lt;/p&gt;
&lt;p&gt;通过指针可以绕过&lt;code&gt;const&lt;/code&gt;修饰，但这种方法不推荐，因为它破坏了&lt;code&gt;const&lt;/code&gt;的语义，可能导致未定义行为。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;示例：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;void modifyValue(const int* value) {
    int* nonConstPtr = const_cast&amp;lt;int*&amp;gt;(value); // 强制转换去除const
    *nonConstPtr = 10; // 修改值
}

int main() {
    int a = 5;
    modifyValue(&amp;amp;a);
    std::cout &amp;lt;&amp;lt; a &amp;lt;&amp;lt; std::endl; // 输出10
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;注意：&lt;/strong&gt; 使用&lt;code&gt;const_cast&lt;/code&gt;时要非常小心，因为它破坏了常量性，可能导致未定义行为，特别是如果原始数据确实不应该被修改时。&lt;/p&gt;
&lt;h3&gt;注意事项：&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;const&lt;/code&gt;变量必须在声明时初始化，除非它是静态存储期变量。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;const&lt;/code&gt;对象只能调用&lt;code&gt;const&lt;/code&gt;成员函数。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;const&lt;/code&gt;指针和&lt;code&gt;const&lt;/code&gt;引用可以指向非&lt;code&gt;const&lt;/code&gt;对象，但通过它们不能修改对象的值。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;const&lt;/code&gt;在函数参数中用于表示参数不会被修改，这有助于提高代码的可读性和安全性。
&lt;code&gt;const&lt;/code&gt;的这些用法有助于编写更安全、更可维护的代码，通过限制变量的可变性，可以避免一些常见的编程错误。&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>智能指针</title><link>http://zaoweiceng.github.io/posts/cpp/%E6%99%BA%E8%83%BD%E6%8C%87%E9%92%88/</link><guid isPermaLink="true">http://zaoweiceng.github.io/posts/cpp/%E6%99%BA%E8%83%BD%E6%8C%87%E9%92%88/</guid><description>智能指针是C++中用于管理动态内存的一种机制，它封装了原始指针，提供了自动的内存管理功能，从而帮助程序员避免内存泄漏、指针越界等常见问题。</description><pubDate>Wed, 12 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;C++中的智能指针&lt;/h2&gt;
&lt;p&gt;智能指针是C++中用于管理动态内存的一种机制，它封装了原始指针，提供了自动的内存管理功能，从而帮助程序员避免内存泄漏、指针越界等常见问题。C++11引入了三种主要的智能指针类型：&lt;code&gt;unique_ptr&lt;/code&gt;、&lt;code&gt;shared_ptr&lt;/code&gt;和&lt;code&gt;weak_ptr&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;1. &lt;code&gt;unique_ptr&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;unique_ptr&lt;/code&gt;是独占式的智能指针，它确保同一时间只有一个智能指针可以指向某个动态分配的对象。当&lt;code&gt;unique_ptr&lt;/code&gt;被销毁时，它所管理的对象也会被自动销毁。
&lt;strong&gt;基本用法：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;memory&amp;gt;
int main() {
    std::unique_ptr&amp;lt;int&amp;gt; ptr1(new int(10));  // 创建一个unique_ptr
    // std::unique_ptr&amp;lt;int&amp;gt; ptr2 = ptr1;  // 错误，不能复制unique_ptr
    std::unique_ptr&amp;lt;int&amp;gt; ptr3 = std::move(ptr1);  // 转移所有权
    if (ptr1 == nullptr) {
        // 此时ptr1不再拥有对象
    }
    // 使用ptr3
    *ptr3 = 20;
    return 0;  // ptr3自动释放内存
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;特点：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;独占性&lt;/strong&gt;：不能共享所管理的对象，不能进行复制操作，但可以转移所有权。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;自动释放&lt;/strong&gt;：当&lt;code&gt;unique_ptr&lt;/code&gt;离开作用域时，自动释放所管理的对象。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;高效&lt;/strong&gt;：由于独占性，不需要引用计数，效率较高。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. &lt;code&gt;shared_ptr&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;shared_ptr&lt;/code&gt;是共享式的智能指针，允许多个智能指针共享同一个动态分配的对象。它通过引用计数来管理对象的生命周期，当引用计数为0时，对象被自动销毁。
&lt;strong&gt;基本用法：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;memory&amp;gt;
int main() {
    std::shared_ptr&amp;lt;int&amp;gt; ptr1(new int(10));  // 创建一个shared_ptr
    std::shared_ptr&amp;lt;int&amp;gt; ptr2 = ptr1;  // 复制shared_ptr，引用计数+1
    {
        std::shared_ptr&amp;lt;int&amp;gt; ptr3 = ptr1;  // 引用计数+1
        // 使用ptr3
    }  // ptr3离开作用域，引用计数-1
    // 使用ptr1和ptr2
    *ptr1 = 20;
    return 0;  // ptr1和ptr2离开作用域，引用计数为0，对象被销毁
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;特点：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;共享性&lt;/strong&gt;：多个&lt;code&gt;shared_ptr&lt;/code&gt;可以共享同一个对象。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;引用计数&lt;/strong&gt;：内部维护一个引用计数，用于管理对象的生命周期。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;自动释放&lt;/strong&gt;：当引用计数为0时，自动释放对象。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3. &lt;code&gt;weak_ptr&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;weak_ptr&lt;/code&gt;是一种弱引用智能指针，它不增加引用计数，主要用于解决&lt;code&gt;shared_ptr&lt;/code&gt;可能引起的循环引用问题。
&lt;strong&gt;基本用法：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;memory&amp;gt;
int main() {
    std::shared_ptr&amp;lt;int&amp;gt; ptr1(new int(10));
    std::weak_ptr&amp;lt;int&amp;gt; weakPtr = ptr1;  // 创建一个weak_ptr
    if (std::shared_ptr&amp;lt;int&amp;gt; sharedPtr = weakPtr.lock()) {  // 尝试提升为shared_ptr
        // 对象存在，可以使用sharedPtr
    } else {
        // 对象已被销毁
    }
    return 0;  // ptr1离开作用域，对象被销毁，weakPtr变为空
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;特点：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;弱引用&lt;/strong&gt;：不增加引用计数，不会影响对象的生命周期。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;提升&lt;/strong&gt;：可以通过&lt;code&gt;lock()&lt;/code&gt;方法尝试提升为&lt;code&gt;shared_ptr&lt;/code&gt;，如果对象存在，则提升成功，否则返回空&lt;code&gt;shared_ptr&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;解决循环引用&lt;/strong&gt;：常用于解决&lt;code&gt;shared_ptr&lt;/code&gt;之间的循环引用问题。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;智能指针的优势&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;自动管理内存&lt;/strong&gt;：减少手动管理内存的需求，降低内存泄漏和指针错误的风险。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;提高代码安全性&lt;/strong&gt;：通过封装原始指针，提供更安全的操作方式。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;简化代码&lt;/strong&gt;：减少内存管理相关的代码，使代码更简洁易读。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;注意事项&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;避免与原始指针混用&lt;/strong&gt;：智能指针和原始指针混用可能导致未定义行为。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;正确使用&lt;code&gt;new&lt;/code&gt;和&lt;code&gt;delete&lt;/code&gt;&lt;/strong&gt;：使用智能指针后，应避免手动调用&lt;code&gt;new&lt;/code&gt;和&lt;code&gt;delete&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;理解循环引用&lt;/strong&gt;：在使用&lt;code&gt;shared_ptr&lt;/code&gt;时，注意循环引用问题，必要时使用&lt;code&gt;weak_ptr&lt;/code&gt;。
智能指针是C++现代编程中非常重要的特性，合理使用智能指针可以大大提高代码的健壮性和可维护性。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;使用&lt;code&gt;std::unique_ptr&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;场景&lt;/strong&gt;：当你想要确保只有一个指针拥有对动态分配对象的访问权时。
&lt;strong&gt;示例&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;memory&amp;gt;
class Resource {
public:
    void use() {
        std::cout &amp;lt;&amp;lt; &quot;Using resource&quot; &amp;lt;&amp;lt; std::endl;
    }
    ~Resource() {
        std::cout &amp;lt;&amp;lt; &quot;Resource destroyed&quot; &amp;lt;&amp;lt; std::endl;
    }
};
int main() {
    // 创建一个unique_ptr管理Resource对象
    std::unique_ptr&amp;lt;Resource&amp;gt; resPtr(new Resource());
    // 使用资源
    resPtr-&amp;gt;use();
    // 不需要手动删除资源，resPtr离开作用域时会自动删除
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;使用技巧&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用&lt;code&gt;std::make_unique&lt;/code&gt;（C++14起可用）来创建&lt;code&gt;unique_ptr&lt;/code&gt;，避免内存分配和构造函数调用之间的异常安全问题。
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;auto resPtr = std::make_unique&amp;lt;Resource&amp;gt;();&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;在函数中返回&lt;code&gt;unique_ptr&lt;/code&gt;，以转移所有权。&lt;/li&gt;
&lt;li&gt;在容器中存储&lt;code&gt;unique_ptr&lt;/code&gt;，以管理动态数组。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;使用&lt;code&gt;std::shared_ptr&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;场景&lt;/strong&gt;：当你需要多个指针共享对同一个动态分配对象的访问权时。
&lt;strong&gt;示例&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;memory&amp;gt;
class Resource {
public:
    void use() {
        std::cout &amp;lt;&amp;lt; &quot;Using resource&quot; &amp;lt;&amp;lt; std::endl;
    }
    ~Resource() {
        std::cout &amp;lt;&amp;lt; &quot;Resource destroyed&quot; &amp;lt;&amp;lt; std::endl;
    }
};
int main() {
    // 创建shared_ptr
    std::shared_ptr&amp;lt;Resource&amp;gt; sharedRes = std::make_shared&amp;lt;Resource&amp;gt;();
    // 复制shared_ptr
    std::shared_ptr&amp;lt;Resource&amp;gt; anotherSharedRes = sharedRes;
    // 使用资源
    sharedRes-&amp;gt;use();
    anotherSharedRes-&amp;gt;use();
    // 当所有shared_ptr都离开作用域时，资源会被自动删除
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;使用技巧&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用&lt;code&gt;std::make_shared&lt;/code&gt;来创建&lt;code&gt;shared_ptr&lt;/code&gt;，以提高效率。&lt;/li&gt;
&lt;li&gt;注意避免循环引用，这会导致内存泄漏。&lt;/li&gt;
&lt;li&gt;在需要共享资源的多线程环境中使用&lt;code&gt;shared_ptr&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;使用&lt;code&gt;std::weak_ptr&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;场景&lt;/strong&gt;：用于解决&lt;code&gt;std::shared_ptr&lt;/code&gt;的循环引用问题，或者当你需要观察某个对象是否存在而不影响其生命周期时。
&lt;strong&gt;示例&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;memory&amp;gt;
class Resource {
public:
    void use() {
        std::cout &amp;lt;&amp;lt; &quot;Using resource&quot; &amp;lt;&amp;lt; std::endl;
    }
    ~Resource() {
        std::cout &amp;lt;&amp;lt; &quot;Resource destroyed&quot; &amp;lt;&amp;lt; std::endl;
    }
};
int main() {
    std::shared_ptr&amp;lt;Resource&amp;gt; sharedRes = std::make_shared&amp;lt;Resource&amp;gt;();
    std::weak_ptr&amp;lt;Resource&amp;gt; weakRes = sharedRes;
    // 尝试提升weak_ptr到shared_ptr
    if (std::shared_ptr&amp;lt;Resource&amp;gt; tempRes = weakRes.lock()) {
        tempRes-&amp;gt;use();
    } else {
        std::cout &amp;lt;&amp;lt; &quot;Resource is no longer available&quot; &amp;lt;&amp;lt; std::endl;
    }
    // 销毁shared_ptr
    sharedRes.reset();
    // 再次尝试提升
    if (weakRes.expired()) {
        std::cout &amp;lt;&amp;lt; &quot;Resource has been destroyed&quot; &amp;lt;&amp;lt; std::endl;
    }
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;使用技巧&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用&lt;code&gt;weak_ptr&lt;/code&gt;来打破&lt;code&gt;shared_ptr&lt;/code&gt;的循环引用。&lt;/li&gt;
&lt;li&gt;在需要观察对象是否存在时，使用&lt;code&gt;weak_ptr&lt;/code&gt;而不影响对象的生命周期。&lt;/li&gt;
&lt;li&gt;使用&lt;code&gt;weak_ptr::lock&lt;/code&gt;来安全地访问对象，如果对象已被销毁，则返回一个空的&lt;code&gt;shared_ptr&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>C++的结构绑定</title><link>http://zaoweiceng.github.io/posts/cpp/%E7%BB%93%E6%9E%84%E7%BB%91%E5%AE%9A/</link><guid isPermaLink="true">http://zaoweiceng.github.io/posts/cpp/%E7%BB%93%E6%9E%84%E7%BB%91%E5%AE%9A/</guid><description>C++的结构绑定允许我们将一个聚合类型（如结构体、数组等）的成员直接绑定到一组变量上，从而简化了代码的编写和阅读。这项特性在很多情况下可以使代码更加简洁和直观。</description><pubDate>Wed, 12 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;C++的结构绑定（Structure Binding）是C++17引入的一项新特性，它允许我们将一个聚合类型（如结构体、数组等）的成员直接绑定到一组变量上，从而简化了代码的编写和阅读。这项特性在很多情况下可以使代码更加简洁和直观。&lt;/p&gt;
&lt;h3&gt;基本概念&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;聚合类型&lt;/strong&gt;：在C++中，聚合类型包括数组、结构体（struct）、联合体（union）以及类（class），但这些类必须满足一定的条件，比如没有私有或保护成员、没有用户定义的构造函数等。
&lt;strong&gt;结构绑定&lt;/strong&gt;：允许我们创建一个或多个变量，这些变量直接绑定到聚合类型的成员上。&lt;/p&gt;
&lt;h3&gt;使用方法&lt;/h3&gt;
&lt;h4&gt;1. 绑定结构体成员&lt;/h4&gt;
&lt;p&gt;假设我们有一个简单的结构体：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct Point {
    int x;
    int y;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在没有结构绑定之前，如果我们想获取一个点对象的x和y值，我们可能需要这样做：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Point p = {1, 2};
int x = p.x;
int y = p.y;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用结构绑定，我们可以这样写：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Point p = {1, 2};
auto [px, py] = p;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里，&lt;code&gt;px&lt;/code&gt; 和 &lt;code&gt;py&lt;/code&gt; 分别绑定了 &lt;code&gt;p.x&lt;/code&gt; 和 &lt;code&gt;p.y&lt;/code&gt;。&lt;/p&gt;
&lt;h4&gt;2. 绑定数组元素&lt;/h4&gt;
&lt;p&gt;对于数组，结构绑定同样有效：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int arr[3] = {1, 2, 3};
auto [a, b, c] = arr;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里，&lt;code&gt;a&lt;/code&gt;、&lt;code&gt;b&lt;/code&gt; 和 &lt;code&gt;c&lt;/code&gt; 分别绑定了 &lt;code&gt;arr[0]&lt;/code&gt;、&lt;code&gt;arr[1]&lt;/code&gt; 和 &lt;code&gt;arr[2]&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;注意事项&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;类型推导&lt;/strong&gt;：结构绑定使用 &lt;code&gt;auto&lt;/code&gt; 关键字进行类型推导，因此编译器会自动推导出每个绑定变量的类型。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;生命周期&lt;/strong&gt;：绑定变量的生命周期与被绑定对象的生命周期相关联。如果被绑定对象销毁，绑定变量也将变得无效。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;const属性&lt;/strong&gt;：如果被绑定对象是 &lt;code&gt;const&lt;/code&gt; 的，那么绑定变量也将是 &lt;code&gt;const&lt;/code&gt; 的。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;引用绑定&lt;/strong&gt;：可以使用引用来绑定成员，这样对绑定变量的修改会反映到原始对象上。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;Point p = {1, 2};
auto&amp;amp; [px, py] = p;
px = 3;  // 修改p.x的值
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;嵌套结构&lt;/strong&gt;：结构绑定也支持嵌套结构。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;struct Rect {
    Point top_left;
    Point bottom_right;
};
Rect r = {{1, 2}, {3, 4}};
auto [tl, br] = r;
auto [tlx, tly] = tl;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里，&lt;code&gt;tl&lt;/code&gt; 和 &lt;code&gt;br&lt;/code&gt; 分别绑定了 &lt;code&gt;r.top_left&lt;/code&gt; 和 &lt;code&gt;r.bottom_right&lt;/code&gt;，而 &lt;code&gt;tlx&lt;/code&gt; 和 &lt;code&gt;tly&lt;/code&gt; 进一步绑定了 &lt;code&gt;tl.x&lt;/code&gt; 和 &lt;code&gt;tl.y&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;应用场景&lt;/h3&gt;
&lt;p&gt;结构绑定在需要快速访问结构体或数组成员的场景中非常有用，比如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;返回多个值的函数。&lt;/li&gt;
&lt;li&gt;遍历映射（map）或其他关联容器。&lt;/li&gt;
&lt;li&gt;处理元组（tuple）数据。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;#include&amp;lt;iostream&amp;gt;
using namespace std;
struct S {
    int a;
    int b;
};
auto f() {
    return S{1, 2};
}
int main(void) {
    auto [a, b] = f();
    cout &amp;lt;&amp;lt; a &amp;lt;&amp;lt; &quot; &quot; &amp;lt;&amp;lt; b &amp;lt;&amp;lt; endl;
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这段代码展示了如何使用结构化绑定（structured bindings）来解构一个结构体，并输出其成员变量的值。定义了一个结构体 S，它包含两个整数成员变量a,b。然后，定义了一个函数 f()，该函数返回一个 S类型的对象，并初始化其成员变量a, b分别为 1 和 2。在main 函数中，使用了 C++17 引入的结构化绑定特性。通过 &lt;code&gt;auto [a, b] = f();&lt;/code&gt;，可以将返回的类型对象的成员变量a和 b分别绑定到新的变量上。这样，便可以直接使用这些变量，而不需要通过结构体对象来访问其成员变量。&lt;/p&gt;
&lt;h3&gt;结论&lt;/h3&gt;
&lt;p&gt;结构绑定是C++17引入的一项强大特性，它简化了代码，提高了可读性。通过结构绑定，我们可以更直观地处理聚合类型的数据，使C++代码更加现代化和易于维护。&lt;/p&gt;
</content:encoded></item><item><title>Stream API</title><link>http://zaoweiceng.github.io/posts/java/stream%E6%B5%81/</link><guid isPermaLink="true">http://zaoweiceng.github.io/posts/java/stream%E6%B5%81/</guid><description>Stream API的简单用法</description><pubDate>Wed, 12 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Java 8引入了Stream API，这是一个用于处理集合的强大工具，它可以让你以声明式的方式处理数据。&lt;code&gt;List&lt;/code&gt;是Java集合框架中的一个接口，表示一个有序的集合（也称为序列）。当你有一个&lt;code&gt;List&lt;/code&gt;并且想要使用Stream来处理它时，你可以利用Stream API提供的各种操作来进行转换、过滤、排序、聚合等操作。&lt;/p&gt;
&lt;h3&gt;创建Stream&lt;/h3&gt;
&lt;p&gt;从&lt;code&gt;List&lt;/code&gt;创建Stream非常简单，只需要调用&lt;code&gt;stream()&lt;/code&gt;方法即可：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;List&amp;lt;String&amp;gt; list = Arrays.asList(&quot;apple&quot;, &quot;banana&quot;, &quot;cherry&quot;);
Stream&amp;lt;String&amp;gt; stream = list.stream();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果你想要并行处理，可以使用&lt;code&gt;parallelStream()&lt;/code&gt;方法：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Stream&amp;lt;String&amp;gt; parallelStream = list.parallelStream();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;只有单列集合才有stream，双列集合需要进一步操作：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;HashMap&amp;lt;String, Integer&amp;gt; hm = new HashMap&amp;lt;&amp;gt;();
hm.keySet().stream();
hm.entrySet().stream;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;数组获取stream：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int[] arr = {1, 2, 3, 4, 5, 6};
Arrays.stream(arr);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;同时，stream中的静态方法of可以针对一些零散的数据获取stream流。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Stream.of(1, 2, 3, 4, 5, 6);
Srream.of(arr); // 可变参类型，如果传递的是数组，那么数组必须是引用类型
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;中间操作&lt;/h3&gt;
&lt;p&gt;中间操作会返回一个新的Stream，允许操作链继续下去。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注意：流中进行的修改不会影响原数据&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;常见的中间操作包括：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Stream&amp;lt;T&amp;gt; filter(Predicate predicate)&lt;/td&gt;
&lt;td&gt;用于对流中的数据进行过滤&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stream&amp;lt;T&amp;gt; limit(long maxSize)&lt;/td&gt;
&lt;td&gt;返回此流中的元素组成的流，截取前指定参数个数的数据&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stream&amp;lt;T&amp;gt; skip(long n)&lt;/td&gt;
&lt;td&gt;跳过指定参数个数的数据，返回由该流的剩余元素组成的流&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;static &amp;lt;T&amp;gt; Stream&amp;lt;T&amp;gt; concat(Stream a, Stream b)&lt;/td&gt;
&lt;td&gt;合并a和b两个流为一个流&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stream&amp;lt;T&amp;gt; distinct()&lt;/td&gt;
&lt;td&gt;返回由该流的不同元素（根据Object.equals(Object) ）组成的流&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stream&amp;lt;T&amp;gt; map(Function&amp;lt;T, R&amp;gt;)&lt;/td&gt;
&lt;td&gt;转换元素。&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 过滤出长度大于4的字符串，并将其转换为大写
List&amp;lt;String&amp;gt; result = list.stream()
                          .filter(s -&amp;gt; s.length() &amp;gt; 4)
                          .map(String::toUpperCase)
                          .collect(Collectors.toList());
Stream.concat(list1.stream(), list2.stream()).collect(Collectors.toList());

list.stream().map(new Function&amp;lt;String, Integer&amp;gt;(){
    @Override
    public Integer apply(String s){
    	String[] arr = s.split(&quot;-&quot;);
        return Integer.parseInt(arr[1]);
    }
}).forEach(s -&amp;gt; System.out.println(s));

list.stream()
    .map(s -&amp;gt; Integer.parseInt(s.split(&quot;-&quot;)[1]))
    .forEach(s -&amp;gt; System.out.println(s));
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;终端操作&lt;/h3&gt;
&lt;p&gt;终端操作会触发Stream的执行，并产生一个结果或副作用。常见的终端操作包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;collect(Collector&amp;lt;T, A, R&amp;gt;)&lt;/code&gt;：收集元素到另一个集合中。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;toArray(IntFunction&amp;lt;? extends Object[]&amp;gt;)&lt;/code&gt;: 转为数组。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;reduce(T, BinaryOperator&amp;lt;T&amp;gt;)&lt;/code&gt;：将元素聚合为一个值。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;forEach(Consumer&amp;lt;T&amp;gt;)&lt;/code&gt;：对每个元素执行操作。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;count()&lt;/code&gt;：返回元素数量。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;findFirst()&lt;/code&gt; / &lt;code&gt;findAny()&lt;/code&gt;：返回第一个或任意一个元素。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;anyMatch(Predicate&amp;lt;T&amp;gt;)&lt;/code&gt; / &lt;code&gt;allMatch(Predicate&amp;lt;T&amp;gt;)&lt;/code&gt; / &lt;code&gt;noneMatch(Predicate&amp;lt;T&amp;gt;)&lt;/code&gt;：检查是否有元素匹配给定条件。
例如：&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;//计算所有字符串长度的总和
int totalLength = list.stream()
                      .mapToInt(String::length)
                      .count();

// toArray
String[] arr = list.stream().toArray(new IntFunction&amp;lt;String[]&amp;gt;(){
    @Override
    public String[] apply(int value){
        return new String[value];
    }
});

String[] arr = list.stream()
    .toArray(v-&amp;gt;new String[value]);

List&amp;lt;String&amp;gt; newlist = list.stream()
    .filer(s-&amp;gt;&quot;男&quot;.equals(s))
    .collect(Collectors.toList());

//姓名-性别-ID
// 使用map，map中的键不能重复
Map&amp;lt;String, Integer&amp;gt; map = list.stream()
    .filter(s -&amp;gt; &quot;男&quot;.equals(s.split(&quot;-&quot;)[1]))
    .collect(Collectors.toMap(new Function&amp;lt;String, String&amp;gt;(){
        @Override
        public String apply(String s) {
            return s.split(&quot;-&quot;)[0];
        }
    },new Function&amp;lt;String, Integer&amp;gt;(){
        @Override
        public Integer apply(String s) {
            return Integer.parseInt(s.split(&quot;-&quot;)[2]);
        }
    }));

Map&amp;lt;String, Integer&amp;gt; map = list.stream()
    .filter(s -&amp;gt; &quot;男&quot;.equals(s.split(&quot;-&quot;)[1]))
    .collect(Collectors.toMap(
        s-&amp;gt;s.split(&quot;-&quot;)[0],
        s-&amp;gt; Integer.parseInt(s.split(&quot;-&quot;)[2])));
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;短路操作&lt;/h3&gt;
&lt;p&gt;短路操作可以提前结束Stream的遍历。例如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;anyMatch(Predicate&amp;lt;T&amp;gt;)&lt;/code&gt;：只要有一个元素匹配就返回true，不再继续检查。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;findFirst()&lt;/code&gt;：找到第一个元素就返回，不再继续查找。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;使用示例&lt;/h3&gt;
&lt;p&gt;下面是一个完整的示例，展示了如何使用Stream来处理一个字符串列表：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class StreamExample {
    public static void main(String[] args) {
        List&amp;lt;String&amp;gt; fruits = Arrays.asList(&quot;apple&quot;, &quot;banana&quot;, &quot;cherry&quot;, &quot;date&quot;, &quot;fig&quot;, &quot;grape&quot;);
        // 过滤出长度大于4的单词，转换为大写，并排序
        List&amp;lt;String&amp;gt; processedFruits = fruits.stream()
                                             .filter(fruit -&amp;gt; fruit.length() &amp;gt; 4)
                                             .map(String::toUpperCase)
                                             .sorted()
                                             .collect(Collectors.toList());
        // 输出结果
        processedFruits.forEach(System.out::println);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个示例中，我们首先过滤出长度大于4的单词，然后将它们转换为大写字母，接着对结果进行排序，并最终收集到一个新的列表中。&lt;/p&gt;
&lt;h3&gt;注意事项&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Stream只能被消费一次，一旦进行了终端操作，就不能再使用这个Stream了。&lt;/li&gt;
&lt;li&gt;Stream操作不会修改原始数据源，它们是惰性的，只有在终端操作时才会执行。&lt;/li&gt;
&lt;li&gt;并行Stream可能会提高性能，但也可能导致线程安全问题，需要谨慎使用。
通过Stream API，你可以以更简洁、更声明式的方式处理集合数据，这使得代码更易于理解和维护。&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>HashMap源码</title><link>http://zaoweiceng.github.io/posts/java/hashmap%E6%BA%90%E7%A0%81/</link><guid isPermaLink="true">http://zaoweiceng.github.io/posts/java/hashmap%E6%BA%90%E7%A0%81/</guid><description>手撕源码</description><pubDate>Wed, 12 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;内部定义&lt;/h2&gt;
&lt;h3&gt;Node&lt;/h3&gt;
&lt;p&gt;基本哈希表的节点，用于大多数条目。哈希表内部存储键值对时，采用Node进行存储。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Node节点实现Map.Entry接口
// 该接口主要包含以下方法：
// K getKey();
// V getValue();
// V setValue(V value);
// boolean equals(Object o);
static class Node&amp;lt;K,V&amp;gt; implements Map.Entry&amp;lt;K,V&amp;gt; {
    final int hash;	// 键的哈希值
    final K key;	// 建
    V value;		// 值
    Node&amp;lt;K,V&amp;gt; next; // 下一个节点的地址
	...   
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;红黑树&lt;/h3&gt;
&lt;p&gt;当单个节点下挂载的链表长度超过一定值(8)时，会转换为红黑树。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; static final class TreeNode&amp;lt;K,V&amp;gt; extends LinkedHashMap.Entry&amp;lt;K,V&amp;gt; {
     TreeNode&amp;lt;K,V&amp;gt; parent;  // red-black tree links
     TreeNode&amp;lt;K,V&amp;gt; left;
     TreeNode&amp;lt;K,V&amp;gt; right;
     TreeNode&amp;lt;K,V&amp;gt; prev;    // needed to unlink next upon deletion
     boolean red;
     ...
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;内部节点表&lt;/h3&gt;
&lt;p&gt;HashMap内部存储数据的表，表在首次使用时初始化，并根据需要调整大小。分配后，长度始终为 2 的幂。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;transient Node&amp;lt;K,V&amp;gt;[] table;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;默认容量&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;static final int DEFAULT_INITIAL_CAPACITY = 1 &amp;lt;&amp;lt; 4; // aka 16
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;默认加载因子&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;static final float DEFAULT_LOAD_FACTOR = 0.75f;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;构造函数&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;默认构造函数不会初始化内部的节点表，节点表在首次使用时才会进行初始化。&lt;/p&gt;
&lt;h2&gt;Put操作&lt;/h2&gt;
&lt;p&gt;操作定义如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;如果覆盖了元素，则返回覆盖元素的值，否则返回null。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;put方法中的&lt;strong&gt;hash&lt;/strong&gt;函数的定义如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h &amp;gt;&amp;gt;&amp;gt; 16);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;利用键计算出对应的hash值，并经过一系列位操作以优化哈希表的性能，最后把返回值作为键的哈希值。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;put方法中&lt;strong&gt;putVal&lt;/strong&gt;函数定义如下：&lt;/p&gt;
&lt;p&gt;添加元素的时候至少考虑三种情况：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;数组位置为null&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;数组位置不为null，键不重复，挂在下面形成链表或者红黑树&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;数组位置不为null，键重复，元素覆盖&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node&amp;lt;K,V&amp;gt;[] tab; //定义一个局部变量，用来记录哈希表中数组的地址值。
    Node&amp;lt;K,V&amp;gt; p; 	 //临时的第三方变量，用来记录键值对对象的地址值
    int n, i;		 //n表示当前数组的长度，i表示索引
    //把哈希表中数组的地址值，赋值给局部变量tab
    if ((tab = table) == null || (n = tab.length) == 0)
        //resize()函数执行以下操作：
        //1.如果当前是第一次添加数据，底层会创建一个默认长度为16，加载因子为0.75的数组
        //2.如果不是第一次添加数据，会看数组中的元素是否达到了扩容的条件
        //如果没有达到扩容条件，底层不会做任何操作
        //如果达到了扩容条件，底层会把数组扩容为原先的两倍，并把数据全部转移到新的哈希表中
        n = (tab = resize()).length;
    	//表示把当前数组的长度赋值给n
    
    //将数组的长度跟键的哈希值进行计算，计算出当前键值对对象，在数组中应存入的位置
    //获取数组中对应元素的数据
    if ((p = tab[i = (n - 1) &amp;amp; hash]) == null)
        //如果获取到的值是null，底层会创建一个键值对对象，直接放到数组当中
        tab[i] = newNode(hash, key, value, null);
    else {
        Node&amp;lt;K,V&amp;gt; e; K k;
        if (p.hash == hash &amp;amp;&amp;amp;
            ((k = p.key) == key || (key != null &amp;amp;&amp;amp; key.equals(k))))
            
            e = p;
        else if (p instanceof TreeNode)
            //判断数组中获取出来的键值对是不是红黑树中的节点
			//如果是，则调用方法putTreeVal，把当前的节点按照红黑树的规则添加到树当中。
            e = ((TreeNode&amp;lt;K,V&amp;gt;)p).putTreeVal(this, tab, hash, key, value);
        else {
            //如果从数组中获取出来的键值对不是红黑树中的节点
			//表示此时下面挂的是链表
            for (int binCount = 0; ; ++binCount) {
                if ((e = p.next) == null) {
                    //此时就会创建一个新的节点，挂在下面形成链表
                    p.next = newNode(hash, key, value, null);
                    //判断当前链表长度是否超过8，如果超过8，就会调用方法treeifyBin
                    //treeifyBin方法的底层还会继续判断
                    //判断数组的长度是否大于等于64
                    //如果同时满足这两个条件，就会把这个链表转成红黑树
                    if (binCount &amp;gt;= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                //如果哈希值一样（哈希碰撞），就会调用equals方法比较内部的属性值是否相同，若相同则break
                if (e.hash == hash &amp;amp;&amp;amp;
                    ((k = e.key) == key || (key != null &amp;amp;&amp;amp; key.equals(k))))
                    break;
                p = e;
            }
        }
        //判断是否有元素的覆盖
        if (e != null) { // existing mapping for key 
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                // 仅修改数据，而不修改节点
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    //threshold：记录的就是数组的长度 * 0.75，哈希表的扩容时机  16 * 0.75 = 12
    if (++size &amp;gt; threshold)
        resize();
    afterNodeInsertion(evict);
    //表示当前没有覆盖任何元素，返回null
    return null;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;参数：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;键的哈希值&lt;/li&gt;
&lt;li&gt;键&lt;/li&gt;
&lt;li&gt;值&lt;/li&gt;
&lt;li&gt;如果为&lt;code&gt;true&lt;/code&gt;，则只有在键不存在时才插入新值。如果键已经存在，则不会替换现有的值。这个参数在&lt;code&gt;putIfAbsent&lt;/code&gt;方法中会用到。&lt;/li&gt;
&lt;li&gt;这个参数在&lt;code&gt;putVal&lt;/code&gt;方法中的默认值为&lt;code&gt;false&lt;/code&gt;。它在&lt;code&gt;LinkedHashMap&lt;/code&gt;的子类中用于控制是否在插入后执行删除操作。在&lt;code&gt;HashMap&lt;/code&gt;中，这个参数通常不被使用。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;putVal&lt;/code&gt;方法的工作原理大致如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;计算键的哈希值，并找到对应的桶（bucket）。&lt;/li&gt;
&lt;li&gt;检查桶中是否已经有键值对。如果有，则根据键的相等性（&lt;code&gt;equals&lt;/code&gt;方法）检查是否是同一个键。&lt;/li&gt;
&lt;li&gt;如果是同一个键，根据&lt;code&gt;onlyIfAbsent&lt;/code&gt;参数决定是否更新值。&lt;/li&gt;
&lt;li&gt;如果不是同一个键，或者桶为空，则将新的键值对插入到桶中。&lt;/li&gt;
&lt;li&gt;如果插入后桶的大小超过了负载因子与容量的乘积，则进行扩容操作。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这个方法的设计考虑了性能和线程安全（在&lt;code&gt;ConcurrentHashMap&lt;/code&gt;中）。在&lt;code&gt;HashMap&lt;/code&gt;中，它不是线程安全的，但在&lt;code&gt;ConcurrentHashMap&lt;/code&gt;中，&lt;code&gt;putVal&lt;/code&gt;方法会采用不同的策略来保证线程安全。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.bilibili.com/video/BV1yW4y1Y7Ms?p=15&quot;&gt;参考视频&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>java学习笔记（6）</title><link>http://zaoweiceng.github.io/posts/java/java%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0_6/</link><guid isPermaLink="true">http://zaoweiceng.github.io/posts/java/java%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0_6/</guid><description>Java学习之路</description><pubDate>Wed, 12 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;注解&lt;/h2&gt;
&lt;p&gt;Java注解（Annotation）是一种用于提供元数据（metadata）的机制，它允许我们在代码中嵌入补充信息，这些信息可以在编译时、运行时或部署时被读取和使用。注解不会改变程序的运行逻辑，但可以改变程序的处理方式。&lt;/p&gt;
&lt;h3&gt;注解的基本概念&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;注解定义&lt;/strong&gt;：注解是通过&lt;code&gt;@interface&lt;/code&gt;关键字定义的。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;注解使用&lt;/strong&gt;：注解可以用于类、方法、字段、参数、局部变量等位置。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;注解Retention&lt;/strong&gt;：注解的保留策略，指定注解在什么时期有效（源码阶段、编译阶段、运行时）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;注解Target&lt;/strong&gt;：注解的目标，指定注解可以用于哪些元素。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;内置注解&lt;/h3&gt;
&lt;p&gt;Java提供了几种内置注解：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;@Override&lt;/code&gt;：表示一个方法声明旨在重写超类中的另一个方法。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@Deprecated&lt;/code&gt;：表示不推荐使用该元素，通常是因为它很危险或存在更好的选择。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@SuppressWarnings&lt;/code&gt;：表示在编译期间忽略指定的警告。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;自定义注解&lt;/h3&gt;
&lt;p&gt;可以创建自定义注解来满足特定需求：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface MyAnnotation {
    String value() default &quot;default&quot;;
    int count() default 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;注解元素&lt;/h3&gt;
&lt;p&gt;注解可以包含元素，类似于方法：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;元素类型&lt;/strong&gt;：可以是基本类型、String、Class、枚举、注解或这些类型的数组。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;默认值&lt;/strong&gt;：可以为元素提供默认值。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;注解的使用&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;@MyAnnotation(value = &quot;example&quot;, count = 10)
public class MyClass {
    @MyAnnotation
    public void myMethod() {
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;注解处理&lt;/h3&gt;
&lt;p&gt;注解本身不包含行为，它们的行为是通过注解处理器来实现的。注解处理器可以在编译时或运行时读取注解并执行相应操作。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;编译时处理&lt;/strong&gt;：使用注解处理器（Annotation Processor）在编译时读取注解并生成代码或执行检查。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;运行时处理&lt;/strong&gt;：通过反射在运行时读取注解并执行操作。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;示例：运行时注解处理&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;public class AnnotationProcessor {
    public static void main(String[] args) throws NoSuchMethodException {
        MyClass obj = new MyClass();
        if (obj.getClass().isAnnotationPresent(MyAnnotation.class)) {
            MyAnnotation annotation = obj.getClass().getAnnotation(MyAnnotation.class);
            System.out.println(&quot;Value: &quot; + annotation.value());
            System.out.println(&quot;Count: &quot; + annotation.count());
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;注解的优点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;提供元数据&lt;/strong&gt;：为代码提供额外的信息，而不改变代码本身。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;简化代码&lt;/strong&gt;：减少编写重复代码的需要。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;增强功能&lt;/strong&gt;：通过注解可以实现一些特殊功能，如依赖注入、权限控制等。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;注解的局限性&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;性能影响&lt;/strong&gt;：运行时注解处理可能影响性能。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;复杂性&lt;/strong&gt;：过度使用注解可能导致代码复杂度增加。
注解是Java中一个非常强大和灵活的特性，它可以用于各种场景，如框架开发、代码生成、验证等。正确使用注解可以大大提高开发效率和代码质量。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;FunctionalInterface&lt;/h2&gt;
&lt;p&gt;在Java中，&lt;code&gt;FunctionalInterface&lt;/code&gt;是一个用于标注接口的注解，它表示该接口是一个函数式接口。函数式接口是Java 8引入的一个新概念，它是指只包含一个抽象方法的接口。这样的接口可以用来表示lambda表达式或者方法引用。&lt;/p&gt;
&lt;h3&gt;FunctionalInterface的特性&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;单一抽象方法&lt;/strong&gt;：函数式接口只能有一个抽象方法。但是它可以包含多个默认方法（default method）和静态方法（static method）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;允许继承方法&lt;/strong&gt;：如果接口继承了另一个接口的抽象方法，那么这个方法不会计入抽象方法的总数内。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Object类的方法&lt;/strong&gt;：函数式接口可以包含Object类的方法，如&lt;code&gt;toString()&lt;/code&gt;、&lt;code&gt;equals()&lt;/code&gt;等，这些方法不会计入抽象方法的总数内。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;FunctionalInterface的使用&lt;/h3&gt;
&lt;p&gt;使用&lt;code&gt;@FunctionalInterface&lt;/code&gt;注解可以确保接口满足函数式接口的定义。如果接口中定义了超过一个抽象方法，编译器将会报错。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@FunctionalInterface
public interface SimpleFunction {
    // 单一抽象方法
    int apply(int value);
    // 默认方法，不会影响FunctionalInterface的单一抽象方法规则
    default void printValue(int value) {
        System.out.println(value);
    }
    // 静态方法，同样不会影响
    static void printMessage(String message) {
        System.out.println(message);
    }
    // Object类的方法，不会计入抽象方法总数
    @Override
    String toString();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;FunctionalInterface的好处&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;保证接口的函数式特性&lt;/strong&gt;：通过编译器检查，确保接口只有一个抽象方法，适合用于lambda表达式。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;提高代码可读性&lt;/strong&gt;：使用&lt;code&gt;@FunctionalInterface&lt;/code&gt;注解的接口，其目的和用法更加明确，提高了代码的可读性。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;支持泛型&lt;/strong&gt;：函数式接口可以定义泛型方法，增加了接口的灵活性和适用性。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;常见的FunctionalInterface&lt;/h3&gt;
&lt;p&gt;Java 8的&lt;code&gt;java.util.function&lt;/code&gt;包中定义了许多常用的函数式接口，如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Predicate&amp;lt;T&amp;gt;&lt;/code&gt;：用于表示一个参数的布尔表达式。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Consumer&amp;lt;T&amp;gt;&lt;/code&gt;：表示一个接受一个参数并且没有返回值的操作。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Function&amp;lt;T, R&amp;gt;&lt;/code&gt;：表示一个接受一个参数并且返回结果的操作。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Supplier&amp;lt;T&amp;gt;&lt;/code&gt;：表示一个没有参数但是返回结果的操作。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;UnaryOperator&amp;lt;T&amp;gt;&lt;/code&gt;：表示一个接受一个参数并且返回相同类型结果的操作。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;BinaryOperator&amp;lt;T&amp;gt;&lt;/code&gt;：表示一个接受两个相同类型的参数并且返回相同类型结果的操作。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;示例&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import java.util.function.Function;
public class FunctionalInterfaceExample {
    public static void main(String[] args) {
        // 使用lambda表达式实现Function接口
        Function&amp;lt;Integer, Integer&amp;gt; square = x -&amp;gt; x * x;
        System.out.println(square.apply(5)); // 输出25
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这个示例中，我们定义了一个&lt;code&gt;Function&amp;lt;Integer, Integer&amp;gt;&lt;/code&gt;类型的变量&lt;code&gt;square&lt;/code&gt;，它接受一个&lt;code&gt;Integer&lt;/code&gt;类型的参数并返回一个&lt;code&gt;Integer&lt;/code&gt;类型的结果。我们使用lambda表达式&lt;code&gt;x -&amp;gt; x * x&lt;/code&gt;来表示这个函数，它将输入的数平方后返回。
通过&lt;code&gt;@FunctionalInterface&lt;/code&gt;，Java提供了一种简洁、高效的方式来表示和操作函数式编程中的函数。这在Java 8及以后的版本中极大地增强了Java语言的表达能力和灵活性。&lt;/p&gt;
&lt;h2&gt;ArrayList初始化&lt;/h2&gt;
&lt;p&gt;在Java中，&lt;code&gt;ArrayList&lt;/code&gt; 是一个常用的动态数组实现，它允许你存储和操作对象。当你想要在定义时给 &lt;code&gt;ArrayList&lt;/code&gt; 赋值，你可以使用多种方法，包括使用匿名内部类、使用双括号初始化（注意可能带来的潜在问题）或者使用Java 9及以上版本的集合工厂方法。
以下是一些示例：&lt;/p&gt;
&lt;h3&gt;1. 使用匿名内部类&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Main {
    public static void main(String[] args) {
        List&amp;lt;String&amp;gt; list = new ArrayList&amp;lt;String&amp;gt;() {{
            add(&quot;Apple&quot;);
            add(&quot;Banana&quot;);
            add(&quot;Cherry&quot;);
        }};
        System.out.println(list);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 使用双括号初始化（不推荐用于多线程环境）&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import java.util.ArrayList;
import java.util.List;
public class Main {
    public static void main(String[] args) {
        List&amp;lt;String&amp;gt; list = new ArrayList&amp;lt;String&amp;gt;() {{
            add(&quot;Apple&quot;);
            add(&quot;Banana&quot;);
            add(&quot;Cherry&quot;);
        }};
        System.out.println(list);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：双括号初始化会创建一个匿名内部类，这可能会导致内存泄漏或者在序列化时出现问题。&lt;/p&gt;
&lt;h3&gt;3. 使用Java 9的集合工厂方法&lt;/h3&gt;
&lt;p&gt;如果你使用的是Java 9或更高版本，你可以使用&lt;code&gt;List.of()&lt;/code&gt;方法来创建一个不可变的列表：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import java.util.List;
public class Main {
    public static void main(String[] args) {
        List&amp;lt;String&amp;gt; list = List.of(&quot;Apple&quot;, &quot;Banana&quot;, &quot;Cherry&quot;);
        System.out.println(list);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：&lt;code&gt;List.of()&lt;/code&gt;创建的列表是不可变的，即不能添加、删除或替换元素。&lt;/p&gt;
&lt;h3&gt;4. 使用Arrays.asList（注意返回的列表是固定大小的）&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import java.util.Arrays;
import java.util.List;
public class Main {
    public static void main(String[] args) {
        List&amp;lt;String&amp;gt; list = Arrays.asList(&quot;Apple&quot;, &quot;Banana&quot;, &quot;Cherry&quot;);
        System.out.println(list);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：&lt;code&gt;Arrays.asList()&lt;/code&gt;返回的列表是固定大小的，不支持添加或删除操作，但可以修改元素。&lt;/p&gt;
&lt;h2&gt;HashMap初始化&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;是的，HashMap也有类似的写法&lt;/strong&gt;。在Java中，HashMap是一个常用的键值对存储结构，你可以在定义时给它赋值。以下是一些示例：&lt;/p&gt;
&lt;h3&gt;1. 使用匿名内部类&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import java.util.HashMap;
import java.util.Map;
public class Main {
    public static void main(String[] args) {
        Map&amp;lt;String, String&amp;gt; map = new HashMap&amp;lt;String, String&amp;gt;() {{
            put(&quot;key1&quot;, &quot;value1&quot;);
            put(&quot;key2&quot;, &quot;value2&quot;);
            put(&quot;key3&quot;, &quot;value3&quot;);
        }};
        System.out.println(map);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 使用双括号初始化（不推荐用于多线程环境）&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import java.util.HashMap;
import java.util.Map;
public class Main {
    public static void main(String[] args) {
        Map&amp;lt;String, String&amp;gt; map = new HashMap&amp;lt;String, String&amp;gt;() {{
            put(&quot;key1&quot;, &quot;value1&quot;);
            put(&quot;key2&quot;, &quot;value2&quot;);
            put(&quot;key3&quot;, &quot;value3&quot;);
        }};
        System.out.println(map);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. 使用Java 9及以上的集合工厂方法&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import java.util.Map;
public class Main {
    public static void main(String[] args) {
        Map&amp;lt;String, String&amp;gt; map = Map.of(
            &quot;key1&quot;, &quot;value1&quot;,
            &quot;key2&quot;, &quot;value2&quot;,
            &quot;key3&quot;, &quot;value3&quot;
        );
        System.out.println(map);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意：&lt;code&gt;Map.of()&lt;/code&gt;创建的映射是不可变的，即不能添加、删除或替换键值对。&lt;/p&gt;
&lt;h3&gt;4. 使用Stream API（Java 8及以上）&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class Main {
    public static void main(String[] args) {
        Map&amp;lt;String, String&amp;gt; map = Stream.of(
            new AbstractMap.SimpleEntry&amp;lt;&amp;gt;(&quot;key1&quot;, &quot;value1&quot;),
            new AbstractMap.SimpleEntry&amp;lt;&amp;gt;(&quot;key2&quot;, &quot;value2&quot;),
            new AbstractMap.SimpleEntry&amp;lt;&amp;gt;(&quot;key3&quot;, &quot;value3&quot;)
        ).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
        System.out.println(map);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5. 使用Collections.unmodifiableMap（创建不可变映射）&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public class Main {
    public static void main(String[] args) {
        Map&amp;lt;String, String&amp;gt;modifiableMap = new HashMap&amp;lt;&amp;gt;();
        modifiableMap.put(&quot;key1&quot;, &quot;value1&quot;);
        modifiableMap.put(&quot;key2&quot;, &quot;value2&quot;);
        modifiableMap.put(&quot;key3&quot;, &quot;value3&quot;);
        Map&amp;lt;String, String&amp;gt; map = Collections.unmodifiableMap(modifiableMap);
        System.out.println(map);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意：&lt;code&gt;Collections.unmodifiableMap()&lt;/code&gt;创建的映射是不可变的，即不能添加、删除或替换键值对。&lt;/p&gt;
&lt;p&gt;方法二（双括号初始化）不推荐在多线程环境中使用的原因主要与Java的匿名内部类和实例变量的初始化顺序有关。以下是详细解释：&lt;/p&gt;
&lt;h4&gt;1. 匿名内部类的实例变量&lt;/h4&gt;
&lt;p&gt;当你使用双括号初始化时，实际上是在创建一个匿名内部类的实例。这个匿名内部类会捕获外部类的实例变量（如果有的话），并且在内部类中创建一个新的HashMap实例。&lt;/p&gt;
&lt;h4&gt;2. 实例变量的初始化顺序&lt;/h4&gt;
&lt;p&gt;在Java中，实例变量的初始化顺序是先执行父类的构造方法，然后是子类的成员变量初始化，最后是子类的构造方法。当你在一个类的构造方法中使用双括号初始化时，这个初始化过程可能会在这个类的实例完全构造完成之前开始。&lt;/p&gt;
&lt;h4&gt;3. 多线程环境中的问题&lt;/h4&gt;
&lt;p&gt;在多线程环境中，如果多个线程同时访问同一个尚未完全构造的对象，就可能导致以下问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;可见性&lt;/strong&gt;：一个线程对共享变量的修改可能对其他线程不可见。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;竞态条件&lt;/strong&gt;：多个线程同时读取和修改共享数据，导致不可预测的结果。
由于双括号初始化涉及到捕获外部类的实例变量，如果在多线程环境中使用，就可能遇到上述问题。特别是当这个HashMap被多个线程共享时，就可能导致数据不一致或其他并发问题。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;4. 示例&lt;/h4&gt;
&lt;p&gt;以下是一个简化的示例，说明为什么双括号初始化在多线程环境中可能有问题：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class SomeClass {
    private Map&amp;lt;String, String&amp;gt; map = new HashMap&amp;lt;String, String&amp;gt;() {{
        put(&quot;key1&quot;, &quot;value1&quot;);
        put(&quot;key2&quot;, &quot;value2&quot;);
    }};
    public SomeClass() {
        // 构造方法中的其他初始化代码
    }
    public Map&amp;lt;String, String&amp;gt; getMap() {
        return map;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这个示例中，如果多个线程在&lt;code&gt;SomeClass&lt;/code&gt;的实例完全构造完成之前就调用&lt;code&gt;getMap()&lt;/code&gt;方法，它们可能会看到一个未完全初始化的&lt;code&gt;map&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;为了避免这些潜在的问题，推荐在多线程环境中使用更明确和线程安全的方法来初始化HashMap，比如方法一（使用匿名内部类但不使用双括号）或其他线程安全的集合初始化方法。这样可以确保在多线程环境中对共享数据的访问是安全的。&lt;/p&gt;
&lt;h2&gt;不可变集合&lt;/h2&gt;
&lt;p&gt;不可变集合是一个长度不可变，内容也无法修改的集合&lt;/p&gt;
&lt;h3&gt;使用场景&lt;/h3&gt;
&lt;p&gt;如果某个数据不能被修改，把它防御性地拷贝到不可变集合中是个很好的实践。
当集合对象被不可信的库调用时，不可变形式是安全的。&lt;/p&gt;
&lt;h3&gt;不可变的list集合&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;List&amp;lt;String&amp;gt; list = List.of(&quot;张三&quot;, &quot;李四&quot;, &quot;王五&quot;, &quot;赵六&quot;);
System.out.println(&quot;---------------------------&quot;);
for (String s : list) {
    System.out.println(s);
}
System.out.println(&quot;---------------------------&quot;);
Iterator&amp;lt;String&amp;gt; it = list.iterator();
while(it.hasNext()){
    String s = it.next();
    System.out.println(s);
}
System.out.println(&quot;---------------------------&quot;);
for (int i = 0; i &amp;lt; list.size(); i++) {
    String s = list.get(i);
    System.out.println(s);
}
System.out.println(&quot;---------------------------&quot;);
//list.remove(&quot;李四&quot;);
//list.add(&quot;aaa&quot;);
list.set(0,&quot;aaa&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;不可变的Set集合&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;Set&amp;lt;String&amp;gt; set = Set.of(&quot;张三&quot;, &quot;张三&quot;, &quot;李四&quot;, &quot;王五&quot;, &quot;赵六&quot;);
for (String s : set) {
    System.out.println(s);
}
System.out.println(&quot;-----------------------&quot;);
Iterator&amp;lt;String&amp;gt; it = set.iterator();
while(it.hasNext()){
    String s = it.next();
    System.out.println(s);
}
System.out.println(&quot;-----------------------&quot;);
//set.remove(&quot;王五&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;不可变的Map集合&lt;/h3&gt;
&lt;h4&gt;键值对个数小于等于10&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;public class ImmutableDemo3 {
    public static void main(String[] args) {
       /*
        创建Map的不可变集合
            细节1：
                键是不能重复的
            细节2：
                Map里面的of方法，参数是有上限的，最多只能传递20个参数，10个键值对
            细节3：
                如果我们要传递多个键值对对象，数量大于10个，在Map接口中还有一个方法
        */

        //一旦创建完毕之后，是无法进行修改的，在下面的代码中，只能进行查询操作
        Map&amp;lt;String, String&amp;gt; map = Map.of(&quot;张三&quot;, &quot;南京&quot;, &quot;张三&quot;, &quot;北京&quot;, &quot;王五&quot;, &quot;上海&quot;, &quot;赵六&quot;, &quot;广州&quot;, &quot;孙七&quot;, &quot;深圳&quot;, &quot;周八&quot;, &quot;杭州&quot;, &quot;吴九&quot;, &quot;宁波&quot;, &quot;郑十&quot;, &quot;苏州&quot;, &quot;刘一&quot;, &quot;无锡&quot;, &quot;陈二&quot;, &quot;嘉兴&quot;);
        Set&amp;lt;String&amp;gt; keys = map.keySet();
        for (String key : keys) {
            String value = map.get(key);
            System.out.println(key + &quot;=&quot; + value);
        }
        System.out.println(&quot;--------------------------&quot;);
        Set&amp;lt;Map.Entry&amp;lt;String, String&amp;gt;&amp;gt; entries = map.entrySet();
        for (Map.Entry&amp;lt;String, String&amp;gt; entry : entries) {
            String key = entry.getKey();
            String value = entry.getValue();
            System.out.println(key + &quot;=&quot; + value);
        }
        System.out.println(&quot;--------------------------&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;键值对个数大于10&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;public class ImmutableDemo4 {
    public static void main(String[] args) {

        /*
            创建Map的不可变集合,键值对的数量超过10个
        */

        //1.创建一个普通的Map集合
        HashMap&amp;lt;String, String&amp;gt; hm = new HashMap&amp;lt;&amp;gt;();
        hm.put(&quot;张三&quot;, &quot;南京&quot;);
        hm.put(&quot;李四&quot;, &quot;北京&quot;);
        hm.put(&quot;王五&quot;, &quot;上海&quot;);
        hm.put(&quot;赵六&quot;, &quot;北京&quot;);
        hm.put(&quot;孙七&quot;, &quot;深圳&quot;);
        hm.put(&quot;周八&quot;, &quot;杭州&quot;);
        hm.put(&quot;吴九&quot;, &quot;宁波&quot;);
        hm.put(&quot;郑十&quot;, &quot;苏州&quot;);
        hm.put(&quot;刘一&quot;, &quot;无锡&quot;);
        hm.put(&quot;陈二&quot;, &quot;嘉兴&quot;);
        hm.put(&quot;aaa&quot;, &quot;111&quot;);

        //2.利用上面的数据来获取一个不可变的集合
/*
        //获取到所有的键值对对象（Entry对象）
        Set&amp;lt;Map.Entry&amp;lt;String, String&amp;gt;&amp;gt; entries = hm.entrySet();
        //把entries变成一个数组
        Map.Entry[] arr1 = new Map.Entry[0];
        //toArray方法在底层会比较集合的长度跟数组的长度两者的大小
        //如果集合的长度 &amp;gt; 数组的长度 ：数据在数组中放不下，此时会根据实际数据的个数，重新创建数组
        //如果集合的长度 &amp;lt;= 数组的长度：数据在数组中放的下，此时不会创建新的数组，而是直接用
        Map.Entry[] arr2 = entries.toArray(arr1);
        //不可变的map集合
        Map map = Map.ofEntries(arr2);
        map.put(&quot;bbb&quot;,&quot;222&quot;);*/


        //Map&amp;lt;Object, Object&amp;gt; map = Map.ofEntries(hm.entrySet().toArray(new Map.Entry[0]));

        Map&amp;lt;String, String&amp;gt; map = Map.copyOf(hm);
        map.put(&quot;bbb&quot;,&quot;222&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>java学习笔记（5）</title><link>http://zaoweiceng.github.io/posts/java/java%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0_5/</link><guid isPermaLink="true">http://zaoweiceng.github.io/posts/java/java%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0_5/</guid><description>Java学习之路</description><pubDate>Tue, 11 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;泛型&lt;/h2&gt;
&lt;h3&gt;泛型方法&lt;/h3&gt;
&lt;p&gt;泛型方法在调用时可以接收不同类型的参数。根据传递给泛型方法的参数类型，编译器适当地处理每一个方法调用。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;java 中泛型标记符：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;E&lt;/strong&gt; - Element (在集合中使用，因为集合中存放的是元素)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;T&lt;/strong&gt; - Type（Java 类）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;K&lt;/strong&gt; - Key（键）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;V&lt;/strong&gt; - Value（值）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;N&lt;/strong&gt; - Number（数值类型）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;？&lt;/strong&gt; - 表示不确定的 java 类型&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;public class GenericMethodTest
{
   // 泛型方法 printArray                         
   public static &amp;lt; E &amp;gt; void printArray( E[] inputArray )
   {
      // 输出数组元素            
         for ( E element : inputArray ){        
            System.out.printf( &quot;%s &quot;, element );
         }
         System.out.println();
    }
 
    public static void main( String args[] )
    {
        // 创建不同类型数组： Integer, Double 和 Character
        Integer[] intArray = { 1, 2, 3, 4, 5 };
        Double[] doubleArray = { 1.1, 2.2, 3.3, 4.4 };
        Character[] charArray = { &apos;H&apos;, &apos;E&apos;, &apos;L&apos;, &apos;L&apos;, &apos;O&apos; };
 
        System.out.println( &quot;整型数组元素为:&quot; );
        printArray( intArray  ); // 传递一个整型数组
 
        System.out.println( &quot;\n双精度型数组元素为:&quot; );
        printArray( doubleArray ); // 传递一个双精度型数组
 
        System.out.println( &quot;\n字符型数组元素为:&quot; );
        printArray( charArray ); // 传递一个字符型数组
    } 
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可能有时候，会想限制那些被允许传递到一个类型参数的类型种类范围。例如，一个操作数字的方法可能只希望接受Number或者Number子类的实例。这就是有界类型参数的目的。&lt;/p&gt;
&lt;p&gt;要声明一个有界的类型参数，首先列出类型参数的名称，后跟extends关键字，最后紧跟它的上界。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class MaximumTest
{
   // 比较三个值并返回最大值
   public static &amp;lt;T extends Comparable&amp;lt;T&amp;gt;&amp;gt; T maximum(T x, T y, T z)
   {                     
      T max = x; // 假设x是初始最大值
      if ( y.compareTo( max ) &amp;gt; 0 ){
         max = y; //y 更大
      }
      if ( z.compareTo( max ) &amp;gt; 0 ){
         max = z; // 现在 z 更大           
      }
      return max; // 返回最大对象
   }
   public static void main( String args[] )
   {
      System.out.printf( &quot;%d, %d 和 %d 中最大的数为 %d\n\n&quot;,
                   3, 4, 5, maximum( 3, 4, 5 ) );
 
      System.out.printf( &quot;%.1f, %.1f 和 %.1f 中最大的数为 %.1f\n\n&quot;,
                   6.6, 8.8, 7.7, maximum( 6.6, 8.8, 7.7 ) );
 
      System.out.printf( &quot;%s, %s 和 %s 中最大的数为 %s\n&quot;,&quot;pear&quot;,
         &quot;apple&quot;, &quot;orange&quot;, maximum( &quot;pear&quot;, &quot;apple&quot;, &quot;orange&quot; ) );
   }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;泛型类&lt;/h3&gt;
&lt;p&gt;泛型类的声明和非泛型类的声明类似，除了在类名后面添加了类型参数声明部分。&lt;/p&gt;
&lt;p&gt;和泛型方法一样，泛型类的类型参数声明部分也包含一个或多个类型参数，参数间用逗号隔开。一个泛型参数，也被称为一个类型变量，是用于指定一个泛型类型名称的标识符。因为他们接受一个或多个参数，这些类被称为参数化的类或参数化的类型。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import java.util.*;
 
public class GenericTest {
     
    public static void main(String[] args) {
        List&amp;lt;String&amp;gt; name = new ArrayList&amp;lt;String&amp;gt;();
        List&amp;lt;Integer&amp;gt; age = new ArrayList&amp;lt;Integer&amp;gt;();
        List&amp;lt;Number&amp;gt; number = new ArrayList&amp;lt;Number&amp;gt;();
        
        name.add(&quot;icon&quot;);
        age.add(18);
        number.add(314);
 
        //getUperNumber(name);//1
        getUperNumber(age);//2
        getUperNumber(number);//3
       
   }
 
   public static void getData(List&amp;lt;?&amp;gt; data) {
      System.out.println(&quot;data :&quot; + data.get(0));
   }
   
   public static void getUperNumber(List&amp;lt;? extends Number&amp;gt; data) {
          System.out.println(&quot;data :&quot; + data.get(0));
       }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 &lt;strong&gt;//1&lt;/strong&gt; 处会出现错误，因为 &lt;strong&gt;getUperNumber()&lt;/strong&gt; 方法中的参数已经限定了参数泛型上限为 &lt;strong&gt;Number&lt;/strong&gt;，所以泛型为 &lt;strong&gt;String&lt;/strong&gt; 是不在这个范围之内，所以会报错。&lt;/p&gt;
&lt;p&gt;3、类型通配符下限通过形如 &lt;strong&gt;List&amp;lt;? super Number&amp;gt;&lt;/strong&gt; 来定义，表示类型只能接受 &lt;strong&gt;Number&lt;/strong&gt; 及其上层父类类型，如 &lt;strong&gt;Object&lt;/strong&gt; 类型的实例。&lt;/p&gt;
&lt;h2&gt;序列化&lt;/h2&gt;
&lt;p&gt;序列化在 Java 中是通过 &lt;code&gt;java.io.Serializable&lt;/code&gt; 接口来实现的，该接口没有任何方法，只是一个标记接口，用于标识类可以被序列化。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import java.io.Serializable;

public class MyClass implements Serializable {
    // 类的成员和方法
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;序列化：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;MyClass obj = new MyClass();
try {
    FileOutputStream fileOut = new FileOutputStream(&quot;object.ser&quot;);
    ObjectOutputStream out = new ObjectOutputStream(fileOut);
    out.writeObject(obj);
    out.close();
    fileOut.close();
} catch (IOException e) {
    e.printStackTrace();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;反序列化：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;MyClass obj = null;
try {
    FileInputStream fileIn = new FileInputStream(&quot;object.ser&quot;);
    ObjectInputStream in = new ObjectInputStream(fileIn);
    obj = (MyClass) in.readObject();
    in.close();
    fileIn.close();
} catch (IOException e) {
    e.printStackTrace();
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Java数据结构</title><link>http://zaoweiceng.github.io/posts/java/java%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/</link><guid isPermaLink="true">http://zaoweiceng.github.io/posts/java/java%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/</guid><description>数组、列表、集合、映射、栈、队列、堆</description><pubDate>Tue, 11 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;数组（Arrays）&lt;/h2&gt;
&lt;p&gt;数组（Arrays）是一种基本的数据结构，可以存储固定大小的相同类型的元素。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int[] array = new int[5];
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;特点：&lt;/strong&gt; 固定大小，存储相同类型的元素。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;优点：&lt;/strong&gt; 随机访问元素效率高。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;缺点：&lt;/strong&gt; 大小固定，插入和删除元素相对较慢。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;列表（Lists）&lt;/h2&gt;
&lt;p&gt;Java 提供了多种列表实现，如 &lt;code&gt;ArrayList&lt;/code&gt; 和 &lt;code&gt;LinkedList&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;List&amp;lt;String&amp;gt; arrayList = new ArrayList&amp;lt;&amp;gt;();
List&amp;lt;Integer&amp;gt; linkedList = new LinkedList&amp;lt;&amp;gt;();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;ArrayList:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;ArrayList 类是一个可以动态修改的数组，与普通数组的区别就是它是没有固定大小的限制，可以添加或删除元素。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import java.util.ArrayList; // 引入 ArrayList 类
ArrayList&amp;lt;E&amp;gt; objectName =new ArrayList&amp;lt;&amp;gt;();　 // 初始化
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;特点：&lt;/strong&gt; 动态数组，可变大小。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;优点：&lt;/strong&gt; 高效的随机访问和快速尾部插入。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;缺点：&lt;/strong&gt; 中间插入和删除相对较慢。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;添加元素: add(obj)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;访问元素: get(int index)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;修改元素: set(int index, obj)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;删除元素: remove(int index)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;删除所有元素: removeAll(obj)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;元素数量: size()&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;元素索引: indexOf(obj)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;是否存在: contains(obj)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;复制: clone()&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;清空: clear()&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;替换所有: replaceAll(UnaryOperator&amp;lt;E&amp;gt; operator&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sites.replaceAll(e -&amp;gt; e.toUpperCase());&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt; numbers.replaceAll(e -&amp;gt; e * 2);;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;删除满足条件元素: removeIf(Predicate&amp;lt;E&amp;gt; filter)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sites.removeIf(e -&amp;gt; e.contains(&quot;Tao&quot;));&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;numbers.removeIf(e -&amp;gt; (e % 2) == 0);&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;遍历: forEach(Consumer&amp;lt;E&amp;gt; action)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;pre&gt;&lt;code&gt;numbers.forEach((e) -&amp;gt; {
            e = e * 10;
            System.out.print(e + &quot; &quot;);
        });
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;LinkedList:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;链表（Linked list）是一种常见的基础数据结构，是一种线性表，但是并不会按线性的顺序存储数据，而是在每一个节点里存到下一个节点的地址。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 引入 LinkedList 类
import java.util.LinkedList; 
LinkedList&amp;lt;E&amp;gt; list = new LinkedList&amp;lt;E&amp;gt;();   // 普通创建方法
LinkedList&amp;lt;E&amp;gt; list = new LinkedList(Collection&amp;lt;? extends E&amp;gt; c); // 使用集合创建链表
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;特点：&lt;/strong&gt; 双向链表，元素之间通过指针连接。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;优点：&lt;/strong&gt; 插入和删除元素高效，迭代器性能好。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;缺点：&lt;/strong&gt; 随机访问相对较慢。&lt;/li&gt;
&lt;li&gt;添加元素: add(obj)&lt;/li&gt;
&lt;li&gt;头部添加: addFirst(obj)&lt;/li&gt;
&lt;li&gt;尾部添加: addLast(obj)&lt;/li&gt;
&lt;li&gt;移除开头: removeFirst()&lt;/li&gt;
&lt;li&gt;移除尾部: removeLast()&lt;/li&gt;
&lt;li&gt;获取头部: getFirst()&lt;/li&gt;
&lt;li&gt;获取尾部: getLast()&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;集合（Sets）&lt;/h2&gt;
&lt;p&gt;集合（Sets）用于存储不重复的元素，常见的实现有 &lt;code&gt;HashSet&lt;/code&gt; 和 &lt;code&gt;TreeSet&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Set&amp;lt;String&amp;gt; hashSet = new HashSet&amp;lt;&amp;gt;();
Set&amp;lt;Integer&amp;gt; treeSet = new TreeSet&amp;lt;&amp;gt;();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;HashSet:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import java.util.HashSet; // 引入 HashSet 类
HashSet&amp;lt;String&amp;gt; sites = new HashSet&amp;lt;String&amp;gt;();
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;特点：&lt;/strong&gt; 无序集合，基于HashMap实现。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;优点：&lt;/strong&gt; 高效的查找和插入操作。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;缺点：&lt;/strong&gt; 不保证顺序。&lt;/li&gt;
&lt;li&gt;添加元素：add(obj)&lt;/li&gt;
&lt;li&gt;判断元素是否存在：contains(obj)&lt;/li&gt;
&lt;li&gt;删除元素：remove()&lt;/li&gt;
&lt;li&gt;清空元素：clear()&lt;/li&gt;
&lt;li&gt;计算大小：size()&lt;/li&gt;
&lt;li&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;TreeSet:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;**特点：**TreeSet 是有序集合，底层基于红黑树实现，不允许重复元素。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;优点：&lt;/strong&gt; 提供自动排序功能，适用于需要按顺序存储元素的场景。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;缺点：&lt;/strong&gt; 性能相对较差，不允许插入 null 元素。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;映射（Maps）&lt;/h2&gt;
&lt;p&gt;映射（Maps）用于存储键值对，常见的实现有 &lt;code&gt;HashMap&lt;/code&gt; 和 &lt;code&gt;TreeMap&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Map&amp;lt;String, Integer&amp;gt; hashMap = new HashMap&amp;lt;&amp;gt;();
Map&amp;lt;String, Integer&amp;gt; treeMap = new TreeMap&amp;lt;&amp;gt;();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;HashMap:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import java.util.HashMap; // 引入 HashMap 类
HashMap&amp;lt;Integer, String&amp;gt; Sites = new HashMap&amp;lt;Integer, String&amp;gt;();
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;特点：&lt;/strong&gt; 基于哈希表实现的键值对存储结构。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;优点：&lt;/strong&gt; 高效的查找、插入和删除操作。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;缺点：&lt;/strong&gt; 无序，不保证顺序。&lt;/li&gt;
&lt;li&gt;添加元素：put(key, value)&lt;/li&gt;
&lt;li&gt;访问元素：get(key)&lt;/li&gt;
&lt;li&gt;删除元素：remove(key)&lt;/li&gt;
&lt;li&gt;删除所有：clear()&lt;/li&gt;
&lt;li&gt;计算元素数量：size()&lt;/li&gt;
&lt;li&gt;获取所有key：keySet()&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;TreeMap:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;特点：&lt;/strong&gt; 基于红黑树实现的有序键值对存储结构。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;优点：&lt;/strong&gt; 有序，支持按照键的顺序遍历。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;缺点：&lt;/strong&gt; 插入和删除相对较慢。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;ThreeMap&amp;lt;Integer, String&amp;gt; tm = new TreeMap&amp;lt;&amp;gt;(new Comparator&amp;lt;Integer&amp;gt;(){
  @Override
  public int compare(Integer o1, Integer o2){
    return o2-o1;
  }
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;栈（Stack）&lt;/h2&gt;
&lt;p&gt;栈（Stack）是一种线性数据结构，它按照后进先出（Last In, First Out，LIFO）的原则管理元素。在栈中，新元素被添加到栈的顶部，而只能从栈的顶部移除元素。这就意味着最后添加的元素是第一个被移除的。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Stack&amp;lt;Integer&amp;gt; stack = new Stack&amp;lt;&amp;gt;();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Stack 类:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;特点：&lt;/strong&gt; 代表一个栈，通常按照后进先出（LIFO）的顺序操作元素。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;队列（Queue）&lt;/h2&gt;
&lt;p&gt;队列（Queue）遵循先进先出（FIFO）原则，常见的实现有 &lt;code&gt;LinkedList&lt;/code&gt; 和 &lt;code&gt;PriorityQueue&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Queue&amp;lt;String&amp;gt; queue = new LinkedList&amp;lt;&amp;gt;();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Queue 接口:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;特点：&lt;/strong&gt; 代表一个队列，通常按照先进先出（FIFO）的顺序操作元素。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;实现类：&lt;/strong&gt; &lt;code&gt;LinkedList&lt;/code&gt;, &lt;code&gt;PriorityQueue&lt;/code&gt;, &lt;code&gt;ArrayDeque&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;堆（Heap）&lt;/h2&gt;
&lt;p&gt;堆（Heap）优先队列的基础，可以实现最大堆和最小堆。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PriorityQueue&amp;lt;Integer&amp;gt; minHeap = new PriorityQueue&amp;lt;&amp;gt;();
PriorityQueue&amp;lt;Integer&amp;gt; maxHeap = new PriorityQueue&amp;lt;&amp;gt;(Collections.reverseOrder());
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>C++ Function</title><link>http://zaoweiceng.github.io/posts/cpp/function/</link><guid isPermaLink="true">http://zaoweiceng.github.io/posts/cpp/function/</guid><description>C++中函数相关内容</description><pubDate>Mon, 10 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;static&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;静态方法，没有this指针，不属于特定的对象。&lt;/li&gt;
&lt;li&gt;不访问这个对象的非静态成员。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;class demo{
    private:
    	std::string VERSION = &quot;v1.0.0&quot;;
    public:
    	static std::string getVersion();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;const&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;承诺不会在方法内不改变对象内部的值。&lt;/li&gt;
&lt;li&gt;非const能调用const方法，const不能调用非const&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;class demo{
    public:
    	std::string getString() const;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;mutable&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;允许在const中修改的成员变量&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;class demo{
    private:
    	int value;
    	mutable size_t numOfAccesses = 0;
    public:
    	int getValue() const{
            numOfAccesses++;
            return value;
        }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;noexcept&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;该方法不会抛出异常&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;void test(int a) noexcept;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;delete&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;显示的删除某个方法&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;class demo{
	public:
    	int foo(int a);
    	int foo(double a) = delete;
}  
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;以  &lt;code&gt;double&lt;/code&gt;为参数调用&lt;code&gt;foo&lt;/code&gt;时编译器会报错&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;inline&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;inline int getValue(){
    ......
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;内联类似于&lt;code&gt;#define&lt;/code&gt;，但是比使用&lt;code&gt;#define&lt;/code&gt;更安全&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;virtual&lt;/h2&gt;
&lt;p&gt;只有在基类中声明为virtual的方法才能被派生类重写&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;建议所有方法都声明为virtual(对于现代计算机而言开销很小，Java默认使用virtual)&lt;/li&gt;
&lt;li&gt;析构函数一定要声明为virtual，否则派生类将默认调用基类的析构函数&lt;/li&gt;
&lt;li&gt;纯虚方法告诉编译器，该方法在该类中不存在定义。包含纯虚方法的类成为&lt;em&gt;&lt;strong&gt;抽象类&lt;/strong&gt;&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;class Base{
    public:
    	virtual void method();
    	virtual void method1() = 0;//纯虚方法
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;override&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;class Next: public Base{
    public:
    	virtual void method() override;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;constexpr&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;常量表达式，通常在编译时进行计算&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;constexpr int getArraySize(){return 32;}
int nums[getArraySize()]; //getArraySize()是一个常量
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>本地大模型部署</title><link>http://zaoweiceng.github.io/posts/ai/ollama/</link><guid isPermaLink="true">http://zaoweiceng.github.io/posts/ai/ollama/</guid><description>本地大模型部署及调用</description><pubDate>Mon, 10 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;大模型运行时采用Ollama，Ollama 是一个开源的本地大语言模型运行框架，专为在本地机器上便捷部署和运行大型语言模型（LLM）而设计。Ollama 支持多种操作系统，包括 macOS、Windows、Linux 以及通过 Docker 容器运行。Ollama 提供对模型量化的支持，可以显著降低显存要求，使得在普通家用计算机上运行大型模型成为可能。&lt;/p&gt;
&lt;h4&gt;Ollama安装&lt;/h4&gt;
&lt;p&gt;Ollama 对硬件要求不高，旨在让用户能够轻松地在本地运行、管理和与大型语言模型进行交互。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Windows：
&lt;ul&gt;
&lt;li&gt;打开浏览器，访问 Ollama 官方网站：&lt;a href=&quot;https://ollama.com/download&quot;&gt;https://ollama.com/download&lt;/a&gt;，下载适用于 Windows 的安装程序。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Linux：
&lt;ul&gt;
&lt;li&gt;Linux 下可以使用一键安装脚本，运行命令：&lt;code&gt;curl -fsSL https://ollama.com/install.sh | bash&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;安装完成之后，通过&lt;code&gt;ollama --version&lt;/code&gt; 验证安装。&lt;/p&gt;
&lt;h4&gt;Ollama运行模型&lt;/h4&gt;
&lt;p&gt;Ollama 运行模型使用 &lt;strong&gt;ollama run&lt;/strong&gt; 命令。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ollama run xxx:yyy # xxx为模型名称，yyy为模型版本
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;若本地不存在该模型，Ollama会自动从服务器拉取。模型列表可以从Ollama官方&lt;a href=&quot;https://ollama.com/search&quot;&gt;链接&lt;/a&gt;找到。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;assets/1741582858287.png&quot; alt=&quot;1741582858287&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;assets/1741582875516.png&quot; alt=&quot;1741582875516&quot; /&gt;&lt;/p&gt;
&lt;p&gt;选择合适的模型，打开选择模型参数，然后复制命令到终端运行，即可自动部署。&lt;/p&gt;
&lt;p&gt;若需要部署第三方模型或者其它开源模型，可访问&lt;a href=&quot;https://huggingface.co/models&quot;&gt;huggingface&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;assets/1741583011479.png&quot; alt=&quot;1741583011479&quot; /&gt;&lt;/p&gt;
&lt;p&gt;选择合适的模型，然后选择使用该模型。(注意：选择模型时，应选择ollama支持的GGUF格式的模型版本。)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;assets/1741583210915.png&quot; alt=&quot;1741583210915&quot; /&gt;&lt;/p&gt;
&lt;p&gt;选择适合硬件的量化方案之后，复制该命令到终端，执行，Ollama会自动拉取该镜像。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;assets/1741583233945.png&quot; alt=&quot;1741583233945&quot; /&gt;&lt;/p&gt;
&lt;p&gt;国内huggingface会被限制访问，可将使用镜像网站：&lt;a href=&quot;https://hf-mirror.com/&quot;&gt;hf-mirror.com&lt;/a&gt;。拉取模型时，将&lt;code&gt;hf.co&lt;/code&gt;替换为&lt;code&gt;hf-mirror.com&lt;/code&gt; 。例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ollama run hf-mirror.com/Qwen/QwQ-32B-GGUF:Q4_0
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;API&lt;/h4&gt;
&lt;p&gt;Ollama 提供了基于 HTTP 的 API，允许开发者通过编程方式与模型进行交互。默认情况下，服务会运行在 &lt;code&gt;http://localhost:11434&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;Ollama 提供了以下主要 API 端点：&lt;/p&gt;
&lt;p&gt;生成文本（Generate Text）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;端点&lt;/strong&gt;：&lt;code&gt;POST /api/generate&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;功能&lt;/strong&gt;：向模型发送提示词（prompt），并获取生成的文本。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;请求格式&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;model&quot;: &quot;&amp;lt;model-name&amp;gt;&quot;,  // 模型名称
  &quot;prompt&quot;: &quot;&amp;lt;input-text&amp;gt;&quot;, // 输入的提示词
  &quot;stream&quot;: false,          // 是否启用流式响应（默认 false）
  &quot;options&quot;: {              // 可选参数
    &quot;temperature&quot;: 0.7,     // 温度参数
    &quot;max_tokens&quot;: 100       // 最大 token 数
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;响应格式&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;response&quot;: &quot;&amp;lt;generated-text&amp;gt;&quot;, // 生成的文本
  &quot;done&quot;: true                    // 是否完成
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;聊天（Chat）&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;端点&lt;/strong&gt;：&lt;code&gt;POST /api/chat&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;功能&lt;/strong&gt;：支持多轮对话，模型会记住上下文。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;请求格式&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;model&quot;: &quot;&amp;lt;model-name&amp;gt;&quot;,  // 模型名称
  &quot;messages&quot;: [             // 消息列表
    {
      &quot;role&quot;: &quot;user&quot;,       // 用户角色
      &quot;content&quot;: &quot;&amp;lt;input-text&amp;gt;&quot; // 用户输入
    }
  ],
  &quot;stream&quot;: false,          // 是否启用流式响应
  &quot;options&quot;: {              // 可选参数
    &quot;temperature&quot;: 0.7,
    &quot;max_tokens&quot;: 100
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;响应格式&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;message&quot;: {
    &quot;role&quot;: &quot;assistant&quot;,    // 助手角色
    &quot;content&quot;: &quot;&amp;lt;generated-text&amp;gt;&quot; // 生成的文本
  },
  &quot;done&quot;: true
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;列出本地模型（List Models）&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;端点&lt;/strong&gt;：&lt;code&gt;GET /api/tags&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;功能&lt;/strong&gt;：列出本地已下载的模型。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;响应格式&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;models&quot;: [
    {
      &quot;name&quot;: &quot;&amp;lt;model-name&amp;gt;&quot;, // 模型名称
      &quot;size&quot;: &quot;&amp;lt;model-size&amp;gt;&quot;, // 模型大小
      &quot;modified_at&quot;: &quot;&amp;lt;timestamp&amp;gt;&quot; // 修改时间
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;拉取模型（Pull Model）&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;端点&lt;/strong&gt;：&lt;code&gt;POST /api/pull&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;功能&lt;/strong&gt;：从模型库中拉取模型。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;请求格式&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;name&quot;: &quot;&amp;lt;model-name&amp;gt;&quot; // 模型名称
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;响应格式&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;status&quot;: &quot;downloading&quot;, // 下载状态
  &quot;digest&quot;: &quot;&amp;lt;model-digest&amp;gt;&quot; // 模型摘要
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;启用流式响应&lt;/h5&gt;
&lt;p&gt;同时，Ollama还支持流式响应，该功能适用于实时生成文本的场景。
在请求中设置 &lt;strong&gt;&quot;stream&quot;: true&lt;/strong&gt;，API 会逐行返回生成的文本。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl http://localhost:11434/api/generate -d &apos;{
  &quot;model&quot;: &quot;deepseek-coder&quot;,
  &quot;prompt&quot;: &quot;你好，你能帮我写一段代码吗？&quot;,
  &quot;stream&quot;: true
}&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;响应格式&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;每行返回一个 JSON 对象：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;response&quot;: &quot;&amp;lt;partial-text&amp;gt;&quot;,  // 部分生成的文本
  &quot;done&quot;:   false                // 是否完成
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;python调用ollama的API&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;非流式调用采用python的requests库&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;def ollama(prompt):
    with requests.post(ollama_base_url,json={
        &quot;model&quot;: llm_model_name,
        &quot;prompt&quot;: prompt,
        &quot;stream&quot;: False,
        &quot;options&quot;: {
            &quot;temperature&quot;: 0,
            &quot;max_tokens&quot;: 64800
        }
    }) as response:
        return response.json()[&apos;response&apos;]
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;流式调用相对于非流式要复杂一些，采用aiohttp库，并使用异步的写法&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;async def ollama_stream(prompt):
    async with aiohttp.ClientSession() as session:
        async with session.post(ollama_base_url,json={
            &quot;model&quot;: llm_model_name,  
            &quot;prompt&quot;: prompt, 
            &quot;stream&quot;: True,          
            &quot;options&quot;: {              
                &quot;temperature&quot;: 0,     
                &quot;max_tokens&quot;: 64800       
                }
            }) as response:
            ans = []
            sentence = &apos;&apos;
            response.raise_for_status()
            try:
                async for reses in response.content.iter_any():
                    lines = reses.decode(&apos;utf-8&apos;).split(&apos;\n&apos;)
                    for line in lines:
                        if line:
                            response_text = json.loads(line)[&apos;response&apos;]
                            sentence += response_text
                            if response_text == &apos;。&apos; or response_text == &apos;？&apos; or response_text == &apos;！&apos; or response_text == &apos;\n&apos; or response_text == &apos;\n\n&apos; :
                                ans.append(sentence)
                                sentence.replace(&apos;\n&apos;,&apos;&apos;)
                                yield sentence
                                sentence = &apos;&apos;
            except Exception as e:
                print(e)
                return
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;流式返回的代码中进行了一定的处理，是的返回的内容并非直接返回ollama的数据流，而是当生成完整的一个句子之后，返回该语句。这样的做法有助于衔接TTS引擎，是的用户不用等待过长的时间才能听到语音的回复。&lt;/p&gt;
</content:encoded></item><item><title>纯虚函数</title><link>http://zaoweiceng.github.io/posts/cpp/virtual/</link><guid isPermaLink="true">http://zaoweiceng.github.io/posts/cpp/virtual/</guid><description>纯虚函数</description><pubDate>Mon, 10 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;纯虚函数（Pure Virtual Function）是C++中的一种特殊类型的虚函数，它在基类中没有具体的实现，并且要求任何继承自该基类的子类都必须提供这个函数的具体实现。纯虚函数在基类中的声明形式是在函数声明后面加上“= 0”，这表示这个函数是纯虚的，没有实际的函数体。
纯虚函数的作用是定义一个接口，使得继承自基类的子类能够以多态的方式实现这个接口。由于纯虚函数没有实现，包含纯虚函数的类不能被实例化，这种类通常被称为&lt;strong&gt;抽象类&lt;/strong&gt;（Abstract Class）。
以下是一个简单的示例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Base {
public:
    virtual void pureVirtualFunction() = 0; // 纯虚函数
};
class Derived : public Base {
public:
    void pureVirtualFunction() override { // 实现纯虚函数
        // 函数的具体实现
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这个示例中，&lt;code&gt;Base&lt;/code&gt;是一个抽象类，因为它包含了一个纯虚函数&lt;code&gt;pureVirtualFunction()&lt;/code&gt;。&lt;code&gt;Derived&lt;/code&gt;类继承自&lt;code&gt;Base&lt;/code&gt;类，并且提供了&lt;code&gt;pureVirtualFunction()&lt;/code&gt;的具体实现。如果&lt;code&gt;Derived&lt;/code&gt;类没有实现这个纯虚函数，那么&lt;code&gt;Derived&lt;/code&gt;也会成为一个抽象类，不能被实例化。
纯虚函数是C++实现多态和接口的一种重要机制，它允许基类定义一个通用的接口，而具体的实现则由子类根据具体的需求来完成。&lt;/p&gt;
</content:encoded></item><item><title>C++ Class</title><link>http://zaoweiceng.github.io/posts/cpp/class/</link><guid isPermaLink="true">http://zaoweiceng.github.io/posts/cpp/class/</guid><description>C++中Class相关内容</description><pubDate>Mon, 10 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;继承&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;class next:public Base{
    ...
};
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;final&lt;/h2&gt;
&lt;p&gt;禁用继承&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Base final{
    ...
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;explicit&lt;/h2&gt;
&lt;p&gt;用于修饰类的构造函数，表示该构造函数必须为显示的。&lt;/p&gt;
&lt;p&gt;类构造函数只有一个参数时，可隐式构造：&lt;code&gt;CString s = 10&lt;/code&gt;编译器会自动调用&lt;code&gt;CString s(10)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;隐式构造容易出错&lt;/strong&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;上下转型&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;Base base = myImplement;
Base &amp;amp;base = myImplement;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;不加引用会导致数据截断。&lt;/p&gt;
&lt;p&gt;通常不建议向下转型，若需要使用则通过&lt;code&gt;static_cast&amp;lt;Implement *&amp;gt;(base)&lt;/code&gt;或者&lt;code&gt;dynamic_cast&amp;lt;Implement *&amp;gt;(base)&lt;/code&gt;。&lt;/p&gt;
&lt;h2&gt;运行时工具&lt;/h2&gt;
&lt;p&gt;RTTI运行时类型信息&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include&amp;lt;typeifo&amp;gt;
class Animal{public: virtual ~Animal() = default};
class Dog: public Animal{};
class Cat: public Animal{};
void speak(const Animal &amp;amp; animal){
    if(typeid(animal)==typeid(Dog)){...}
    if(typeid(animal)==typeid(Cat)){...}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;虚函数&lt;/h2&gt;
&lt;p&gt;虚函数是C++中实现多态的一种机制。当基类中的某个函数被声明为虚函数时，其派生类中的同名函数自动成为虚函数，无论是否使用&lt;code&gt;virtual&lt;/code&gt;关键字。&lt;/p&gt;
&lt;p&gt;c++中的虚函数主要作用是允许在派生类中重新定义基类的函数，以便实现特定的行为。通过基类指针或引用调用虚函数时，能够根据实际对象的类型来决定调用哪个函数，从而实现运行时多态。&lt;/p&gt;
&lt;p&gt;如果在基类中&lt;strong&gt;没有定义虚函数&lt;/strong&gt;，&lt;strong&gt;派生类可以调用基类中的非虚函数&lt;/strong&gt;。即使基类中的函数没有被定义为虚函数，派生类仍然可以继承并调用这些函数。在这种情况下，派生类对基类函数的调用是通过静态绑定进行的，即在编译时就已经确定了要调用的函数。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;继承&lt;/strong&gt;：派生类继承基类的所有非私有成员函数，包括非虚函数。因此，派生类对象可以调用这些继承来的非虚函数。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;静态绑定&lt;/strong&gt;：对于非虚函数，调用哪个函数是在编译时确定的，基于对象的静态类型（即声明时的类型）而不是动态类型（即实际对象的类型）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;覆盖与重载&lt;/strong&gt;：如果派生类定义了一个与基类中非虚函数同名但参数列表不同的函数，这被称为重载，而不是覆盖。调用哪个函数将基于函数的参数列表和对象的静态类型。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;访问控制&lt;/strong&gt;：派生类调用基类非虚函数的权限取决于基类中该函数的访问级别（如public、protected或private）。如果基类函数是私有的，派生类将无法直接调用它。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;需要注意的是&lt;/strong&gt;，如果希望在派生类中修改基类函数的行为，并且希望这种修改在通过基类指针或引用调用时生效（即实现多态），那么应该将基类中的相应函数声明为虚函数。非虚函数的调用不会实现多态行为。&lt;/p&gt;
&lt;h2&gt;虚基类&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;虚基类&lt;/strong&gt;是C++中用于解决多继承时可能出现的二义性问题的一种机制。当某个类在多条继承路径上被多次继承时，如果不使用虚基类，那么这个类的成员变量和成员函数可能会在派生类中存在多个副本，导致二义性和资源浪费。
&lt;strong&gt;概念：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;虚继承&lt;/strong&gt;：虚基类是通过虚继承来实现的。在声明继承关系时，使用&lt;code&gt;virtual&lt;/code&gt;关键字来指定基类为虚基类。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;共享副本&lt;/strong&gt;：通过虚继承，无论虚基类在继承层次中出现了多少次，派生类中都只包含虚基类的一个共享副本。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;虚基类表&lt;/strong&gt;：为了实现这种共享，编译器会为每个包含虚基类的对象添加一个虚基类表，用于记录虚基类成员的偏移量。
&lt;strong&gt;用法：&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;声明虚基类&lt;/strong&gt;：在继承时使用&lt;code&gt;virtual&lt;/code&gt;关键字。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class A { /* ... */ };
class B : virtual public A { /* ... */ };
class C : virtual public A { /* ... */ };
class D : public B, public C { /* ... */ };
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这个例子中，&lt;code&gt;A&lt;/code&gt;是&lt;code&gt;B&lt;/code&gt;和&lt;code&gt;C&lt;/code&gt;的虚基类，&lt;code&gt;D&lt;/code&gt;通过&lt;code&gt;B&lt;/code&gt;和&lt;code&gt;C&lt;/code&gt;间接继承了&lt;code&gt;A&lt;/code&gt;，但&lt;code&gt;A&lt;/code&gt;的成员在&lt;code&gt;D&lt;/code&gt;中只有一个副本。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;构造函数调用&lt;/strong&gt;：虚基类的构造函数由最派生的类负责调用，而不是由直接继承的子类调用。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class A {
public:
    A() { /* ... */ }
};
class B : virtual public A {
public:
    B() { /* ... */ } // 不调用A的构造函数
};
class C : virtual public A {
public:
    C() { /* ... */ } // 不调用A的构造函数
};
class D : public B, public C {
public:
    D() : A(), B(), C() { /* ... */ } // 由D负责调用A的构造函数
};
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;避免二义性&lt;/strong&gt;：使用虚基类可以避免在多继承情况下对基类成员的访问产生二义性。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;D d;
d.someMemberOfA; // 无二义性，因为A的成员在D中只有一个副本
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;注意事项：&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;虚基类主要解决的是多继承中的二义性问题，并不是所有多继承场景都需要使用虚基类。&lt;/li&gt;
&lt;li&gt;虚基类的构造函数由最派生的类负责调用，因此需要确保最派生的类能够访问到虚基类的构造函数。&lt;/li&gt;
&lt;li&gt;虚继承会引入一些额外的开销，如虚基类表，因此在使用时应权衡其利弊。
通过合理使用虚基类，可以有效地管理多继承关系，避免潜在的二义性和资源浪费问题。&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>数组索引可能的溢出及解决方式</title><link>http://zaoweiceng.github.io/posts/cpp/%E6%95%B0%E6%8D%AE%E6%BA%A2%E5%87%BA%E5%8F%8A%E5%A4%84%E7%90%86%E6%96%B9%E5%BC%8F/</link><guid isPermaLink="true">http://zaoweiceng.github.io/posts/cpp/%E6%95%B0%E6%8D%AE%E6%BA%A2%E5%87%BA%E5%8F%8A%E5%A4%84%E7%90%86%E6%96%B9%E5%BC%8F/</guid><description>访问索引时，索引值本身可能超出int上限，导致运行时异常</description><pubDate>Mon, 10 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;c++中int可能存在溢出问题，例如：
类型上溢出
$$
m = [(i+j)/2]int
$$
当i，j均为int类型时，可能出现int类型上溢出，可考虑使用一下手段在不改变数据类型的前提下，避免溢出：
$$
m = [i+(j-i)/2]
$$&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include&amp;lt;iostream&amp;gt;
using namespace std;

int main(void){
    int a = 1 &amp;lt;&amp;lt; 31, b = 1 &amp;lt;&amp;lt; 31;
    a -= 10;
    b -= 1;
    cout &amp;lt;&amp;lt; a &amp;lt;&amp;lt; &quot; &quot; &amp;lt;&amp;lt; b &amp;lt;&amp;lt; endl;
    cout &amp;lt;&amp;lt; (a+b)/2 &amp;lt;&amp;lt; endl;
    cout &amp;lt;&amp;lt; a + (b-a)/2 &amp;lt;&amp;lt; endl;
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>java学习笔记（4）</title><link>http://zaoweiceng.github.io/posts/java/java%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0_4/</link><guid isPermaLink="true">http://zaoweiceng.github.io/posts/java/java%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0_4/</guid><description>Java学习之路</description><pubDate>Mon, 10 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;虚函数&lt;/h2&gt;
&lt;p&gt;虚函数的存在是为了多态。Java 中其实没有虚函数的概念，它的普通函数就相当于 C++ 的虚函数，动态绑定是Java的默认行为。如果 Java 中不希望某个函数具有虚函数特性，可以加上 final 关键字变成非虚函数。&lt;/p&gt;
&lt;h3&gt;C++的虚函数&lt;/h3&gt;
&lt;p&gt;虚函数是C++中实现多态的一种机制。当基类中的某个函数被声明为虚函数时，其派生类中的同名函数自动成为虚函数，无论是否使用&lt;code&gt;virtual&lt;/code&gt;关键字。&lt;/p&gt;
&lt;p&gt;c++中的虚函数主要作用是允许在派生类中重新定义基类的函数，以便实现特定的行为。通过基类指针或引用调用虚函数时，能够根据实际对象的类型来决定调用哪个函数，从而实现运行时多态。&lt;/p&gt;
&lt;p&gt;如果在基类中&lt;strong&gt;没有定义虚函数&lt;/strong&gt;，&lt;strong&gt;派生类可以调用基类中的非虚函数&lt;/strong&gt;。即使基类中的函数没有被定义为虚函数，派生类仍然可以继承并调用这些函数。在这种情况下，派生类对基类函数的调用是通过静态绑定进行的，即在编译时就已经确定了要调用的函数。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;继承&lt;/strong&gt;：派生类继承基类的所有非私有成员函数，包括非虚函数。因此，派生类对象可以调用这些继承来的非虚函数。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;静态绑定&lt;/strong&gt;：对于非虚函数，调用哪个函数是在编译时确定的，基于对象的静态类型（即声明时的类型）而不是动态类型（即实际对象的类型）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;覆盖与重载&lt;/strong&gt;：如果派生类定义了一个与基类中非虚函数同名但参数列表不同的函数，这被称为重载，而不是覆盖。调用哪个函数将基于函数的参数列表和对象的静态类型。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;访问控制&lt;/strong&gt;：派生类调用基类非虚函数的权限取决于基类中该函数的访问级别（如public、protected或private）。如果基类函数是私有的，派生类将无法直接调用它。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;需要注意的是&lt;/strong&gt;，如果希望在派生类中修改基类函数的行为，并且希望这种修改在通过基类指针或引用调用时生效（即实现多态），那么应该将基类中的相应函数声明为虚函数。非虚函数的调用不会实现多态行为。&lt;/p&gt;
&lt;h3&gt;C++的虚基类&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;虚基类&lt;/strong&gt;是C++中用于解决多继承时可能出现的二义性问题的一种机制。当某个类在多条继承路径上被多次继承时，如果不使用虚基类，那么这个类的成员变量和成员函数可能会在派生类中存在多个副本，导致二义性和资源浪费。
&lt;strong&gt;概念：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;虚继承&lt;/strong&gt;：虚基类是通过虚继承来实现的。在声明继承关系时，使用&lt;code&gt;virtual&lt;/code&gt;关键字来指定基类为虚基类。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;共享副本&lt;/strong&gt;：通过虚继承，无论虚基类在继承层次中出现了多少次，派生类中都只包含虚基类的一个共享副本。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;虚基类表&lt;/strong&gt;：为了实现这种共享，编译器会为每个包含虚基类的对象添加一个虚基类表，用于记录虚基类成员的偏移量。
&lt;strong&gt;用法：&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;声明虚基类&lt;/strong&gt;：在继承时使用&lt;code&gt;virtual&lt;/code&gt;关键字。&lt;pre&gt;&lt;code&gt;class A { /* ... */ };
class B : virtual public A { /* ... */ };
class C : virtual public A { /* ... */ };
class D : public B, public C { /* ... */ };
&lt;/code&gt;&lt;/pre&gt;
在这个例子中，&lt;code&gt;A&lt;/code&gt;是&lt;code&gt;B&lt;/code&gt;和&lt;code&gt;C&lt;/code&gt;的虚基类，&lt;code&gt;D&lt;/code&gt;通过&lt;code&gt;B&lt;/code&gt;和&lt;code&gt;C&lt;/code&gt;间接继承了&lt;code&gt;A&lt;/code&gt;，但&lt;code&gt;A&lt;/code&gt;的成员在&lt;code&gt;D&lt;/code&gt;中只有一个副本。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;构造函数调用&lt;/strong&gt;：虚基类的构造函数由最派生的类负责调用，而不是由直接继承的子类调用。&lt;pre&gt;&lt;code&gt;class A {
public:
    A() { /* ... */ }
};
class B : virtual public A {
public:
    B() { /* ... */ } // 不调用A的构造函数
};
class C : virtual public A {
public:
    C() { /* ... */ } // 不调用A的构造函数
};
class D : public B, public C {
public:
    D() : A(), B(), C() { /* ... */ } // 由D负责调用A的构造函数
};
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;避免二义性&lt;/strong&gt;：使用虚基类可以避免在多继承情况下对基类成员的访问产生二义性。&lt;pre&gt;&lt;code&gt;D d;
d.someMemberOfA; // 无二义性，因为A的成员在D中只有一个副本
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;注意事项：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;虚基类主要解决的是多继承中的二义性问题，并不是所有多继承场景都需要使用虚基类。&lt;/li&gt;
&lt;li&gt;虚基类的构造函数由最派生的类负责调用，因此需要确保最派生的类能够访问到虚基类的构造函数。&lt;/li&gt;
&lt;li&gt;虚继承会引入一些额外的开销，如虚基类表，因此在使用时应权衡其利弊。
通过合理使用虚基类，可以有效地管理多继承关系，避免潜在的二义性和资源浪费问题。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;抽象类&lt;/h2&gt;
&lt;p&gt;在Java中，可以使用&lt;code&gt;abstract&lt;/code&gt;关键字来将一个方法标记为抽象方法，这样继承类就必须实现该方法。抽象方法没有方法体，只有方法签名。包含抽象方法的类也必须被声明为抽象类。
以下是一个示例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 声明一个抽象类
public abstract class AbstractClass {
    // 声明一个抽象方法
    public abstract void abstractMethod();
}
// 继承抽象类
public class ConcreteClass extends AbstractClass {
    // 实现抽象方法
    @Override
    public void abstractMethod() {
        // 方法实现
        System.out.println(&quot;实现了抽象方法&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这个示例中，&lt;code&gt;AbstractClass&lt;/code&gt;是一个抽象类，它包含了一个抽象方法&lt;code&gt;abstractMethod()&lt;/code&gt;。&lt;code&gt;ConcreteClass&lt;/code&gt;继承了&lt;code&gt;AbstractClass&lt;/code&gt;，并且必须实现&lt;code&gt;abstractMethod()&lt;/code&gt;方法。
如果不实现抽象方法，那么继承类也必须被声明为抽象类。例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 另一个继承抽象类的类，没有实现抽象方法
public abstract class AnotherAbstractClass extends AbstractClass {
    // 可以添加其他方法或属性
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这个例子中，&lt;code&gt;AnotherAbstractClass&lt;/code&gt;继承了&lt;code&gt;AbstractClass&lt;/code&gt;但没有实现&lt;code&gt;abstractMethod()&lt;/code&gt;，因此它也必须被声明为抽象类。&lt;/p&gt;
&lt;p&gt;Java的反射机制是Java语言的一个重要特性，它允许程序在运行时获取任意类的内部信息，包括类的方法、属性、构造器等，并且可以动态地创建对象、调用方法、访问属性。这种机制极大地提高了Java的灵活性和动态性，但同时也带来了一些性能开销和安全性问题。&lt;/p&gt;
&lt;h2&gt;反射机制的基本概念&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Class类&lt;/strong&gt;：Java中的每个类都有一个对应的Class对象，这个对象包含了类的所有信息。通过Class对象，可以获取类的构造器、方法、属性等。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Constructor类&lt;/strong&gt;：代表类的构造器。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Method类&lt;/strong&gt;：代表类的方法。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Field类&lt;/strong&gt;：代表类的属性（字段）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Modifier类&lt;/strong&gt;：提供了一系列静态方法来解析类的访问修饰符。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;反射的基本用法&lt;/h3&gt;
&lt;h4&gt;获取Class对象&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;通过类名：&lt;code&gt;Class&amp;lt;?&amp;gt; clazz = String.class;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;通过对象：&lt;code&gt;Class&amp;lt;?&amp;gt; clazz = &quot;hello&quot;.getClass();&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;通过全限定名：&lt;code&gt;Class&amp;lt;?&amp;gt; clazz = Class.forName(&quot;java.lang.String&quot;);&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;获取构造器&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Constructor&amp;lt;?&amp;gt;[] constructors = clazz.getConstructors();&lt;/code&gt; // 获取所有公共构造器&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Constructor&amp;lt;?&amp;gt; constructor = clazz.getConstructor(String.class);&lt;/code&gt; // 获取特定公共构造器&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Constructor&amp;lt;?&amp;gt;[] constructors = clazz.getDeclaredConstructors();&lt;/code&gt; // 获取所有构造器，包括私有&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Constructor&amp;lt;?&amp;gt; constructor = clazz.getDeclaredConstructor(String.class);&lt;/code&gt; // 获取特定构造器，包括私有&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;创建对象&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Object obj = constructor.newInstance(&quot;hello&quot;);&lt;/code&gt; // 调用构造器创建对象&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;获取方法&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Method[] methods = clazz.getMethods();&lt;/code&gt; // 获取所有公共方法&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Method method = clazz.getMethod(&quot;toString&quot;);&lt;/code&gt; // 获取特定公共方法&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Method[] methods = clazz.getDeclaredMethods();&lt;/code&gt; // 获取所有方法，包括私有&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Method method = clazz.getDeclaredMethod(&quot;someMethod&quot;, String.class);&lt;/code&gt; // 获取特定方法，包括私有&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;调用方法&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Object result = method.invoke(obj, &quot;param&quot;);&lt;/code&gt; // 调用方法，obj为对象实例，param为参数&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;获取属性&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Field[] fields = clazz.getFields();&lt;/code&gt; // 获取所有公共属性&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Field field = clazz.getField(&quot;someField&quot;);&lt;/code&gt; // 获取特定公共属性&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Field[] fields = clazz.getDeclaredFields();&lt;/code&gt; // 获取所有属性，包括私有&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Field field = clazz.getDeclaredField(&quot;someField&quot;);&lt;/code&gt; // 获取特定属性，包括私有&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;访问属性&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Object value = field.get(obj);&lt;/code&gt; // 获取属性值&lt;/li&gt;
&lt;li&gt;&lt;code&gt;field.set(obj, &quot;newValue&quot;);&lt;/code&gt; // 设置属性值&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;反射的应用场景&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;框架开发&lt;/strong&gt;：如Spring、Hibernate等框架大量使用反射来实现依赖注入、对象关系映射等功能。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;动态代理&lt;/strong&gt;：如Java的动态代理机制，通过反射来动态创建代理类。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;代码分析工具&lt;/strong&gt;：如IDE、代码生成器等工具使用反射来分析代码结构。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;测试框架&lt;/strong&gt;：如JUnit使用反射来识别和调用测试方法。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;反射的注意事项&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;性能开销&lt;/strong&gt;：反射操作相比直接代码调用会有较大的性能开销。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;安全性问题&lt;/strong&gt;：反射可以绕过类的访问控制，可能会破坏封装性，导致安全问题。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;代码可读性&lt;/strong&gt;：过度使用反射会使代码难以理解和维护。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;异常处理&lt;/strong&gt;：反射操作会抛出如&lt;code&gt;ClassNotFoundException&lt;/code&gt;、&lt;code&gt;NoSuchMethodException&lt;/code&gt;等异常，需要妥善处理。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;示例代码&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;public class ReflectionDemo {
    public static void main(String[] args) throws Exception {
        // 获取Class对象
        Class&amp;lt;?&amp;gt; clazz = Class.forName(&quot;java.lang.String&quot;);
        // 获取构造器并创建对象
        Constructor&amp;lt;?&amp;gt; constructor = clazz.getConstructor(String.class);
        Object obj = constructor.newInstance(&quot;hello&quot;);
        // 获取方法并调用
        Method method = clazz.getMethod(&quot;toUpperCase&quot;);
        Object result = method.invoke(obj);
        // 输出结果
        System.out.println(result); // 输出：HELLO
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个示例展示了如何使用反射来获取String类的构造器、创建对象、获取方法并调用方法。通过这个简单的例子，可以初步了解Java反射机制的基本用法。在实际应用中，反射通常用于更复杂和动态的场景。&lt;/p&gt;
&lt;h4&gt;获取接口和父类&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;Class&amp;lt;?&amp;gt; clazz = Person.class;

// 获取所有接口
Class&amp;lt;?&amp;gt;[] interfaces = clazz.getInterfaces();
for (Class&amp;lt;?&amp;gt; i : interfaces) {
    System.out.println(&quot;Interface: &quot; + i.getName());
}

// 获取父类
Class&amp;lt;?&amp;gt; superClass = clazz.getSuperclass();
System.out.println(&quot;Superclass: &quot; + superClass.getName());
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;code&gt;java.lang.reflect&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;java.lang.reflect&lt;/code&gt; 是 Java 反射机制的核心包，提供了操作类及其成员（字段、方法、构造函数等）的类和接口。通过这些 API，开发者可以在运行时动态地查询和修改类的结构。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./img/reflect.png&quot; alt=&quot;(https://www.runoob.com/java/java-reflection.html)&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://www.runoob.com/java/java-reflection.html&quot;&gt;图片来源：https://www.runoob.com/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;Class 类&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;功能：表示类的对象，提供了获取类信息的方法，如字段、方法、构造函数等。&lt;/li&gt;
&lt;li&gt;主要方法：
&lt;ul&gt;
&lt;li&gt;getFields()：获取所有公共字段。&lt;/li&gt;
&lt;li&gt;getDeclaredFields()：获取所有声明的字段，包括私有字段。&lt;/li&gt;
&lt;li&gt;getMethods()：获取所有公共方法。&lt;/li&gt;
&lt;li&gt;getDeclaredMethods()：获取所有声明的方法，包括私有方法。&lt;/li&gt;
&lt;li&gt;getConstructors()：获取所有公共构造函数。&lt;/li&gt;
&lt;li&gt;getDeclaredConstructors()：获取所有声明的构造函数，包括私有构造函数。&lt;/li&gt;
&lt;li&gt;getSuperclass()：获取类的父类。&lt;/li&gt;
&lt;li&gt;getInterfaces()：获取类实现的所有接口。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;Field 类&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;功能：表示类的字段（属性），提供了访问和修改字段值的方法。&lt;/li&gt;
&lt;li&gt;主要方法：
&lt;ul&gt;
&lt;li&gt;get(Object obj)：获取指定对象的字段值。&lt;/li&gt;
&lt;li&gt;set(Object obj, Object value)：设置指定对象的字段值。&lt;/li&gt;
&lt;li&gt;getType()：获取字段的数据类型。&lt;/li&gt;
&lt;li&gt;getModifiers()：获取字段的修饰符（如 public、private）。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;Method 类&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;功能：表示类的方法，提供了调用方法的能力。&lt;/li&gt;
&lt;li&gt;主要方法：
&lt;ul&gt;
&lt;li&gt;invoke(Object obj, Object... args)：调用指定对象的方法。&lt;/li&gt;
&lt;li&gt;getReturnType()：获取方法的返回类型。&lt;/li&gt;
&lt;li&gt;etParameterTypes()：获取方法的参数类型。&lt;/li&gt;
&lt;li&gt;etModifiers()：获取方法的修饰符（如 public、private）。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;Constructor 类&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;功能：表示类的构造函数，提供了创建对象的能力。&lt;/li&gt;
&lt;li&gt;主要方法：
&lt;ul&gt;
&lt;li&gt;newInstance(Object... initargs)：创建一个新实例，使用指定的构造函数参数。&lt;/li&gt;
&lt;li&gt;getParameterTypes()：获取构造函数的参数类型。&lt;/li&gt;
&lt;li&gt;getModifiers()：获取构造函数的修饰符（如 public、private）。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Java 排序</title><link>http://zaoweiceng.github.io/posts/java/java_sort/</link><guid isPermaLink="true">http://zaoweiceng.github.io/posts/java/java_sort/</guid><description>Java学习之路</description><pubDate>Sun, 09 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Java排序&lt;/h2&gt;
&lt;p&gt;在Java中，&lt;code&gt;Collections&lt;/code&gt; 类的 &lt;code&gt;sort()&lt;/code&gt; 方法用于对列表进行排序，而 &lt;code&gt;Arrays&lt;/code&gt; 类的 &lt;code&gt;sort()&lt;/code&gt; 方法用于对数组进行排序。如果你想要对自定义对象数组或列表进行排序，你需要确保这些对象实现了 &lt;code&gt;Comparable&lt;/code&gt; 接口。
以下是如何手动实现 &lt;code&gt;Comparable&lt;/code&gt; 接口的步骤：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;定义自定义类&lt;/strong&gt;：首先，定义一个自定义类，比如 &lt;code&gt;Person&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;实现 &lt;code&gt;Comparable&lt;/code&gt; 接口&lt;/strong&gt;：在自定义类中实现 &lt;code&gt;Comparable&lt;/code&gt; 接口，并指定比较的对象类型。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;覆盖 &lt;code&gt;compareTo&lt;/code&gt; 方法&lt;/strong&gt;：实现 &lt;code&gt;compareTo&lt;/code&gt; 方法，定义对象的比较逻辑。
下面是一个示例，展示了如何为一个 &lt;code&gt;Person&lt;/code&gt; 类实现 &lt;code&gt;Comparable&lt;/code&gt; 接口，以便根据 &lt;code&gt;Person&lt;/code&gt; 的 &lt;code&gt;age&lt;/code&gt; 属性进行排序：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;public class Person implements Comparable&amp;lt;Person&amp;gt; {
    private String name;
    private int age;
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public int getAge() {
        return age;
    }
    @Override
    public int compareTo(Person other) {
        // 根据年龄升序排序
        return this.age - other.age;
    }
    @Override
    public String toString() {
        return &quot;Person{name=&apos;&quot; + name + &quot;&apos;, age=&quot; + age + &apos;}&apos;;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这个例子中，&lt;code&gt;compareTo&lt;/code&gt; 方法根据 &lt;code&gt;Person&lt;/code&gt; 对象的 &lt;code&gt;age&lt;/code&gt; 属性进行比较。如果需要降序排序，可以修改 &lt;code&gt;compareTo&lt;/code&gt; 方法中的返回值为 &lt;code&gt;other.age - this.age&lt;/code&gt;。
现在，你可以使用 &lt;code&gt;Arrays.sort()&lt;/code&gt; 或 &lt;code&gt;Collections.sort()&lt;/code&gt; 方法对 &lt;code&gt;Person&lt;/code&gt; 对象数组或列表进行排序：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import java.util.Arrays;
import java.util.List;
public class Main {
    public static void main(String[] args) {
        Person[] people = {
            new Person(&quot;Alice&quot;, 30),
            new Person(&quot;Bob&quot;, 25),
            new Person(&quot;Charlie&quot;, 35)
        };
        Arrays.sort(people);
        for (Person person : people) {
            System.out.println(person);
        }
        List&amp;lt;Person&amp;gt; personList = Arrays.asList(people);
        Collections.sort(personList);
        for (Person person : personList) {
            System.out.println(person);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这个例子中，&lt;code&gt;Arrays.sort(people)&lt;/code&gt; 和 &lt;code&gt;Collections.sort(personList)&lt;/code&gt; 都会根据 &lt;code&gt;Person&lt;/code&gt; 类中实现的 &lt;code&gt;compareTo&lt;/code&gt; 方法进行排序。输出将显示根据年龄排序的 &lt;code&gt;Person&lt;/code&gt; 对象。&lt;/p&gt;
&lt;h2&gt;&lt;code&gt;Arrays&lt;/code&gt;和&lt;code&gt;Collections&lt;/code&gt;类中常用的一些API方法：&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;类名&lt;/th&gt;
&lt;th&gt;方法签名&lt;/th&gt;
&lt;th&gt;描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Arrays&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;static void sort(int[] a)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;对整型数组进行排序&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;static void sort(Object[] a)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;对对象数组进行排序，要求对象实现&lt;code&gt;Comparable&lt;/code&gt;接口&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;static void sort(T[] a, Comparator&amp;lt;? super T&amp;gt; c)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;使用指定的&lt;code&gt;Comparator&lt;/code&gt;对对象数组进行排序&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;static int binarySearch(int[] a, int key)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;在已排序的整型数组中使用二分搜索查找元素&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;static int binarySearch(Object[] a, Object key)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;在已排序的对象数组中使用二分搜索查找元素，要求对象实现&lt;code&gt;Comparable&lt;/code&gt;接口&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;static int binarySearch(T[] a, T key, Comparator&amp;lt;? super T&amp;gt; c)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;使用指定的&lt;code&gt;Comparator&lt;/code&gt;在已排序的对象数组中进行二分搜索&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;static void fill(int[] a, int val)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;将指定的整数值填充到数组中&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;static void fill(Object[] a, Object val)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;将指定的对象引用填充到数组中&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;static boolean equals(int[] a, int[] a2)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;判断两个整型数组是否相等&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;static boolean equals(Object[] a, Object[] a2)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;判断两个对象数组是否相等&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;static String toString(int[] a)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;返回数组的字符串表示形式&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;static String toString(Object[] a)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;返回对象数组的字符串表示形式&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Collections&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;static &amp;lt;T extends Comparable&amp;lt;? super T&amp;gt;&amp;gt; void sort(List&amp;lt;T&amp;gt; list)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;对列表进行排序，要求列表中的元素实现&lt;code&gt;Comparable&lt;/code&gt;接口&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;static &amp;lt;T&amp;gt; void sort(List&amp;lt;T&amp;gt; list, Comparator&amp;lt;? super T&amp;gt; c)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;使用指定的&lt;code&gt;Comparator&lt;/code&gt;对列表进行排序&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;static int binarySearch(List&amp;lt;? extends Comparable&amp;lt;? super T&amp;gt;&amp;gt; list, T key)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;在已排序的列表中使用二分搜索查找元素，要求元素实现&lt;code&gt;Comparable&lt;/code&gt;接口&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;static int binarySearch(List&amp;lt;? extends T&amp;gt; list, T key, Comparator&amp;lt;? super T&amp;gt; c)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;使用指定的&lt;code&gt;Comparator&lt;/code&gt;在已排序的列表中进行二分搜索&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;static void reverse(List&amp;lt;?&amp;gt; list)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;反转列表中元素的顺序&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;static void shuffle(List&amp;lt;?&amp;gt; list)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;随机打乱列表中元素的顺序&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;static void swap(List&amp;lt;?&amp;gt; list, int i, int j)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;交换列表中两个位置上的元素&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;static &amp;lt;T&amp;gt; void fill(List&amp;lt;? super T&amp;gt; list, T obj)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;使用指定的对象填充列表&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;static &amp;lt;T&amp;gt; boolean replaceAll(List&amp;lt;T&amp;gt; list, T oldVal, T newVal)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;替换列表中所有旧值为新值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;static boolean addAll(Collection&amp;lt;? super T&amp;gt; c, T... elements)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;将所有指定元素添加到集合中&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;static &amp;lt;T&amp;gt; T min(Collection&amp;lt;? extends T&amp;gt; coll)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;返回集合中的最小元素，要求元素实现&lt;code&gt;Comparable&lt;/code&gt;接口&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;static &amp;lt;T&amp;gt; T min(Collection&amp;lt;? extends T&amp;gt; coll, Comparator&amp;lt;? super T&amp;gt; comp)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;使用指定的&lt;code&gt;Comparator&lt;/code&gt;返回集合中的最小元素&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;static &amp;lt;T&amp;gt; T max(Collection&amp;lt;? extends T&amp;gt; coll)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;返回集合中的最大元素，要求元素实现&lt;code&gt;Comparable&lt;/code&gt;接口&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;static &amp;lt;T&amp;gt; T max(Collection&amp;lt;? extends T&amp;gt; coll, Comparator&amp;lt;? super T&amp;gt; comp)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;使用指定的&lt;code&gt;Comparator&lt;/code&gt;返回集合中的最大元素&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;static void rotate(List&amp;lt;?&amp;gt; list, int distance)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;将列表中的元素循环移动指定的距离&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Arrays&lt;/code&gt;类主要用于数组操作，而&lt;code&gt;Collections&lt;/code&gt;类主要用于集合框架中的列表、集和映射操作。在使用这些方法时，需要确保传入的参数符合方法的要求，例如数组或列表已经排序、元素类型正确等。&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Comparator&lt;/h2&gt;
&lt;p&gt;在Java中，&lt;code&gt;Comparator&lt;/code&gt;接口用于定义对象的比较逻辑，尤其是在需要对某个类的对象进行排序，但该类没有实现&lt;code&gt;Comparable&lt;/code&gt;接口，或者需要按照不同的排序逻辑进行排序时。实现&lt;code&gt;Comparator&lt;/code&gt;接口需要创建一个实现了&lt;code&gt;Comparator&lt;/code&gt;接口的类，并覆盖其&lt;code&gt;compare&lt;/code&gt;方法。
以下是如何实现&lt;code&gt;Comparator&lt;/code&gt;接口的步骤：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;创建一个实现&lt;code&gt;Comparator&lt;/code&gt;接口的类。&lt;/li&gt;
&lt;li&gt;覆盖&lt;code&gt;compare&lt;/code&gt;方法，定义比较逻辑。&lt;/li&gt;
&lt;li&gt;在需要排序的地方使用这个&lt;code&gt;Comparator&lt;/code&gt;。
下面是一个简单的示例，展示了如何为&lt;code&gt;Person&lt;/code&gt;类实现一个&lt;code&gt;Comparator&lt;/code&gt;来根据年龄进行排序：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;import java.util.Comparator;
// Person类
class Person {
    String name;
    int age;
    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    @Override
    public String toString() {
        return name + &quot;: &quot; + age;
    }
}
// 实现Comparator接口的AgeComparator类
class AgeComparator implements Comparator&amp;lt;Person&amp;gt; {
    @Override
    public int compare(Person p1, Person p2) {
        return Integer.compare(p1.age, p2.age);
    }
}
// 主类
public class ComparatorExample {
    public static void main(String[] args) {
        Person[] people = {
            new Person(&quot;Alice&quot;, 30),
            new Person(&quot;Bob&quot;, 25),
            new Person(&quot;Charlie&quot;, 35)
        };
        // 使用Arrays.sort方法和AgeComparator来排序
        Arrays.sort(people, new AgeComparator());
        // 输出排序后的结果
        for (Person person : people) {
            System.out.println(person);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这个示例中，&lt;code&gt;AgeComparator&lt;/code&gt;类实现了&lt;code&gt;Comparator&amp;lt;Person&amp;gt;&lt;/code&gt;接口，并覆盖了&lt;code&gt;compare&lt;/code&gt;方法，该方法比较两个&lt;code&gt;Person&lt;/code&gt;对象的年龄。在&lt;code&gt;main&lt;/code&gt;方法中，我们使用&lt;code&gt;Arrays.sort&lt;/code&gt;方法和&lt;code&gt;AgeComparator&lt;/code&gt;实例来对&lt;code&gt;Person&lt;/code&gt;数组进行排序。
&lt;code&gt;compare&lt;/code&gt;方法的返回值应该是一个负数、零或正数，分别表示第一个参数小于、等于或大于第二个参数。
此外，从Java 8开始，你可以使用lambda表达式来简化&lt;code&gt;Comparator&lt;/code&gt;的实现，如下所示：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 使用lambda表达式创建Comparator
Comparator&amp;lt;Person&amp;gt; ageComparator = (p1, p2) -&amp;gt; Integer.compare(p1.age, p2.age);
// 使用Arrays.sort方法和lambda表达式来排序
Arrays.sort(people, ageComparator);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这种方式更加简洁，特别适合于简单的比较逻辑。&lt;/p&gt;
</content:encoded></item><item><title>Java学习笔记（3）</title><link>http://zaoweiceng.github.io/posts/java/java%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0_3/</link><guid isPermaLink="true">http://zaoweiceng.github.io/posts/java/java%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0_3/</guid><description>Java学习之路</description><pubDate>Sun, 09 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;try-with-resources&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;try-with-resources&lt;/code&gt;是Java 7引入的一种特性，用于自动管理资源，例如文件、网络连接等。这种机制确保了在&lt;code&gt;try&lt;/code&gt;代码块执行完毕后，资源会被自动关闭，从而避免了资源泄露。&lt;/p&gt;
&lt;h3&gt;工作原理&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;资源定义&lt;/strong&gt;：在&lt;code&gt;try&lt;/code&gt;语句的括号内声明并初始化资源。这些资源必须实现&lt;code&gt;AutoCloseable&lt;/code&gt;接口。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;资源使用&lt;/strong&gt;：在&lt;code&gt;try&lt;/code&gt;代码块内使用这些资源。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;自动关闭&lt;/strong&gt;：无论&lt;code&gt;try&lt;/code&gt;代码块是正常结束还是因为异常而结束，都会自动调用资源的&lt;code&gt;close()&lt;/code&gt;方法来关闭资源。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;语法&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;try (ResourceType resource = resourceInitialization) {
    // 使用资源的代码
} catch (ExceptionType e) {
    // 处理异常的代码
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;示例&lt;/h3&gt;
&lt;p&gt;假设我们有一个文件&lt;code&gt;example.txt&lt;/code&gt;，我们想要读取它的内容并打印到控制台。使用&lt;code&gt;try-with-resources&lt;/code&gt;，我们可以确保文件在读取后会被正确关闭。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class TryWithResourcesExample {
    public static void main(String[] args) {
        // 使用try-with-resources自动管理BufferedReader资源
        try (BufferedReader br = new BufferedReader(new FileReader(&quot;example.txt&quot;))) {
            String line;
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            System.err.println(&quot;发生IO异常: &quot; + e.getMessage());
        }
        // 不需要显式关闭BufferedReader，try-with-resources会自动处理
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这个例子中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;BufferedReader&lt;/code&gt;是一个实现了&lt;code&gt;AutoCloseable&lt;/code&gt;接口的资源。&lt;/li&gt;
&lt;li&gt;我们在&lt;code&gt;try&lt;/code&gt;的括号内初始化了这个资源。&lt;/li&gt;
&lt;li&gt;在&lt;code&gt;try&lt;/code&gt;代码块内，我们读取文件的内容。&lt;/li&gt;
&lt;li&gt;无论读取过程中是否发生异常，&lt;code&gt;BufferedReader&lt;/code&gt;都会在&lt;code&gt;try&lt;/code&gt;代码块执行完毕后自动关闭。
这种方式简化了资源管理，使得代码更加简洁和健壮。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;ry-with-resources 处理多个资源&lt;/h3&gt;
&lt;p&gt;try-with-resources 语句中可以声明多个资源，方法是使用分号 &lt;strong&gt;;&lt;/strong&gt; 分隔各个资源：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import java.io.*;
import java.util.*;
class RunoobTest {
    public static void main(String[] args) throws IOException{
        try (Scanner scanner = new Scanner(new File(&quot;testRead.txt&quot;)); 
            PrintWriter writer = new PrintWriter(new File(&quot;testWrite.txt&quot;))) {
            while (scanner.hasNext()) {
                writer.print(scanner.nextLine());
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;以上实例使用 &lt;code&gt;Scanner&lt;/code&gt; 对象从 &lt;code&gt;testRead.txt&lt;/code&gt; 文件中读取一行并将其写入新的 &lt;code&gt;testWrite.txt&lt;/code&gt; 文件中。&lt;/p&gt;
&lt;p&gt;多个声明资源时，&lt;code&gt;try-with-resources&lt;/code&gt; 语句以相反的顺序关闭这些资源。 在本例中，&lt;code&gt;PrintWriter&lt;/code&gt; 对象先关闭，然后 &lt;code&gt;Scanner&lt;/code&gt; 对象关闭。&lt;/p&gt;
&lt;h2&gt;类实例对象监测&lt;/h2&gt;
&lt;p&gt;要实时检测某个类在当前程序中的实例数量，我们可以使用一个静态变量来跟踪实例化的次数。每次创建新实例时，我们增加这个计数器；每次实例被垃圾回收时，我们减少这个计数器。为了实现后者，我们可以使用Java的&lt;code&gt;finalize()&lt;/code&gt;方法，但是请注意，&lt;code&gt;finalize()&lt;/code&gt;方法不保证被及时调用，并且它的使用通常是不推荐的。从Java 9开始，&lt;code&gt;finalize()&lt;/code&gt;方法已被弃用。
一个更现代的方法是使用Java的&lt;code&gt;java.lang.ref.PhantomReference&lt;/code&gt;和&lt;code&gt;java.lang.ref.ReferenceQueue&lt;/code&gt;，这样可以更准确地跟踪对象的垃圾回收。
以下是一个使用&lt;code&gt;PhantomReference&lt;/code&gt;和&lt;code&gt;ReferenceQueue&lt;/code&gt;来实时检测类实例数量的示例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
public class InstanceTracker {
    private static int instanceCount = 0;
    private static ReferenceQueue&amp;lt;InstanceTracker&amp;gt; referenceQueue = new ReferenceQueue&amp;lt;&amp;gt;();
    private static PhantomReference&amp;lt;InstanceTracker&amp;gt; phantomReference;
    public InstanceTracker() {
        instanceCount++;
        phantomReference = new PhantomReference&amp;lt;&amp;gt;(this, referenceQueue);
    }
    @Override
    protected void finalize() throws Throwable {
        try {
            super.finalize();
        } finally {
            instanceCount--;
        }
    }
    public static int getInstanceCount() {
        // 清理已经被回收的实例
        while ((phantomReference = (PhantomReference&amp;lt;InstanceTracker&amp;gt;) referenceQueue.poll()) != null) {
            instanceCount--;
        }
        return instanceCount;
    }
    public static void main(String[] args) throws InterruptedException {
        System.out.println(&quot;Initial instance count: &quot; + InstanceTracker.getInstanceCount());
        InstanceTracker instance1 = new InstanceTracker();
        System.out.println(&quot;Instance count after creating instance1: &quot; + InstanceTracker.getInstanceCount());
        InstanceTracker instance2 = new InstanceTracker();
        System.out.println(&quot;Instance count after creating instance2: &quot; + InstanceTracker.getInstanceCount());
        // 强制垃圾回收
        instance1 = null;
        System.gc();
        Thread.sleep(100); // 等待垃圾回收完成
        System.out.println(&quot;Instance count after garbage collecting instance1: &quot; + InstanceTracker.getInstanceCount());
        instance2 = null;
        System.gc();
        Thread.sleep(100); // 等待垃圾回收完成
        System.out.println(&quot;Instance count after garbage collecting instance2: &quot; + InstanceTracker.getInstanceCount());
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这个示例中，我们定义了一个&lt;code&gt;InstanceTracker&lt;/code&gt;类，它有一个静态变量&lt;code&gt;instanceCount&lt;/code&gt;来跟踪实例的数量。每次创建&lt;code&gt;InstanceTracker&lt;/code&gt;的新实例时，&lt;code&gt;instanceCount&lt;/code&gt;都会增加。我们使用&lt;code&gt;PhantomReference&lt;/code&gt;和&lt;code&gt;ReferenceQueue&lt;/code&gt;来检测对象何时被垃圾回收，并在&lt;code&gt;getInstanceCount&lt;/code&gt;方法中更新&lt;code&gt;instanceCount&lt;/code&gt;。
请注意，这个示例中的&lt;code&gt;System.gc()&lt;/code&gt;和&lt;code&gt;Thread.sleep()&lt;/code&gt;是为了演示目的，强制垃圾回收并等待其完成。在实际应用中，不推荐依赖这些方法来管理内存，因为垃圾回收的行为是不可预测的。
此外，这个示例仅用于演示如何跟踪实例数量，并不推荐作为生产环境中的解决方案。在生产环境中，更好的做法是使用现有的库或框架来管理对象的生命周期和资源。&lt;/p&gt;
</content:encoded></item><item><title>Java String 类</title><link>http://zaoweiceng.github.io/posts/java/java_string/</link><guid isPermaLink="true">http://zaoweiceng.github.io/posts/java/java_string/</guid><description>Java学习之路</description><pubDate>Sat, 08 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;字符串广泛应用 在 Java 编程中，在 Java 中字符串属于对象，Java 提供了 String 类来创建和操作字符串。&lt;/p&gt;
&lt;h3&gt;String 基础用法&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;创建String&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;String a = &quot;hello world!&quot;;
String b = new String(&quot;hello world!&quot;);
char[] chs = {&apos;h&apos;, &apos;e&apos;, &apos;l&apos;, &apos;l&apos;, &apos;o&apos;};
String c = new String(chs);
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;获取字符串长度&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;int len = str.length();
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;连接字符串&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;string1.concat(string2); // 返回 string2 连接 string1 的新字符串。
&quot;hello,&quot; + &quot; world&quot; + &quot;!&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;常用API&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;char charAt(int index);&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;返回指定索引处的 char 值。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;int compareTo(String anotherString)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;字符串比较(字典序)。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;boolean startsWith(String prefix)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;测试此字符串是否以指定的前缀开始。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;boolean endsWith(String suffix)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;测试此字符串是否以指定的后缀结束。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;byte[] getBytes()&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;使用平台的默认字符集将此 String 编码为 byte 序列，并将结果存储到一个新的 byte 数组中。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;byte[] getBytes(String charsetName)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;使用指定的字符集将此 String 编码为 byte 序列，并将结果存储到一个新的 byte 数组中。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;int indexOf(int ch)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;返回指定字符在此字符串中第一次出现处的索引。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;int indexOf(int ch, int fromIndex)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;返回在此字符串中第一次出现指定字符处的索引，从指定的索引开始搜索。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;int indexOf(String str)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;返回指定子字符串在此字符串中第一次出现处的索引。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;int indexOf(String str,  int fromIndex)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;返回指定子字符串在此字符串中第一次出现处的索引。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;int length()&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;返回此字符串的长度。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;String replace(char oldChar, char newChar)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;返回一个新的字符串，它是通过用 newChar 替换此字符串中出现的所有 oldChar 得到的。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;String replaceAll(String regex, String replacement)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;使用给定的 replacement 替换此字符串所有匹配给定的正则表达式的子字符串。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;String[] split(String regex)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;根据给定正则表达式的匹配拆分此字符串。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;String substring(int beginIndex, int endIndex)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;返回一个新字符串，它是此字符串的一个子字符串。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;char[] toCharArray()&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;将此字符串转换为一个新的字符数组。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;String toLowerCase()&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;使用默认语言环境的规则将此 String 中的所有字符都转换为小写。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;String toUpperCase()&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;使用默认语言环境的规则将此 String 中的所有字符都转换为大写。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;String trim()&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;返回字符串的副本，忽略前导空白和尾部空白。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;isEmpty()&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;判断字符串是否为空。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;String 基础特性&lt;/h3&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;不可变性&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;Java的&lt;code&gt;String&lt;/code&gt;对象一旦创建，其内容不可修改。所有看似修改的操作（如拼接、替换）实际上是创建新的&lt;code&gt;String&lt;/code&gt;对象。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;String s = &quot;Hello&quot;;
s = s + &quot; World&quot;; // 创建新对象，原&quot;Hello&quot;仍存在内存中
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;字符串常量池&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;Java通过字符串池优化内存，相同字面量的字符串会复用池中的对象（通过&lt;code&gt;intern()&lt;/code&gt;方法可手动入池）。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;String s1 = &quot;abc&quot;; // 从池中获取
String s2 = new String(&quot;abc&quot;); // 强制创建新对象
System.out.println(s1 == s2); // false（比较对象地址）
System.out.println(s1.equals(s2)); // true（比较内容）
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;字符串比较&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;必须使用&lt;code&gt;equals()&lt;/code&gt;而非&lt;code&gt;==&lt;/code&gt;来比较内容，因为&lt;code&gt;==&lt;/code&gt;比较的是对象地址。&lt;/p&gt;
&lt;h3&gt;String 深入用法&lt;/h3&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;高效字符串操作&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;StringBuilder&lt;/strong&gt;与&lt;strong&gt;StringBuffer&lt;/strong&gt;
频繁修改字符串时，使用&lt;code&gt;StringBuilder&lt;/code&gt;（非线程安全）或&lt;code&gt;StringBuffer&lt;/code&gt;（线程安全）避免频繁创建新对象。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;pre&gt;&lt;code&gt;StringBuilder sb = new StringBuilder();
sb.append(&quot;Hello&quot;).append(&quot; World&quot;);
String result = sb.toString(); // 高效拼接
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;字符串分割与正则表达式&lt;/strong&gt;
使用&lt;code&gt;split()&lt;/code&gt;和正则表达式处理复杂字符串：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;pre&gt;&lt;code&gt;String str = &quot;a,b,c&quot;;
String[] parts = str.split(&quot;,&quot;); // [&quot;a&quot;, &quot;b&quot;, &quot;c&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;字符串格式化&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;String.format()&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;似C的&lt;code&gt;printf&lt;/code&gt;，支持格式化输出：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;pre&gt;&lt;code&gt;String formatted = String.format(&quot;Name: %s, Age: %d&quot;, &quot;Alice&quot;, 30);
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Java 15+ 的文本块（Text Blocks）&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;pre&gt;&lt;code&gt;String json = &quot;&quot;&quot;
              {
                  &quot;name&quot;: &quot;Alice&quot;,
                  &quot;age&quot;: 30
              }
              &quot;&quot;&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;编码与字节转换&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;pre&gt;&lt;code&gt;byte[] utf8Bytes = str.getBytes(StandardCharsets.UTF_8);
String decoded = new String(utf8Bytes, StandardCharsets.UTF_8);
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;与C++ std::string和Python str的对比&lt;/h3&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;不可变性&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Java：&lt;code&gt;String&lt;/code&gt;不可变，修改操作生成新对象。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;C++：&lt;code&gt;std::string&lt;/code&gt;是可变的，直接修改原对象。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;std::string s = &quot;Hello&quot;;
s += &quot; World&quot;; // 直接修改原对象&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
- Python：`str`不可变，类似Java，修改生成新对象。

&amp;gt; 2. 内存管理

- Java：依赖JVM垃圾回收，字符串池优化内存。
- C++：手动管理内存（RAII），`std::string`内部使用动态数组。
- Python：引用计数和垃圾回收，字符串驻留（类似池化）优化。

&amp;gt; **3. **常用操作

- 拼接效率
- Java：推荐`StringBuilder`（`O(n)`复杂度）。
- C++：直接使用`+=`或`append()`（动态数组扩容，高效）。
- Python：推荐`join()`（高效处理列表拼接）。
- 切片与索引
- Java：需用`substring(begin, end)`。
- C++：`substr(pos, length)`。
- Python：直接使用`s[start:end]`语法，最简洁。

&amp;gt; 4. Unicode支持

- Java：`String`内部为UTF-16，需注意代理对（Surrogate Pairs）。
- Python3：`str`默认支持Unicode，透明处理多语言文本。&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Java学习笔记（2）</title><link>http://zaoweiceng.github.io/posts/java/java%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0_2/</link><guid isPermaLink="true">http://zaoweiceng.github.io/posts/java/java%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0_2/</guid><description>Java学习之路</description><pubDate>Sat, 08 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;可变参数&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Java的函数支持可变参数&lt;/strong&gt;。在Java中，可变参数允许在调用方法时传入不定数量的参数。这为方法提供了一种灵活的方式来处理不同数量的输入。&lt;/p&gt;
&lt;p&gt;要定义一个可变参数方法，只需在参数类型后加上省略号（…）。例如，以下是一个简单的可变参数方法的示例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class VarargsExample {
    public static void main(String[] args) {
        // 调用可变参数方法
        printNumbers(1, 2, 3, 4, 5);
        printNumbers(10, 20);
    }
    // 定义可变参数方法
    public static void printNumbers(int... numbers) {
        for (int number : numbers) {
            System.out.println(number);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这个示例中，&lt;code&gt;printNumbers&lt;/code&gt;方法可以接受任意数量的&lt;code&gt;int&lt;/code&gt;类型参数。当调用这个方法时，可以传入任意数量的整数，方法会遍历并打印每个数字。&lt;/p&gt;
&lt;p&gt;需要注意的是，可变参数必须是方法参数列表中的最后一个参数，并且一个方法只能有一个可变参数。如果方法还有其他参数，它们必须位于可变参数之前。&lt;/p&gt;
&lt;p&gt;此外，可变参数在内部实际上是一个数组，因此可以像数组一样进行操作。但是，为了提高代码的可读性和清晰度，建议在需要处理不定数量参数的情况下使用可变参数。&lt;/p&gt;
&lt;h2&gt;Java类内实例化与循环引用分析&lt;/h2&gt;
&lt;h4&gt;静态方法实例化类&lt;/h4&gt;
&lt;p&gt;Java中类的静态方法（比如main）属于类本身，而不是实例。在静态方法中，可以创建该类的实例，这没有问题。比如常见的public static void main里，通常会创建自己类的实例来启动程序。所以这并不会导致循环引用，因为这只是创建一个实例，而不会无限递归。比如:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class Test {
    public static void main(String[] args) {
    	Test t = new Test(); // 这是合法的，不会导致循环
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这时候，每次运行main方法会创建一个Test实例，但如果没有在构造函数里再次创建Test实例的话，就不会有问题。&lt;/p&gt;
&lt;h4&gt;循环引用&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;循环引用的定义&lt;/strong&gt;：循环引用指两个或多个对象&lt;strong&gt;互相持有引用&lt;/strong&gt;，导致无法被垃圾回收。例如：&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;class A { B b; }
class B { A a; }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;实际上，只有在构造函数中不加控制地创建自身实例只会导致栈溢出。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class Test {
    public Test() {
    	new Test(); // 每次构造都会创建新的实例，导致无限递归，栈溢出
    }
    public static void main(String[] args) {
    	new Test();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;同时，对象之间的相互引用，比如A引用B，B引用A，这样可能导致内存泄漏。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class reference {
    private class Node{
        int a, b, c;
        Node(int a, int b, int c){
            this.a = a;
            this.b = b;
            this.c = c;
        }
        public String toString(){
            return &quot;a: &quot; + a + &quot; b: &quot; + b + &quot; c: &quot; + c;
        }
    }
    public void change(Node node){
        node.a = 1;
        node.b = 2;
        node.c = 3;
    }
    public static void main(String[] args) {
        reference outer = new reference();
        Node node = outer.new Node(0, 0, 0);
        System.out.println(node);
        outer.change(node);
        System.out.println(node);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这段代码仅存在&lt;strong&gt;单向引用&lt;/strong&gt;（&lt;code&gt;node&lt;/code&gt;→&lt;code&gt;outer&lt;/code&gt;），而非&lt;code&gt;reference&lt;/code&gt;与&lt;code&gt;Node&lt;/code&gt;互相引用。因此不存在循环引用。&lt;/p&gt;
&lt;p&gt;非静态内部类在编译后会生成一个包含外部类引用的构造器。例如，&lt;code&gt;Node&lt;/code&gt;类会被编译为类似以下形式：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class reference$Node {
    final reference outer; // 隐式持有外部类实例
    int a, b, c;
    
    reference$Node(reference outer, int a, int b, int c) {
        this.outer = outer;
        this.a = a;
        // ...
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;当你通过&lt;code&gt;outer.new Node(...)&lt;/code&gt;创建&lt;code&gt;Node&lt;/code&gt;实例时，实际调用的是上述构造器，将&lt;code&gt;outer&lt;/code&gt;作为参数传入。&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Java学习笔记（1）</title><link>http://zaoweiceng.github.io/posts/java/java%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0_1/</link><guid isPermaLink="true">http://zaoweiceng.github.io/posts/java/java%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0_1/</guid><description>Java学习之路</description><pubDate>Fri, 07 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;default关键字&lt;/h3&gt;
&lt;p&gt;像其他语言一样，Java可以使用修饰符来修饰类中方法和属性。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;访问控制修饰符 : default, public , protected, private&lt;/li&gt;
&lt;li&gt;非访问控制修饰符 : final, abstract, static, synchronized&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在Java中，&lt;code&gt;default&lt;/code&gt; 关键词有两个主要的用途：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;默认访问修饰符&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;当一个类、方法或变量没有明确指定访问修饰符（如&lt;code&gt;public&lt;/code&gt;、&lt;code&gt;protected&lt;/code&gt;、&lt;code&gt;private&lt;/code&gt;）时，它默认具有包级私有（package-private）访问权限。这意味着只有同一个包内的类可以访问这个类、方法或变量。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;接口中的默认方法&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;从Java 8开始，接口中可以包含默认方法（default methods）。这些方法是带有具体实现的方法，使用&lt;code&gt;default&lt;/code&gt;关键字修饰。默认方法允许在接口中提供方法的实现，这样现有的实现类不需要修改就可以使用新添加的方法。这为接口的扩展提供了更大的灵活性。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;// 默认访问修饰符示例
class DefaultExample {
    int number; // 默认访问权限，同一包内的类可以访问

    void display() { // 默认访问权限，同一包内的类可以访问
        System.out.println(&quot;Number: &quot; + number);
    }
}

// 接口中的默认方法示例
interface MyInterface {
    void method1(); // 抽象方法

    default void method2() { // 默认方法
        System.out.println(&quot;Default method implementation&quot;);
    }
}

class ImplementationClass implements MyInterface {
    public void method1() {
        System.out.println(&quot;Implementation of method1&quot;);
    }
}

public class Main {
    public static void main(String[] args) {
        ImplementationClass obj = new ImplementationClass();
        obj.method1(); // 调用实现的方法
        obj.method2(); // 调用接口中的默认方法
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这个示例中，&lt;code&gt;DefaultExample&lt;/code&gt;类没有明确指定访问修饰符，因此它的成员具有包级私有访问权限。&lt;code&gt;MyInterface&lt;/code&gt;接口包含一个默认方法&lt;code&gt;method2&lt;/code&gt;，&lt;code&gt;ImplementationClass&lt;/code&gt;实现了这个接口，并可以直接使用&lt;code&gt;method2&lt;/code&gt; 而不用提供实现。&lt;/p&gt;
&lt;h3&gt;枚举&lt;/h3&gt;
&lt;p&gt;Java 5.0引入了枚举，枚举限制变量只能是预先设定好的值。使用枚举可以减少代码中的 bug。&lt;/p&gt;
&lt;p&gt;例如，我们为果汁店设计一个程序，它将限制果汁为小杯、中杯、大杯。这就意味着它不允许顾客点除了这三种尺寸外的果汁。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class FreshJuice {
   enum FreshJuiceSize{ SMALL, MEDIUM , LARGE }
   FreshJuiceSize size;
}
 
public class FreshJuiceTest {
   public static void main(String[] args){
      FreshJuice juice = new FreshJuice();
      juice.size = FreshJuice.FreshJuiceSize.MEDIUM  ;
   }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;**注意：**枚举可以单独声明或者声明在类里面。方法、变量、构造函数也可以在枚举中定义。&lt;/p&gt;
&lt;h3&gt;数组存在于堆上&lt;/h3&gt;
&lt;p&gt;Java中的数组作为对象，确实是在堆上存储的，而指向这些数组的引用则存储在栈上。在Java中，所有的对象，包括数组，都是在堆上分配内存的。这是因为对象通常需要较大的内存空间，并且生命周期较长，因此将它们存储在堆上可以更有效地管理内存。
当你在Java中创建一个数组时，例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int[] myArray = new int[10];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里，&lt;code&gt;myArray&lt;/code&gt;是一个引用变量，它存储在栈上，而实际的数组对象（包含10个整数元素）是在堆上分配的。&lt;code&gt;myArray&lt;/code&gt;变量持有的只是这个堆上数组对象的引用（或地址）。
这种设计允许Java具有更灵活的内存管理，并支持复杂的数据结构和动态内存分配。但是，这也意味着开发者需要负责管理对象的创建和销毁，以避免内存泄漏和其他内存相关的问题。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Java具有自动垃圾回收机制，可以自动回收不再被引用的对象所占用的空间&lt;/strong&gt;。当对象不再被任何引用所指向时，垃圾回收器会在适当的时候回收这些对象所占用的内存。
然而，尽管Java提供了自动垃圾回收，开发者仍然需要谨慎地管理对象的引用，以避免不必要的内存占用和潜在的内存泄漏。例如，如果对象之间存在循环引用，且这些对象不再被外部引用，垃圾回收器可能无法回收它们，从而导致内存泄漏。
此外，垃圾回收器的具体行为和效率也受到多种因素的影响，包括Java虚拟机的实现、垃圾回收算法的选择、系统的内存压力等。因此，尽管Java提供了自动垃圾回收，开发者仍然需要关注内存管理和性能优化。Java的自动垃圾回收机制确实可以自动回收不再被引用的对象所占用的空间，但开发者仍需谨慎管理对象引用并关注内存使用情况。&lt;/p&gt;
&lt;h3&gt;抽象类和接口类&lt;/h3&gt;
&lt;p&gt;在Java中，抽象类（Abstract Class）和接口（Interface）都是用来实现抽象化的重要机制，但它们之间有一些关键的区别：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;定义&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;抽象类&lt;/strong&gt;：是一种不能被实例化的类，可以包含具体实现的方法和抽象方法（没有方法体的方法）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;接口&lt;/strong&gt;：是一种完全抽象的类，只能包含抽象方法和默认方法（Java 8+），以及常量。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;继承与实现&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;抽象类&lt;/strong&gt;：一个类只能继承一个抽象类，因为Java不支持多重继承。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;接口&lt;/strong&gt;：一个类可以实现多个接口，提供多继承的效果。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;成员&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;抽象类&lt;/strong&gt;：可以包含构造器、非final的变量、具体实现的方法、抽象方法、静态方法、初始化块等。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;接口&lt;/strong&gt;：只能包含静态常量、抽象方法、默认方法（Java 8+）、静态方法（Java 9+）。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;访问修饰符&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;抽象类&lt;/strong&gt;：可以有任何访问修饰符（public、protected、private）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;接口&lt;/strong&gt;：默认是public的，成员变量默认是public static final的，方法默认是public的。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;使用场景&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;抽象类&lt;/strong&gt;：当多个类共享一些共同的方法和属性时，可以使用抽象类来定义这些共同的特性。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;接口&lt;/strong&gt;：用于定义公共的方法规范，实现类可以以任何方式实现这些方法。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;扩展性&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;抽象类&lt;/strong&gt;：扩展性有限，因为子类只能继承一个抽象类。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;接口&lt;/strong&gt;：提供更高的扩展性，因为一个类可以实现多个接口。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;默认实现&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;抽象类&lt;/strong&gt;：可以提供某些方法的具体实现，子类可以选择性地覆盖这些方法。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;接口&lt;/strong&gt;：在Java 8之前不能提供方法的实现，Java 8引入了默认方法，允许接口提供方法的默认实现。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;多态性&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;抽象类&lt;/strong&gt;：可以通过抽象类引用指向子类对象来实现多态。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;接口&lt;/strong&gt;：可以通过接口引用指向实现类对象来实现多态。
总的来说，抽象类更适合于那些部分实现或共享代码的场景，而接口则适用于定义完全抽象的API，使得实现类可以以多种方式实现这些API。选择使用抽象类还是接口，取决于具体的设计需求和场景。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;instanceof&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;instanceof&lt;/code&gt; 是 Java 语言中的一个二元操作符，用于测试左边的对象是否是右边类或接口的实例，返回 boolean 类型的结果。如果左边的对象是右边类或接口的实例，或者是右边类的子类实例，那么 &lt;code&gt;instanceof&lt;/code&gt; 操作符返回 true，否则返回 false。
其基本语法如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;object instanceof Class
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;object&lt;/code&gt;：要测试的对象。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Class&lt;/code&gt;：类或接口的类型。
&lt;code&gt;instanceof&lt;/code&gt; 操作符的主要用途包括：&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;类型检查&lt;/strong&gt;：在运行时确定对象的类型，以便进行相应的操作。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;向下转型&lt;/strong&gt;：在向下转型（将父类对象转换为子类对象）之前，使用 &lt;code&gt;instanceof&lt;/code&gt; 检查可以避免 &lt;code&gt;ClassCastException&lt;/code&gt;。
示例：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;class Animal {}
class Dog extends Animal {}
public class Test {
    public static void main(String[] args) {
        Animal myAnimal = new Dog();
        if (myAnimal instanceof Dog) {
            Dog myDog = (Dog) myAnimal; // 安全的向下转型
            System.out.println(&quot;This is a Dog.&quot;);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这个例子中，&lt;code&gt;myAnimal instanceof Dog&lt;/code&gt; 检查 &lt;code&gt;myAnimal&lt;/code&gt; 是否是 &lt;code&gt;Dog&lt;/code&gt; 类的实例，因为 &lt;code&gt;myAnimal&lt;/code&gt; 实际上指向的是一个 &lt;code&gt;Dog&lt;/code&gt; 对象，所以这个表达式返回 true，随后可以安全地进行向下转型。
需要注意的是，&lt;code&gt;instanceof&lt;/code&gt; 操作符只能用于对象类型，不能用于基本数据类型。例如，以下代码是错误的：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int num = 5;
if (num instanceof Integer) { // 错误，基本数据类型不能使用 instanceof
    // ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在使用 &lt;code&gt;instanceof&lt;/code&gt; 时，还应该注意避免与过度的类型检查，这可能会使代码变得冗余和难以维护。在设计类和接口时，应尽量使用多态和接口来减少对 &lt;code&gt;instanceof&lt;/code&gt; 的依赖。&lt;/p&gt;
</content:encoded></item><item><title>Go语言中encoding的用法——Binary</title><link>http://zaoweiceng.github.io/posts/go/go%E8%AF%AD%E8%A8%80%E4%B8%ADencoding%E7%9A%84%E7%94%A8%E6%B3%95/</link><guid isPermaLink="true">http://zaoweiceng.github.io/posts/go/go%E8%AF%AD%E8%A8%80%E4%B8%ADencoding%E7%9A%84%E7%94%A8%E6%B3%95/</guid><description>encoding/binary是Go语言标准库中的一个包，用于实现数据的二进制编码和解码。这个包主要提供了将数据以大端或小端格式进行编码和解码的功能。</description><pubDate>Wed, 12 Feb 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;code&gt;encoding/binary&lt;/code&gt;是Go语言标准库中的一个包，用于实现数据的二进制编码和解码。这个包主要提供了将数据以大端或小端格式进行编码和解码的功能。&lt;/p&gt;
&lt;h3&gt;1. &lt;code&gt;binary.BigEndian&lt;/code&gt; 和 &lt;code&gt;binary.LittleEndian&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;这两个变量是&lt;code&gt;binary&lt;/code&gt;包提供的两个实现了&lt;code&gt;binary.ByteOrder&lt;/code&gt;接口的结构体，分别用于表示大端和小端字节序。&lt;/p&gt;
&lt;h3&gt;2. &lt;code&gt;Read&lt;/code&gt; 方法&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Read&lt;/code&gt;方法用于从字节切片中读取指定类型的数据。例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var data []byte = []byte{0x01, 0x02, 0x03, 0x04}
var value uint32
err := binary.BigEndian.Read(data, &amp;amp;value)
if err != nil {
    log.Fatal(err)
}
fmt.Println(value) // 输出: 16909060
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. &lt;code&gt;Write&lt;/code&gt; 方法&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Write&lt;/code&gt;方法用于将数据写入到字节切片中。例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var buffer []byte = make([]byte, 4)
var value uint32 = 16909060
err := binary.BigEndian.Write(buffer, value)
if err != nil {
    log.Fatal(err)
}
fmt.Println(buffer) // 输出: [1 2 3 4]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4. &lt;code&gt;Uvarint&lt;/code&gt; 和 &lt;code&gt;Varint&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;这两个函数用于处理变长整数（variable-length integers）。&lt;code&gt;Uvarint&lt;/code&gt;用于解码一个无符号的变长整数，&lt;code&gt;Varint&lt;/code&gt;用于解码一个有符号的变长整数。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;buf := []byte{0x96, 0x01}
value, n := binary.Uvarint(buf)
if n &amp;lt;= 0 {
    log.Fatal(&quot;Uvarint error&quot;)
}
fmt.Println(value) // 输出: 150
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5. &lt;code&gt;PutUvarint&lt;/code&gt; 和 &lt;code&gt;PutVarint&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;这两个函数用于将变长整数编码为字节切片。&lt;code&gt;PutUvarint&lt;/code&gt;用于无符号整数，&lt;code&gt;PutVarint&lt;/code&gt;用于有符号整数。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;buf := make([]byte, 10)
n := binary.PutUvarint(buf, 150)
fmt.Println(buf[:n]) // 输出: [150 1]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;注意事项&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;在使用&lt;code&gt;Read&lt;/code&gt;和&lt;code&gt;Write&lt;/code&gt;方法时，需要确保提供的字节切片长度足够。&lt;/li&gt;
&lt;li&gt;变长整数的编码和解码在处理大量数据时可以节省空间，但可能会增加编码和解码的复杂性。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;实际应用&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;encoding/binary&lt;/code&gt;包通常用于网络通信、文件存储等需要处理二进制数据的场景。例如，在实现自定义协议时，可以使用这个包来编码和解码消息头或消息体。
以上是&lt;code&gt;encoding/binary&lt;/code&gt;包的一些基本用法。在实际使用中，还需要根据具体的需求来选择合适的函数和方法。&lt;/p&gt;
&lt;p&gt;在Go语言中，没有内置的方法可以自动将任意结构体转换为二进制格式。但是，你可以使用&lt;code&gt;encoding/binary&lt;/code&gt;包中的函数手动实现这一功能。为了方便地将结构体转换为二进制，你通常需要确保结构体的字段是固定大小的基本数据类型，并且使用&lt;code&gt;binary&lt;/code&gt;包中的&lt;code&gt;Read&lt;/code&gt;和&lt;code&gt;Write&lt;/code&gt;方法。
下面是一个示例，展示如何定义一个结构体并将其转换为二进制数据：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;package main
import (
	&quot;bytes&quot;
	&quot;encoding/binary&quot;
	&quot;fmt&quot;
	&quot;io&quot;
)
// 定义一个简单的结构体
type MyStruct struct {
	Field1 uint32
	Field2 int16
	Field3 bool
}
// 将结构体转换为二进制数据
func structToBinary(data interface{}) ([]byte, error) {
	buf := new(bytes.Buffer)
	err := binary.Write(buf, binary.BigEndian, data)
	if err != nil {
		return nil, err
	}
	return buf.Bytes(), nil
}
// 从二进制数据解析回结构体
func binaryToStruct(data []byte, obj interface{}) error {
	buf := bytes.NewReader(data)
	return binary.Read(buf, binary.BigEndian, obj)
}
func main() {
	// 创建结构体实例
	myData := MyStruct{
		Field1: 123456,
		Field2: -123,
		Field3: true,
	}
	// 转换为二进制
	binaryData, err := structToBinary(&amp;amp;myData)
	if err != nil {
		fmt.Println(&quot;Error converting to binary:&quot;, err)
		return
	}
	fmt.Println(&quot;Binary data:&quot;, binaryData)
	// 解析回结构体
	var myDataCopy MyStruct
	err = binaryToStruct(binaryData, &amp;amp;myDataCopy)
	if err != nil {
		fmt.Println(&quot;Error converting from binary:&quot;, err)
		return
	}
	fmt.Printf(&quot;Parsed struct: %+v\n&quot;, myDataCopy)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这个示例中，我们定义了一个&lt;code&gt;MyStruct&lt;/code&gt;结构体，并实现了两个函数&lt;code&gt;structToBinary&lt;/code&gt;和&lt;code&gt;binaryToStruct&lt;/code&gt;来处理结构体与二进制数据之间的转换。我们使用&lt;code&gt;binary.Write&lt;/code&gt;来将结构体数据写入一个&lt;code&gt;bytes.Buffer&lt;/code&gt;，然后将其转换为字节切片。同样，我们使用&lt;code&gt;binary.Read&lt;/code&gt;来从字节切片中读取数据并填充到结构体中。
请注意，为了使这个示例工作，结构体的所有字段都必须是导出的（即首字母大写），因为&lt;code&gt;binary.Write&lt;/code&gt;和&lt;code&gt;binary.Read&lt;/code&gt;函数需要访问这些字段。此外，结构体中的字段类型应该是&lt;code&gt;binary&lt;/code&gt;包能够处理的类型。
如果你想要更高级的序列化功能，比如支持更复杂的数据结构或自动处理字段标签，你可能需要使用像&lt;code&gt;protobuf&lt;/code&gt;、&lt;code&gt;msgpack&lt;/code&gt;或其他序列化库。这些库提供了更复杂的序列化机制，但需要你定义数据结构的方式符合它们的规范。&lt;/p&gt;
</content:encoded></item><item><title>Go语言中结构体标签（tag）的用法</title><link>http://zaoweiceng.github.io/posts/go/go%E8%AF%AD%E8%A8%80%E4%B8%AD%E7%BB%93%E6%9E%84%E4%BD%93%E6%A0%87%E7%AD%BEtag%E7%9A%84%E7%94%A8%E6%B3%95/</link><guid isPermaLink="true">http://zaoweiceng.github.io/posts/go/go%E8%AF%AD%E8%A8%80%E4%B8%AD%E7%BB%93%E6%9E%84%E4%BD%93%E6%A0%87%E7%AD%BEtag%E7%9A%84%E7%94%A8%E6%B3%95/</guid><description>结构体标签（tag）是Go语言中一种为结构体字段提供额外元数据的机制。它们通常用于JSON序列化/反序列化、数据库映射等场景。</description><pubDate>Sat, 08 Feb 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;结构体标签（tag）是Go语言中一种为结构体字段提供额外元数据的机制。它们通常用于JSON序列化/反序列化、数据库映射等场景。下面将详细讲解结构体标签的用法：&lt;/p&gt;
&lt;h3&gt;1. 基本用法&lt;/h3&gt;
&lt;p&gt;结构体标签以字符串的形式紧跟在字段类型后面，用反引号&lt;code&gt;`&lt;/code&gt;括起来。标签内部可以包含多个键值对，键和值之间用冒号&lt;code&gt;:&lt;/code&gt;分隔，多个键值对之间用空格分隔。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;type Person struct {
    Name string `json:&quot;name&quot;`
    Age  int    `json:&quot;age&quot;`
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这个例子中，&lt;code&gt;json:&quot;name&quot;&lt;/code&gt;和&lt;code&gt;json:&quot;age&quot;&lt;/code&gt;是结构体标签。它们指定了在JSON序列化/反序列化时，&lt;code&gt;Name&lt;/code&gt;字段对应JSON中的&lt;code&gt;name&lt;/code&gt;键，&lt;code&gt;Age&lt;/code&gt;字段对应JSON中的&lt;code&gt;age&lt;/code&gt;键。&lt;/p&gt;
&lt;h3&gt;2. 多个标签&lt;/h3&gt;
&lt;p&gt;一个字段可以拥有多个标签，用于不同的场景。例如，同时为JSON和XML序列化提供标签：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;type Person struct {
    Name string `json:&quot;name&quot; xml:&quot;name&quot;`
    Age  int    `json:&quot;age&quot; xml:&quot;age&quot;`
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. 标签键的命名&lt;/h3&gt;
&lt;p&gt;标签键的命名通常是场景或库特定的。例如，&lt;code&gt;json&lt;/code&gt;键用于JSON处理，&lt;code&gt;bson&lt;/code&gt;键用于MongoDB的BSON格式，&lt;code&gt;xml&lt;/code&gt;键用于XML处理等。&lt;/p&gt;
&lt;h3&gt;4. 标签值的选项&lt;/h3&gt;
&lt;p&gt;标签值可以包含多个选项，用逗号&lt;code&gt;,&lt;/code&gt;分隔。例如，在JSON标签中，可以使用&lt;code&gt;omitempty&lt;/code&gt;选项来指示空值不应被序列化：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;type Person struct {
    Name string `json:&quot;name&quot;`
    Age  int    `json:&quot;age,omitempty&quot;` // 如果Age为0，则不会被序列化
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其他常见选项包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;omitempty&lt;/code&gt;：空值不序列化。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;string&lt;/code&gt;：强制将整数值序列化为字符串（用于JSON）。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;inline&lt;/code&gt;：内联嵌套结构体的字段（用于JSON）。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;5. 忽略字段&lt;/h3&gt;
&lt;p&gt;如果想要在序列化/反序列化时忽略某个字段，可以在标签中使用&lt;code&gt;-&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;type Person struct {
    Name string `json:&quot;name&quot;`
    Age  int    `json:&quot;age&quot;`
    Ignored string `json:&quot;-&quot;` // 该字段将被忽略
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;6. 自定义标签&lt;/h3&gt;
&lt;p&gt;除了标准库支持的标签外，还可以自定义标签，用于特定的库或场景：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;type Person struct {
    Name string `mytag:&quot;name&quot;`
    Age  int    `mytag:&quot;age&quot;`
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这里，&lt;code&gt;mytag&lt;/code&gt;是一个自定义的标签键，可以根据需要自行解析和使用。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;package main

import (
	&quot;fmt&quot;
	&quot;reflect&quot;
	&quot;strings&quot;
)

type Person struct {
	Name string `mytag:&quot;name&quot;`
	Age  int    `mytag:&quot;age&quot;`
}

func parseTags(v interface{}) {
	val := reflect.ValueOf(v)
	typ := val.Type()

	for i := 0; i &amp;lt; val.NumField(); i++ {
		field := val.Field(i)
		fieldType := typ.Field(i)
		tag := fieldType.Tag.Get(&quot;mytag&quot;)

		if tag != &quot;&quot; {
			fmt.Printf(&quot;Field: %s, MyTag: %s\n&quot;, fieldType.Name, tag)
		}
	}
}

func main() {
	p := Person{Name: &quot;Alice&quot;, Age: 30}
	parseTags(p)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;code&gt;v interface{}&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;表示一个空接口，它是 Go 语言中所有类型的超集。任何类型都可以赋值给空接口。通常用于函数参数，允许函数接受任何类型的参数。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func printType(v interface{}) {
    fmt.Println(reflect.TypeOf(v))
}

func main() {
    printType(42)      // 输出：int
    printType(&quot;hello&quot;) // 输出：string
}

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;code&gt;reflect.ValueOf(v)&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;反射包中的一个函数，用于获取传入参数 &lt;code&gt;v&lt;/code&gt; 的反射值对象（&lt;code&gt;reflect.Value&lt;/code&gt;）。这个对象包含了 &lt;code&gt;v&lt;/code&gt; 的值和类型信息。通过 &lt;code&gt;reflect.Value&lt;/code&gt; 对象，可以访问和操作原始值，包括获取类型信息、字段、方法等。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;常用方法&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Type()&lt;/code&gt;：返回值的类型信息（&lt;code&gt;reflect.Type&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Kind()&lt;/code&gt;：返回值的类型种类，如 &lt;code&gt;int&lt;/code&gt;, &lt;code&gt;string&lt;/code&gt;, &lt;code&gt;struct&lt;/code&gt; 等。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Field(i int)&lt;/code&gt;：获取结构体的第 &lt;code&gt;i&lt;/code&gt; 个字段。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Method(i int)&lt;/code&gt;：获取方法的第 &lt;code&gt;i&lt;/code&gt; 个方法。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Interface()&lt;/code&gt;：将 &lt;code&gt;reflect.Value&lt;/code&gt; 转换回空接口，可以再断言回原始类型。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;import (
    &quot;fmt&quot;
    &quot;reflect&quot;
)

type Person struct {
    Name string
    Age  int
}

func main() {
    p := Person{Name: &quot;Alice&quot;, Age: 30}
    v := reflect.ValueOf(p)

    fmt.Println(v.Type())          // 输出：main.Person
    fmt.Println(v.Kind())          // 输出：struct

    for i := 0; i &amp;lt; v.NumField(); i++ {
        field := v.Field(i)
        fmt.Printf(&quot;Field %d: %v\n&quot;, i, field.Interface())
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;性能&lt;/strong&gt;：反射通常比直接访问慢，因为它是运行时动态解析的。应避免在性能敏感的代码路径中使用反射。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;类型断言&lt;/strong&gt;：从 &lt;code&gt;reflect.Value&lt;/code&gt; 转换回原始类型时，通常需要使用类型断言。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;安全性&lt;/strong&gt;：反射可以绕过编译时的类型检查，可能导致运行时错误。使用时需谨慎。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;7. 结构体标签的解析&lt;/h3&gt;
&lt;p&gt;结构体标签的解析通常由相应的库完成。例如，&lt;code&gt;encoding/json&lt;/code&gt;库会解析&lt;code&gt;json&lt;/code&gt;标签，&lt;code&gt;encoding/xml&lt;/code&gt;库会解析&lt;code&gt;xml&lt;/code&gt;标签。自定义标签则需要自己编写解析逻辑。&lt;/p&gt;
&lt;h3&gt;示例：使用结构体标签进行JSON序列化&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;package main
import (
	&quot;encoding/json&quot;
	&quot;fmt&quot;
)
type Person struct {
	Name string `json:&quot;name&quot;`
	Age  int    `json:&quot;age,omitempty&quot;`
}
func main() {
	p := Person{Name: &quot;Alice&quot;, Age: 0}
	jsonData, err := json.Marshal(p)
	if err != nil {
		fmt.Println(&quot;Error marshalling JSON:&quot;, err)
		return
	}
	fmt.Println(string(jsonData)) // 输出：{&quot;name&quot;:&quot;Alice&quot;}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这个示例中，由于&lt;code&gt;Age&lt;/code&gt;字段使用了&lt;code&gt;omitempty&lt;/code&gt;选项，并且其值为0（在Go中，0被视为itempty的空值），所以在序列化后的JSON中，&lt;code&gt;age&lt;/code&gt;键被省略了。&lt;/p&gt;
&lt;h3&gt;示例&lt;/h3&gt;
&lt;p&gt;假设有以下复杂的JSON结构，其中包含重名字段：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;user&quot;: {
    &quot;name&quot;: &quot;Alice&quot;,
    &quot;email&quot;: &quot;alice@example.com&quot;,
    &quot;profile&quot;: {
      &quot;name&quot;: &quot;Alice Profile&quot;, // 重名字段
      &quot;age&quot;: 30
    },
    &quot;contacts&quot;: [
      {&quot;type&quot;: &quot;home&quot;, &quot;number&quot;: &quot;123-456-7890&quot;},
      {&quot;type&quot;: &quot;work&quot;, &quot;number&quot;: &quot;987-654-3210&quot;}
    ]
  },
  &quot;settings&quot;: {
    &quot;notifications&quot;: true,
    &quot;theme&quot;: &quot;dark&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对应的Go结构体可能如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;package main
import (
	&quot;encoding/json&quot;
	&quot;fmt&quot;
)
type User struct {
	Name    string   `json:&quot;name&quot;`
	Email   string   `json:&quot;email&quot;`
	Profile Profile  `json:&quot;profile&quot;`
	Contacts []Contact `json:&quot;contacts&quot;`
}
type Profile struct {
	Name string `json:&quot;name&quot;` // 使用相同的标签，但属于不同的结构体
	Age  int    `json:&quot;age&quot;`
}
type Contact struct {
	Type   string `json:&quot;type&quot;`
	Number string `json:&quot;number&quot;`
}
type Settings struct {
	Notifications bool   `json:&quot;notifications&quot;`
	Theme         string `json:&quot;theme&quot;`
}
type RequestBody struct {
	User     User     `json:&quot;user&quot;`
	Settings Settings `json:&quot;settings&quot;`
}
func main() {
	// 示例：序列化和反序列化
	// ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这个例子中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;User&lt;/code&gt;和&lt;code&gt;Profile&lt;/code&gt;结构体都有一个名为&lt;code&gt;Name&lt;/code&gt;的字段，但由于它们属于不同的结构体，所以不会冲突。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Contacts&lt;/code&gt;是一个切片，表示JSON中的数组。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Settings&lt;/code&gt;是一个独立的结构体，表示JSON中的另一个对象。
如果结构体中确实需要处理重名字段，并且这些字段在同一个结构体中，那么可以使用不同的标签别名来区分它们：&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;type Example struct {
	Name  string `json:&quot;name&quot;`
	Name2 string `json:&quot;name&quot;` // 这将导致错误，因为标签相同
}
// 应该改为：
type Example struct {
	Name  string `json:&quot;name&quot;`
	Name2 string `json:&quot;name2&quot;` // 使用不同的标签别名
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在反序列化时，JSON处理包会根据标签来决定哪个字段对应JSON中的哪个键。如果出现标签相同的情况，会导致反序列化失败，因为Go不允许同一个结构体中有相同的标签。&lt;/p&gt;
</content:encoded></item><item><title>Git基本使用方法</title><link>http://zaoweiceng.github.io/posts/git/git_usage/</link><guid isPermaLink="true">http://zaoweiceng.github.io/posts/git/git_usage/</guid><description>Git基本使用方法</description><pubDate>Thu, 09 Jan 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;初始化Git仓库&lt;/h2&gt;
&lt;p&gt;初始化一个Git仓库，使用&lt;code&gt;git init&lt;/code&gt;命令。&lt;/p&gt;
&lt;p&gt;添加文件到Git仓库，分两步：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;使用命令&lt;code&gt;git add &amp;lt;file&amp;gt;&lt;/code&gt;，注意，可反复多次使用，添加多个文件；&lt;/li&gt;
&lt;li&gt;使用命令&lt;code&gt;git commit -m &amp;lt;message&amp;gt;&lt;/code&gt;，完成。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;工作区&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;要随时掌握工作区的状态，使用&lt;code&gt;git status&lt;/code&gt;命令。&lt;/li&gt;
&lt;li&gt;如果&lt;code&gt;git status&lt;/code&gt;告诉你有文件被修改过，用&lt;code&gt;git diff&lt;/code&gt;可以查看修改内容。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;版本回退&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;HEAD&lt;/code&gt;指向的版本就是当前版本，因此，Git允许我们在版本的历史之间穿梭，使用命令&lt;code&gt;git reset --hard commit_id&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;穿梭前，用&lt;code&gt;git log&lt;/code&gt;可以查看提交历史，以便确定要回退到哪个版本。&lt;/li&gt;
&lt;li&gt;要重返未来，用&lt;code&gt;git reflog&lt;/code&gt;查看命令历史，以便确定要回到未来的哪个版本。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;撤销修改&lt;/h2&gt;
&lt;p&gt;场景1：当你改乱了工作区某个文件的内容，想直接丢弃工作区的修改时，用命令&lt;code&gt;git checkout -- file&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;场景2：当你不但改乱了工作区某个文件的内容，还添加到了暂存区时，想丢弃修改，分两步，第一步用命令&lt;code&gt;git reset HEAD &amp;lt;file&amp;gt;&lt;/code&gt;，就回到了场景1，第二步按场景1操作。&lt;/p&gt;
&lt;p&gt;场景3：已经提交了不合适的修改到版本库时，想要撤销本次提交，参考&lt;a href=&quot;#%E7%89%88%E6%9C%AC%E5%9B%9E%E9%80%80&quot;&gt;版本回退&lt;/a&gt;一节，不过前提是没有推送到远程库。&lt;/p&gt;
&lt;h2&gt;删除文件&lt;/h2&gt;
&lt;p&gt;先手动删除文件，然后使用&lt;code&gt;git status&lt;/code&gt;命令会立刻告诉你哪些文件被删除了&lt;/p&gt;
&lt;p&gt;现在有两个选择，一是确实要从版本库中删除该文件，那就用命令&lt;code&gt;git rm&lt;/code&gt;删掉，并且&lt;code&gt;git commit&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;另一种情况是删错了，因为版本库里还有呢，所以可以很轻松地把误删的文件恢复到最新版本：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ git checkout -- test.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;git checkout&lt;/code&gt;其实是用版本库里的版本替换工作区的版本，无论工作区是修改还是删除，都可以“一键还原”。从来没有被添加到版本库就被删除的文件，是无法恢复的！&lt;/p&gt;
&lt;h2&gt;远程仓库&lt;/h2&gt;
&lt;h3&gt;初始化&lt;/h3&gt;
&lt;p&gt;要关联一个远程库，使用命令&lt;code&gt;git remote add origin git@server-name:path/repo-name.git&lt;/code&gt;；&lt;/p&gt;
&lt;p&gt;关联一个远程库时必须给远程库指定一个名字，&lt;code&gt;origin&lt;/code&gt;是默认习惯命名；&lt;/p&gt;
&lt;p&gt;关联后，使用命令&lt;code&gt;git push -u origin master&lt;/code&gt;第一次推送&lt;code&gt;master&lt;/code&gt;分支的所有内容；&lt;/p&gt;
&lt;p&gt;此后，每次本地提交后，只要有必要，就可以使用命令&lt;code&gt;git push origin master&lt;/code&gt;推送最新修改；&lt;/p&gt;
&lt;p&gt;&lt;code&gt;git config user.name &quot;username&quot; &lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;git config user.email &quot;username@example.com&quot;&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;仓库克隆&lt;/h3&gt;
&lt;p&gt;要克隆一个仓库，首先必须知道仓库的地址，然后使用&lt;code&gt;git clone&lt;/code&gt;命令克隆。&lt;/p&gt;
&lt;h2&gt;Git分支管理&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Git鼓励大量使用分支：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;查看分支：&lt;code&gt;git branch&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;创建分支：&lt;code&gt;git branch &amp;lt;name&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;切换分支：&lt;code&gt;git checkout &amp;lt;name&amp;gt;&lt;/code&gt;或者&lt;code&gt;git switch &amp;lt;name&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;创建+切换分支：&lt;code&gt;git checkout -b &amp;lt;name&amp;gt;&lt;/code&gt;或者&lt;code&gt;git switch -c &amp;lt;name&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;合并某分支到当前分支：&lt;code&gt;git merge &amp;lt;name&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;删除分支：&lt;code&gt;git branch -d &amp;lt;name&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;如果要丢弃一个没有被合并过的分支，可以通过&lt;code&gt;git branch -D &amp;lt;name&amp;gt;&lt;/code&gt;强行删除。&lt;/p&gt;
&lt;p&gt;当Git无法自动合并分支时，就必须首先解决冲突。解决冲突后，再提交，合并完成。&lt;/p&gt;
&lt;p&gt;解决冲突就是把Git合并失败的文件手动编辑为我们希望的内容，再提交。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ git add readme.txt 
$ git commit -m &quot;conflict fixed&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;用&lt;code&gt;git log --graph&lt;/code&gt;命令可以看到分支合并图。&lt;/p&gt;
&lt;h4&gt;bug分支&lt;/h4&gt;
&lt;p&gt;软件开发中，bug就像家常便饭一样。有了bug就需要修复，在Git中，由于分支是如此的强大，所以，每个bug都可以通过一个新的临时分支来修复，修复后，合并分支，然后将临时分支删除。&lt;/p&gt;
&lt;h4&gt;多人协作&lt;/h4&gt;
&lt;p&gt;多人协作的工作模式通常是这样：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;首先，可以尝试用&lt;code&gt;git push origin &amp;lt;branch-name&amp;gt;&lt;/code&gt;推送自己的修改；&lt;/li&gt;
&lt;li&gt;如果推送失败，则因为远程分支比你的本地更新，需要先用&lt;code&gt;git pull&lt;/code&gt;试图合并；&lt;/li&gt;
&lt;li&gt;如果合并有冲突，则解决冲突，并在本地提交；&lt;/li&gt;
&lt;li&gt;没有冲突或者解决掉冲突后，再用&lt;code&gt;git push origin &amp;lt;branch-name&amp;gt;&lt;/code&gt;推送就能成功！&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如果&lt;code&gt;git pull&lt;/code&gt;提示&lt;code&gt;no tracking information&lt;/code&gt;，则说明本地分支和远程分支的链接关系没有创建，用命令&lt;code&gt;git branch --set-upstream-to &amp;lt;branch-name&amp;gt; origin/&amp;lt;branch-name&amp;gt;&lt;/code&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;查看远程库信息，使用&lt;code&gt;git remote -v&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;本地新建的分支如果不推送到远程，对其他人就是不可见的；&lt;/li&gt;
&lt;li&gt;从本地推送分支，使用&lt;code&gt;git push origin branch-name&lt;/code&gt;，如果推送失败，先用&lt;code&gt;git pull&lt;/code&gt;抓取远程的新提交；&lt;/li&gt;
&lt;li&gt;在本地创建和远程分支对应的分支，使用&lt;code&gt;git checkout -b branch-name origin/branch-name&lt;/code&gt;，本地和远程分支的名称最好一致；&lt;/li&gt;
&lt;li&gt;建立本地分支和远程分支的关联，使用&lt;code&gt;git branch --set-upstream branch-name origin/branch-name&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;从远程抓取分支，使用&lt;code&gt;git pull&lt;/code&gt;，如果有冲突，要先处理冲突。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Tag&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;命令&lt;code&gt;git tag &amp;lt;tagname&amp;gt;&lt;/code&gt;用于新建一个标签，默认为&lt;code&gt;HEAD&lt;/code&gt;，也可以指定一个commit id；&lt;/li&gt;
&lt;li&gt;命令&lt;code&gt;git tag -a &amp;lt;tagname&amp;gt; -m &quot;blablabla...&quot;&lt;/code&gt;可以指定标签信息；&lt;/li&gt;
&lt;li&gt;命令&lt;code&gt;git tag&lt;/code&gt;可以查看所有标签。&lt;/li&gt;
&lt;li&gt;命令&lt;code&gt;git push origin &amp;lt;tagname&amp;gt;&lt;/code&gt;可以推送一个本地标签；&lt;/li&gt;
&lt;li&gt;命令&lt;code&gt;git push origin --tags&lt;/code&gt;可以推送全部未推送过的本地标签；&lt;/li&gt;
&lt;li&gt;命令&lt;code&gt;git tag -d &amp;lt;tagname&amp;gt;&lt;/code&gt;可以删除一个本地标签；&lt;/li&gt;
&lt;li&gt;命令&lt;code&gt;git push origin :refs/tags/&amp;lt;tagname&amp;gt;&lt;/code&gt;可以删除一个远程标签。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;.gitignore&lt;/h2&gt;
&lt;p&gt;不需要从头写&lt;code&gt;.gitignore&lt;/code&gt;文件，GitHub已经为我们准备了各种配置文件，只需要组合一下就可以使用了。所有配置文件可以直接在线浏览：&lt;a href=&quot;https://github.com/github/gitignore&quot;&gt;GitHub/gitignore&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;示例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Windows:
Thumbs.db
ehthumbs.db
Desktop.ini

# Python:
*.py[cod]
*.so
*.egg
*.egg-info
dist
build

# My configurations:
db.ini
deploy_key_rsa

# 排除所有.开头的隐藏文件:
.*
# 排除所有.class文件:
*.class

# 不排除.gitignore和App.class:
!.gitignore
!App.class
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;.gitignore&lt;/code&gt;写得有问题，需要找出来到底哪个规则写错了，可以用&lt;code&gt;git check-ignore&lt;/code&gt;命令检查&lt;/p&gt;
</content:encoded></item><item><title>go语言学习笔记（1）</title><link>http://zaoweiceng.github.io/posts/go/go%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B01/</link><guid isPermaLink="true">http://zaoweiceng.github.io/posts/go/go%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B01/</guid><description>go语言学习之路</description><pubDate>Sat, 10 Aug 2024 00:00:00 GMT</pubDate><content:encoded>&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;编译器会自动选择在栈上还是在堆上分配局部变量的存储空间，这个选择并不是由用var还是new声明变量的方式决定的。因此，函数内声明的函数指针，赋值后，返回该指针，函数外仍然可用。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Go语言的自动垃圾收集器是如何知道一个变量是何时可以被回收呢？基本的实现思路是，从每个包级的变量和每个当前运行函数的每一个局部变量开始，通过指针或引用的访问路径遍历，是否可以找到该变量。如果不存在这样的访问路径，那么说明该变量是不可达的，也就是说它是否存在并不会影响程序后续的计算结果。因为一个变量的有效周期只取决于是否可达，因此一个循环迭代内部的局部变量的生命周期可能超出其局部作用域。同时，局部变量可能在函数返回之后依然存在。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;go语言中指针，实现类似c++中的&lt;code&gt;*(p+1)&lt;/code&gt;来访问地址&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;pre&gt;&lt;code&gt;func point() *int32 {
	var i int32 = 0
	return &amp;amp;i
}

func main(){
    var p = point()
	*p = 100
	var p1 = point()
	var p2 = point()
	*p2 = 200
    
    *(*int32)(unsafe.Pointer(uintptr(unsafe.Pointer(p)) + uintptr(unsafe.Sizeof(*p)))) //*(p+1) -&amp;gt; p1
    *(*int32)(unsafe.Pointer(uintptr(unsafe.Pointer(p)) + 2*uintptr(unsafe.Sizeof(*p))))  //*(p+2) -&amp;gt;p2
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;go中的swap：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;a, b = b, a
num[0], num[1], num[2] = num[1], num[2], num[0]
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;切片操作可能出问题：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var nums = []int{1, 2, 3, 4, 5}
a := nums[1:3]
//对a的任何操作都会反作用于nums
//若a.append(a, 100)，那么会修改nums[3]的值。
//
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;匿名嵌入&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;type Point struct {
    X, Y int
}

type Circle struct {
    Center Point
    Radius int
}

type Wheel struct {
    Circle Circle
    Spokes int
}
var w Wheel
//因为匿名嵌入
w.X = 10 //等价于 w.Circle.Point.X = 10
w.Y = 100
//但是赋值的时候必须循环嵌套,下面这种会报错
w = Wheel{X: 8, Y: 8, Radius: 5, Spokes: 20} // compile error: unknown fields
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;导出结构，只有大写字母开头的成员才会被导出&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;type Movie struct {
	Title  string `json:&quot;标题&quot;`
	Year   int    `json:&quot;年&quot;`
	Color  bool   `json:&quot;color, omitempty&quot;` //omitempty选项，表示当Go语言结构体成员为空或零值时不生成该JSON对象
	Actors []string
	test   string // 小写字母开头不会被编码
}
var movies = []Movie{
	{Title: &quot;Casablanca&quot;, Year: 1942, Color: false,
     Actors: []string{&quot;Humphrey Bogart&quot;, &quot;Ingrid Bergman&quot;, &quot;Claude Rains&quot;}, test: &quot;test&quot;}}

data, err := json.Marshal(movies)
//data的json字符串中没有test字段
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item><item><title>c++程序编译</title><link>http://zaoweiceng.github.io/posts/cpp/cpp%E7%A8%8B%E5%BA%8F%E7%BC%96%E8%AF%91/</link><guid isPermaLink="true">http://zaoweiceng.github.io/posts/cpp/cpp%E7%A8%8B%E5%BA%8F%E7%BC%96%E8%AF%91/</guid><description>使用g++对c++程序编译，基本的命令介绍</description><pubDate>Thu, 09 May 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;gcc/g++&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;g++&lt;/code&gt; 是 GNU 编译器集合（GNU Compiler Collection）中的 C++ 编译器。以下是一些基本的 &lt;code&gt;g++&lt;/code&gt; 使用方法：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;编译一个 C++ 源文件&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;g++ -o output_file source_file.cpp
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这条命令会编译 &lt;code&gt;source_file.cpp&lt;/code&gt; 并生成名为 &lt;code&gt;output_file&lt;/code&gt; 的可执行文件。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;编译多个 C++ 源文件&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;g++ -o output_file source1.cpp source2.cpp
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果你有多个源文件，可以一次性编译它们。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;链接外部库&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;g++ -o output_file source_file.cpp -llibrary
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果你的程序需要链接一个外部库（例如 &lt;code&gt;-lstdc++&lt;/code&gt; 是 C++ 标准库），可以在命令中加入 &lt;code&gt;-l&lt;/code&gt; 选项后跟库的名字。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;指定编译器的搜索路径&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;g++ -o output_file source_file.cpp -I/include/path
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用 &lt;code&gt;-I&lt;/code&gt; 选项可以指定头文件的搜索路径。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;启用 C++11 或更高版本的标准&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;g++ -o output_file source_file.cpp -std=c++11
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果你使用了 C++11 或更高版本的特性，需要使用 &lt;code&gt;-std&lt;/code&gt; 选项指定版本。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;优化代码&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;g++ -o output_file source_file.cpp -O2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;-O0&lt;/code&gt; 不优化。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;-O1&lt;/code&gt; O或-01对程序做部分编译优化，对于大函数，优化编译占用稍微多的时间和相当大的内存。使用本项优化，编译器会尝试减小生成代码的尺寸，以及缩短执行时间，但并不执行需要占用大量编译时间的优化。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;-O2&lt;/code&gt; 比O1更高级的选项，进行更多的优化。与O1比较而言，O2优化增加了编译时间的基础上，提高了生成代码的执行效率。&lt;strong&gt;推荐使用&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;-O3&lt;/code&gt;比O2更优 。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;调试信息&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;g++ -o output_file source_file.cpp -g
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用 &lt;code&gt;-g&lt;/code&gt; 选项可以在编译时包含调试信息，这样你可以使用调试器（如 gdb）进行调试。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;警告和错误&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;g++ -o output_file source_file.cpp -Wall -Werror
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;-Wall&lt;/code&gt; 选项启用所有警告，&lt;code&gt;-Werror&lt;/code&gt; 将警告视为错误，使得编译在遇到警告时停止。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;运行程序&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;./output_file
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;编译完成后，可以通过在命令行中输入可执行文件的名称来运行程序。
这些是 &lt;code&gt;g++&lt;/code&gt; 的一些基本用法。根据你的具体需求，你可能会使用更多的选项和参数。&lt;code&gt;g++&lt;/code&gt; 的 man 页面提供了完整的选项列表和详细说明，可以通过运行 &lt;code&gt;man g++&lt;/code&gt; 来查看。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;静态库&lt;/h1&gt;
&lt;p&gt;在 C++ 中，静态库（Static Library）是一种包含编译后的代码的文件，可以在链接阶段与其他源代码一起被链接到可执行文件中。静态库通常以 &lt;code&gt;.a&lt;/code&gt;（在 Linux 和 macOS 上）或 &lt;code&gt;.lib&lt;/code&gt;（在 Windows 上）文件扩展名结尾。
以下是创建和使用静态库的基本步骤：&lt;/p&gt;
&lt;h3&gt;创建静态库&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;编写源代码&lt;/strong&gt;:
创建一个或多个源文件（&lt;code&gt;.cpp&lt;/code&gt;）和一个头文件（&lt;code&gt;.h&lt;/code&gt;）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;编译源文件&lt;/strong&gt;:
使用 &lt;code&gt;g++&lt;/code&gt;（或其他编译器）将源文件编译成目标文件（&lt;code&gt;.o&lt;/code&gt;）。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;g++ -c source_file1.cpp source_file2.cpp
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;-c&lt;/code&gt; 选项告诉编译器只编译源文件，不进行链接。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;创建静态库&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;g++ -c -o libXXX.a source_file.cpp
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;使用静态库&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;包含头文件&lt;/strong&gt;:
在使用静态库的源代码中包含库的头文件。&lt;pre&gt;&lt;code&gt;#include &quot;libraryname.h&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;编译源代码&lt;/strong&gt;:
编译源代码时，指定静态库的路径和名称。&lt;pre&gt;&lt;code&gt;g++ -o output_file source_file.cpp -L/path/to/library -llibraryname
&lt;/code&gt;&lt;/pre&gt;
&lt;code&gt;-L&lt;/code&gt; 选项指定静态库的路径，&lt;code&gt;-l&lt;/code&gt; 选项指定静态库的名称（去掉 &lt;code&gt;lib&lt;/code&gt; 前缀和 &lt;code&gt;.a&lt;/code&gt; 或 &lt;code&gt;.lib&lt;/code&gt; 后缀）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;链接静态库&lt;/strong&gt;:
在链接阶段，链接器会从指定的路径中查找静态库，并将其链接到可执行文件中。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;示例&lt;/h3&gt;
&lt;p&gt;假设你有以下文件：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;library.h&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;library.cpp&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;main.cpp&lt;/code&gt;
首先，编译 &lt;code&gt;library.cpp&lt;/code&gt;：&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;g++ -c -o liblibrary.a library.cpp
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;接下来，编译 &lt;code&gt;main.cpp&lt;/code&gt; 并链接静态库：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;g++ -o main main.cpp -L. -llibrary
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最后，运行可执行文件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;./main
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这个例子中，&lt;code&gt;.&lt;/code&gt; 表示当前目录，&lt;code&gt;-llibrary&lt;/code&gt; 指定了静态库 &lt;code&gt;liblibrary.a&lt;/code&gt;。
请注意，这些步骤可能会根据你的操作系统和编译器有所不同。以上步骤适用于类 Unix 系统，如 Linux 和 macOS。在 Windows 上，你可能需要使用不同的命令和工具。&lt;/p&gt;
&lt;h1&gt;动态库&lt;/h1&gt;
&lt;p&gt;在使用g++编译动态库时，需要遵循几个步骤。以下是一个简单的指南：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;编写源代码&lt;/strong&gt;：
动态库由源代码文件组成，通常是以&lt;code&gt;.cpp&lt;/code&gt;为扩展名的C++源文件和以&lt;code&gt;.h&lt;/code&gt;为扩展名的头文件。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;创建编译命令&lt;/strong&gt;：
使用g++编译器，可以使用以下命令来编译一个动态库：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;g++ -shared -fPIC -o libyourlibrary.so yoursourcefile.cpp
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-shared&lt;/code&gt;：告诉g++生成一个共享库。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-fPIC&lt;/code&gt;：表示生成位置无关代码，这是创建动态库所必需的。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-o&lt;/code&gt;：指定输出文件名。在Linux系统中，动态库通常以&lt;code&gt;lib&amp;lt;name&amp;gt;.so&lt;/code&gt;的格式命名。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;yoursourcefile.cpp&lt;/code&gt;：这是的源代码文件。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;包含头文件&lt;/strong&gt;：
如果动态库中有函数声明在头文件中，那么在使用这个库的时候需要包含这些头文件。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;链接动态库&lt;/strong&gt;：
当编译使用这个动态库的代码时，需要指定动态库的位置，可以使用&lt;code&gt;-L&lt;/code&gt;选项来指定库文件所在的目录，使用&lt;code&gt;-l&lt;/code&gt;选项来指定库的名称（去掉&lt;code&gt;lib&lt;/code&gt;前缀和&lt;code&gt;.so&lt;/code&gt;后缀）。
例如，如果的动态库叫&lt;code&gt;libyourlibrary.so&lt;/code&gt;，在&lt;code&gt;/usr/local/lib&lt;/code&gt;目录下，那么编译命令可能如下所示：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;g++ -o yourprogram yourprogram.cpp -L/usr/local/lib -lyourlibrary
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;设置库路径&lt;/strong&gt;：
在运行使用动态库的程序之前，可能需要确保动态链接器知道库的位置。这可以通过设置&lt;code&gt;LD_LIBRARY_PATH&lt;/code&gt;环境变量来实现：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;运行程序&lt;/strong&gt;：
最后，可以通过输入程序名称来运行程序。如果一切设置正确，程序将能够找到并使用动态库。
以上步骤是编译和使用动态库的基本流程。在具体实施时，可能需要根据具体环境和需求进行适当的调整。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item><item><title>python中网页解析库及基本用法</title><link>http://zaoweiceng.github.io/posts/python/%E7%BD%91%E9%A1%B5%E8%A7%A3%E6%9E%90/</link><guid isPermaLink="true">http://zaoweiceng.github.io/posts/python/%E7%BD%91%E9%A1%B5%E8%A7%A3%E6%9E%90/</guid><description>python爬虫中常用的网页解析库及基本用法。</description><pubDate>Tue, 13 Feb 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;lxml&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;安装&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;pip install lxml&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;基本使用&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;pre&gt;&lt;code&gt;from lxml import etree

html = etree.HTML(text)
res = html.xpath(xpath_string)
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;限制属性：&lt;code&gt;&apos;//div[@class=&quot;attr0&quot;]&apos;&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;获取文本：&lt;code&gt;&apos;//div/text()&apos;&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;获取属性：&lt;code&gt;‘//li/a/@href’&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Beautifal Soup&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;安装&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pip install beautifulsoup4&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;基本使用&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;pre&gt;&lt;code&gt;from bs4 import BeautifulSoup

soup = BeautifulSoup(html, &apos;lxml&apos;)
for i, child in enumerate(soup.children):
    print(i, child)
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;获取属性：&lt;code&gt;soup.p.attrs[&apos;name&apos;]&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;获取文本：&lt;code&gt;soup.p.tring&lt;/code&gt;  （默认为当前节点下的第一个p节点）&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;方法选择器find_all(name, attrs, recursive, text), find类似但返回单个&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;查找所有&lt;code&gt;ul&lt;/code&gt;节点：&lt;code&gt;soup.find_all(name=&apos;ul&apos;)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;加入属性：&lt;code&gt;soup.find_all(name=&apos;ul&apos;, attrs={&apos;id&apos;:&apos;demo1&apos;})&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>python中requests的基本用法</title><link>http://zaoweiceng.github.io/posts/python/requests/</link><guid isPermaLink="true">http://zaoweiceng.github.io/posts/python/requests/</guid><description>python爬虫中常用的网页解析库及基本用法。</description><pubDate>Mon, 12 Feb 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;requests&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;发起的是同步网络请求&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;导入&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;import requests
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;请求&lt;/h2&gt;
&lt;h3&gt;get&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;requests.get(...)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;传递参数&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;data = {
	&apos;name&apos;:&apos;amy&apos;,
	&apos;age&apos;:15
}
headers = {
	&apos;...&apos;:&apos;...&apos;
}
res = requests.get(url, params=data, headers=headers)
res.json() # 转为字典，若返回不是json格式将报错
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;若网站的SSL出错，则可以不进行SSL验证&lt;code&gt;requests.get(url, verify=False)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;超时设置&lt;code&gt;requests.get(url, timeout=1)&lt;/code&gt; 单位为：秒。(设置为None表示永久等待)&lt;/li&gt;
&lt;li&gt;登录验证：&lt;code&gt;requests.get(url, auth=(username, password))&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;获取二进制数据（图片、音频、视频等）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;res = requests.get(&apos;...../a.png&apos;, params=data)
with open(&apos;a.png&apos;, &apos;wb&apos;) as f:
	f.write(res.content)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;post&lt;/h3&gt;
&lt;p&gt;类似于get&lt;/p&gt;
&lt;h2&gt;session&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;session = requests.session()&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;session.get(...)&lt;/code&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;响应&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;res.status_code&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;res.headers&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;res.cookies&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;res.url&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;res.history&lt;/code&gt; 请求历史&lt;/p&gt;
</content:encoded></item><item><title>说说</title><link>http://zaoweiceng.github.io/posts/md/shuoshuo/</link><guid isPermaLink="true">http://zaoweiceng.github.io/posts/md/shuoshuo/</guid><pubDate>Thu, 09 Sep 1999 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;2025-03-16: 四年了，黑魂3全成就！
&lt;img src=&quot;./img/20250316213403.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;2025-03-15: 太棒了，是Maven！&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;2025-03-10: 开干！&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item></channel></rss>