- 跨域问题:通过一个地址访问另一个地址,如果访问协议,IP地址,端口号有任意一个不一样,就是跨域问题
解决方式:- 在controller类上加注解:@CrossOrigin
- 网关解决
- 前端解决
- nodejs是什么
(1)之前学过java,运行java需要安装jdk环境
这个就是JavaScript的运行环境,不需要浏览器,直接使用nodejs运行JavaScript代码
(2)模拟服务器效果,比如tomcat - NPM是什么:包管理工具(安装nodejs默认给装上了),类似于maven
(1)在后端开发中,使用过maven,maven构建项目,管理jar依赖,联网下载依赖
(2)npm类似maven,用在前端中,管理前端js依赖,联网下载js依赖,比如:jquery - babel是什么
babel是转发器,把es6代码转换成es5代码
因为写的是es6,但是es6代码浏览器兼容性差,需将es6转成es5 - 模块化
是什么:(1)开发后端接口的时候,开发controller,service,mapper,类与类之间的调用叫做后 端模块化操作
(2)前端模块化,在前端中,js与js之间调用称为前端模块化操作 - webpack
打包工具,可以把多个静态资源文件打包成一个文件,减少页面的请求 - 乐观锁:解决某些问题(在表中加个版本号)
主要解决:丢失更新
如果不考虑事务隔离性,产生读问题:脏读,不可重复读,幻读api
写问题:丢失更新问题 - swagger:前后端分离开发模式中,api文档是最好的沟通方式。Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。
作用:生成在线接口文档,方便接口测试。 - @ResponserBody:用于返回json数据
@RequestBody:使用json传递数据,把json数据封装到对应对象里面,提交方式得是post - 程序:一段静态的代码
进程:正在运行的一个程序
线程:程序内部的一条执行路径,线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc) - 并行:多个CPU同时执行多个任务,比如:多个人同时做不同的事
并发:一个CPU(采用时间片)”同时”执行多个任务,比如:秒杀,多个人做同一件事 - @RequestBody
使用json传输数据,把json数据封装到对应对象中去(需要使用post提交方式) - 用过的日志工具:log4j,logback
- 面试题:遇到的问题:es6代码在nodejs中无法运行,需要转换成es5
- Nginx:反向代理服务器
请求转发
负载均衡
动静分离
- 403状态码问题: 1).跨域 2).路径不对
- 做项目最难忘的bug:点删除按钮,后端删除正常执行,状态码是200,但是前端没有执行 .then(),执行了.catch(),原因是:接口返回null,没有返回数据
- 微服务: 1).微服务是架构风格
2).把一个项目拆分成独立的多个服务,多个服务是独立运行,每个服务占用独立进程 - Springcloud
(1)springcloud不是一种技术,是很多技术总称,很多框架集合
(2)springcloud里面有很多框架(技术),使用springcloud里面的这些框架实现微服务操作
(3)使用springcloud,需要依赖springboot - OAuth2是什么?
- OAuth2是针对特定问题的一种解决方案
- 主要可以解决两个问题:开放系统间授权,分布式访问问题
- 依赖注入的几种方式:setter,有参构造,接口注入
- Spring,springmvc,springboot之间的区别?
spring是一个轻量级的控制反转,面向切面的开源容器框架,它可以配置各种组件,管理组件之间的依赖依赖关系,
springmvc是spring的一部分,是基于sevlet的mvc框架,主要用于开发web应用,
springboot是一个微服务框架,简化spring应用的搭建和开发 - Springboot自动配置原理:
Spring Boot启动的时候会通过@EnableAutoConfiguration注解找到META-INF/spring.factories配置文件中的所有自动配置类,并对其进行加载,而这些自动配置类都是以AutoConfiguration结尾来命名的,它实际上就是一个JavaConfig形式的Spring容器配置类,它能通过以Properties结尾命名的类中取得在全局配置文件中配置的属性如:server.port,而XxxxProperties类是通过@ConfigurationProperties注解与全局配置文件中对应的属性进行绑定的。 - 数据结构存储分类:
- 集合:
- 线性表:顺序表,链表,队列,栈,数组
- 树结构:普通树,二叉树,线索二叉树
- 图存储结构
- AOP面向切面编程
在不修改源码的基础上对原有的方法进行增强 - ArrayList是List的主要实现类,线程不安全的,效率高,底层用数组存储
底层原理:- jdk7:new ArrayList():底层会创建一个长度为10的object数组,调用add()会往数组里添加值,如果容量不够,会进行扩容,默认情况下会扩容为原来的1.5倍,并将旧数组的数据复制到新数组中
- jdk8:new ArrayList():底层会初始化数组,并不会创建一个长度为10的数组,当我们第一次调用add()时,底层才创建一个长度为10的数组,后续操作和jdk7一样
- Vector:作为List的古老实现类,线程安全的,效率低,底层用数组存储
底层原理:
Jdk7和jdk8通过new Vector()创建对象,底层都创建了长度为10的数组,在扩容方面默认扩容为原来的数组长度的2倍
- LinkedList:对于频繁的插入,删除操作,使用此类比ArrayList效率高,底层用双向链表存储
- Set集合的特点
- 存储无序的,不可重复的得数据,没有额外的方法,都是collection接口的方法
- 无序性:不等于随机性,是指添加数据时的无序,添加数据时,在底层并非按照数组索引进行添加,而是按照哈希值
- 不可重复性:添加的数据是按照equals(),相同的数据只能添加一个
- 向Set(主要是向HashSet,LinkedHashSet)添加数据时,其所在的类一定要重写equals()和hashCode()
- 重写的equals()和hashCode()时尽可能保持一致性:相等的对象具有相等的散列码
- 存储无序的,不可重复的得数据,没有额外的方法,都是collection接口的方法
- HashSet:作为Set接口的主要实现类,线程不安全的,可以存储null,底层是数组加链表
底层原理:
向HashSet添加元素a,首先根据hashCode()计算出哈希值,在跟据哈希值通过某种算法计算出在底层数组中的索引位置,判断此位置上是否有元素:如果没有元素,就添加成功;如果有元素b(或者是一链表形式存在的多个元素),则比较元素a与元素b的hash值:如果hash值不相同,就添加成功,如果相同,进而调用元素的equals(),如果返回false,元素a添加成功,返回true,添加失败
jdk 7 :元素a放到数组中,指向原来的元素。
jdk 8 :原来的元素在数组中,指向元素a
总结:七上八下
- LinkedHashSet(HashSet的子类):
- 作为HashSet的子类,遍历其内部数据时,按照添加数据的顺序遍历,对于频繁的遍历操作,LinkedHashSet效率高于HashSet,
- 在添加数据时,每个数据还维护了两个引用,记录了前一个数据和后一个数据
- TreeSet(可以按照添加对象的指定属性进行排序):
- 向TreeSet中添加的数据,要求是相同类的对象
- 两种排序方式:自然排序(实现comparable接口)和定制排序(comparator)
- 自然排序中,比较两个对象是否相同的标准是:compareTo()返回0,不再是equals()
- 定制排序中,比较两个对象是否相同的标准是:compare()返回0,不再是equals()
- Map:
- Map是双列数据,存储key-value数据,类似于y=f(x)
- Map中的key:无序的,不可重复的,使用set存储所有的key,key所在的类需要重写equals()和hashCode()
- Map中的value:无序的,可重复的,使用collection存储所有的value,value所在的类要重写equals()
- 一个键值对:key-value构成了一个Entry对象
- Map中的entry:无序的,不可重复的,使用set存储所有的entry
- HashMap:是Map的主要实现类,线程不安全的,效率高,可以存储null的key和value
- 成员变量
DEFAULT_INITIAL_CAPACITY : HashMap的默认容量,16
DEFAULT_LOAD_FACTOR:HashMap的默认加载因子:0.75
threshold:扩容的临界值,=容量填充因子:16 0.75 => 12
TREEIFY_THRESHOLD:Bucket中链表长度大于该默认值,转化为红黑树:8
MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量:64
- 底层源码
- Jdk7及以前(数组+链表):
HashMap map=new HashMap();在实例化以后,底层创建了长度是16的一维数组:Entry[] table;
map.put(key1,value1):首先调用key1所在类的hashCode()计算key1的哈希值,在经过某种算法后,得到在Entry[]中的存放位置,
如果该位置上为空,此时key1-value1添加成功,
如果该位置上数据不为空(意味着这个位置上有一个数据或者多个数据),比较key1和已经存在了的一个或者多个数据的哈希值,
如果key1的哈希值和存在的数据都不相同,此key1-value1添加成功,
如果key1的哈希值与某一个的哈希值相同,就用equals()比较:
如果返回false,此时的key1-value1添加成功,
返回true,此时的value1替换掉原来的value值
扩容机制:当添加的数据超出扩容临界值(且要存放的位置非空)时,进行扩容,默认的扩容方式:扩容原来的2倍,并将原来的数据复制过来
1. Jdk8(数组+链表+红黑树)与jdk7的不同
1. new HashMap():底层没有创建一个长度为16的数组
1. Jdk8底层的数组是:Node(),而非Entry[]
1. 首次调用put()是,底层才创建长度为16的数组
1. Jdk7底层是数组+链表,jdk8底层是数组+链表+红黑树
1. 形成链表时,七上八下(jdk7:新的元素指向旧的元素,jdk8:旧的元素指向新的元素)
1. 当数组的某一个索引位置的元素以链表形式存在的数据个数>8并且当前数组的长度>64时,此索引位置上的所有数据改为使用红黑树存储
- LinkedHashMap:
- 保证在遍历map元素时,可以按照添加的顺序实现遍历 原因是:在原有的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素
- 对于频繁的遍历操作,此类执行效率高于HashMap
- TreeMap(红黑树):
保证按照添加的key-value对进行排序,实现排序遍历,此时考虑key的自然排序或定制排序
- HashTable
- 作为Map的古老实现类,线程安全的,效率低;不能存储null的key-value
- Properties:常用来处理配置文件,key和value都是String类型
- Redis:Remote Dictionary Server(远程字典服务器)
- 存储过程和函数:事先经过编译并存储在数据库中的一段sql语句的集合
使用好处:
- 简化应用开发人员的很多工作,可重复调用
- 减少了数据在数据库和应用服务器之间的传输
- 提高了数据处理的效率
- 视图:是一种虚拟存在的表,只保存了sql逻辑,不保存查询结果
- 重用sql语句
- 简化复杂的sql操作,
- 提高安全性,用户不知道表结构
- 线程的通信:
- wait():一旦执行此方法,当前线程就会进入阻塞状态,并释放同步监视器
- notify():一旦执行此方法,就会唤醒被wait的一个线程,如果有多个线程,唤醒优先级高的那个
- notifyAll():一旦执行此方法,就会唤醒所有被wait的线程
- 以上三个方法必须使用在同步代码块或同步方法中
- 三个方法的调用者必须是同步代码块或同步方法中的同步监视器,否则会报异常
- 三个方法都是定义在Object类中
- sleep()和wait()的异同:
- 相同点:执行方法后,都能让当前线程进入阻塞状态
- 不同点:
- 两个方法的声明位置不同:sleep()是Thread()的,wait()是Object里的
- 调用的要求不同:sleep()可以再任何需求的场景下调用,wait()必须使用在同步代码块或者同步方法中
- 关于是否释放同步监视器:如果两个方法都是用在同步方法或者同步代码块中,sleep()不会释放锁,wait()会释放锁
- 哪些方法不能被重写?
- final
- 静态
- private等子类不可见方法
- 重写的要求,重写和重载的区别
- Redis持久化
- RDB
- AOF
- GC垃圾回收
- 发生在JVM的heap堆上
- GC分类
- 次数上频繁收集Young区 Minor GC
- 次数上较少收集Old区 Full GC
- GC四大算法:
- 引用计数法(已经被JVM淘汰,不能循环引用)
- 复制算法(年轻代中使用的是Minor GC):
- 效率高,没有内存碎片
- 需要双倍空间
- 标记清除(老年代)
- 不需要额外空间
- 两次扫描,耗时严重,会产生内存碎片
- 标记压缩(老年代)
- 没有压缩碎片
- 需要移动对象的成本
- 标记清除压缩(老年代)
- 减少移动对象的成本
- Redis在项目中的使用场景
- String:想封锁一个IP地址,Incrby命令,当访问量到达一定值,可以封锁这个IP
- Hash:存储用户信息[id,name,age]
- List:实现最新消息的排行还可以利用List的push命令,将任务存在List中,同时使用另一个命令将任务从集合中取出(pop),用来模拟消息队列(例如:秒杀)
- Set:特殊之处,可以排重,微博好友
- ZSet:以某一个条件为权重,进行排序(按照各个不同的方式排名)
- Es和solr的区别
- 他们都是基于Lucene搜索服务器基础上开发[他们都是基于分词技术构建的倒排索引的方式进行查询]
- 当实时建立索引的时候,solr会产生io阻塞,而es则不会,es查询性能要高于solr
- 在不断动态添加数据的时候,solr的检索效率会变得低下,而es没有什么变化
- Solr利用zookeeper进行分布式管理,es自带分布式系统管理功能
- Solr支持xml.json,csv等,es只支持json
- Solr是传统搜索应用的有力解决方案,但是es更适用于新兴的实时搜索应用
- Solr官网提供的功能更多,而es本身更注重于核心功能,高级功能有第三方插件
- 单点登录
- 常见方式:
- session广播机制
- cookie+redis
- token
- 常见方式:
- Redis持久化
- Mycat:可以看做是分库分表的中间件
- dependencyManageMent里只是声明依赖(坐标和版本号),并不实现引入,因此子项目需要显示的声明需要引用的依赖
- 微服务模块:
- 建moudle
- 改pom
- 写yml
- 主启动
- 业务类
- Sql优化:
- 建索引
- 在where和order by 和group by后面的字段加索引
- 少用in,用exists代替
- 少用or,用union,union all代替
- 不用select *,要用具体的字段
- 不要再where子句后面对字段使用表达式操作
- 用like的时候第一个字符不要写%,例如:’%张三%’ 应该为 ’张三%’
- 用>=代替>
- 避免使用!=和<>
- 建索引
- 状态码:
- 413:nginx上传文件过大,请求体过大
- 403:跨域
- 302:重定向
- 类初始化过程
- 一个类要创建实例需要先加载并初始化该类
- main方法所在的类需要先加载和初始化
- 一个子类要初始化需要先初始化父类
- 一个类初始化就是执行
()方法 ()方法由静态类变量显示赋值代码和静态代码块组成 - 类变量显示赋值代码和静态代码块从上到下顺序执行
()只执行一次
- 一个类要创建实例需要先加载并初始化该类
- 实例初始化过程
- 实例初始化就是执行
()方法 ()方法可能重载有多个,有几个构造器就有几个 ()方法 ()方法由非静态实例变量显示赋值代码和非静态代码块、对应构造器组成 - 非静态实例变量显示赋值代码和非静态代码块从上到下顺序执行,而对应构造器的代码最后执行
- 每次创建实例对象,调用对应构造器,执行的就是对应的
()方法 ()方法的首行是super()或super(参数列表),即对应父类的 ()方法
- 实例初始化就是执行
- 方法的重写
- 哪些方法不可以被重写
- final方法
- 静态方法
- Private等子类中不可见的方法
- 对象的多态性
- 重写的要求
- 哪些方法不可以被重写
- 在分布式系统中是如何处理高并发的?
在高并发的环境下,来不及同步处理用户发送的请求,则会导致请求发生阻塞。比如说:大量的insert、update之类的请求同时到达mysql,直接导致无数的行锁和表锁,甚至会导致请求堆积很多,从而触发too many connections 错误,使用消息队列可以解决【异步通信】
- tomcat目录
- bin:存放tomcat服务器的可执行程序
- conf:存放配置文件
- lib:jar包
- logs:输出的日志信息
- temp:临时数据
- webapps:存放部署的工程
- work;是tomcat工作时的目录,用来存放tomcat运行时jsp翻译为sevlet的源码,和session钝化的目录
- Tomcat部署工程
- 第一种部署方法: 只需要把 web 工程的目录拷贝到 Tomcat 的 webapps 目录下
即可。 - 第二种部署方法:
找到 Tomcat 下的 conf 目录\Catalina\localhost\ 下,创建如下的配置文件:
- 第一种部署方法: 只需要把 web 工程的目录拷贝到 Tomcat 的 webapps 目录下
Xxx.xml,内容如下:
- mq相关知识
Mysql存储引擎对比 | 存储引擎 | InnoDB | MyISAM | | —- | —- | —- | | 主外键 | 支持 | 不支持 | | 事务 | 支持 | 不支持 | | 行表锁 | 支持行锁 | 不支持行锁 |
数据库索引
MySQL官方对索引的定义为:索引(Index)是帮助MySQL高校获取数据的数据结构。可以得到索引的本质:索引是数据结构
- 数据库的锁
读锁(共享锁):针对同一份数据,多个读操作可以同时进行而不会互相影响
写锁(排它锁):当前写操作没有完成前,它会阻断其他写锁和读锁。
- @Valid
@Valid 注解通常用于对象属性字段的规则检测
- 设计模式是针对软件设计中普遍存在的问题提出的解决方案(可扩展,可维护)
- Git管理的是修改,不是文件。
- 撤销修改
场景1:当你改乱了工作区某个文件的内容,想直接丢弃工作区的修改时,用命令git checkout — file。
场景2:当你不但改乱了工作区某个文件的内容,还添加到了暂存区时,想丢弃修改,分两步,第一步用命令git reset HEAD
场景3:已经提交了不合适的修改到版本库时,想要撤销本次提交,参考版本回退一节,不过前提是没有推送到远程库。
HEAD指向的版本就是当前版本,因此,Git允许我们在版本的历史之间穿梭,使用命令git reset —hard commit_id。
穿梭前,用git log可以查看提交历史,以便确定要回退到哪个版本。
要重返未来,用git reflog查看命令历史,以便确定要回到未来的哪个版本。
- server.context-path= # Context path of the application. 应用的上下文路径,也可以称为项目路径,是构成url地址的一部分。
- @Pointcut(“@within(com.season.core.csrf.CSRFCheck)||@annotation(com.season.core.csrf.CSRFCheck)”)
@within和@annotation的区别:
@within 对象级别
@annotation 方法级别
- JAXB(Java Architecture for XML Binding)是javaee和javase的一部分,开发者可以快速完成java类和xml的相互映射
- CommandLineRunner接口介绍
CommandLineRunner接口是在容器启动成功后的最后一步回调(类似开机自启动)。实际应用中,我们会有在项目服务启动的时候就去加载一些数据或做一些事情这样的需求。
为了解决这样的问题,Spring Boot 为我们提供了一个方法,通过实现接口 CommandLineRunner 来实现。很简单,只需要一个类就可以,无需其他配置。创建实现接口 CommandLineRunner 的类
- 类之间的关系:
- 依赖:use a
- 聚合:has a
- 继承:is a
- 局部变量不会初始值为null
- 如果在一个条件表达式中混合使用 Integer 和 Double 类型, Integer 值就会拆箱,提升为 double, 再装箱为 Double。
- 装箱和拆箱是编译器认可的, 而不是虚拟机。编译器在生成类的字节码时, 插人必要的方法调用。虚拟机只是执行这些字节码。
- 在 Java SE 8 中,允许在接口中增加静态方法。理论上讲,没有任何理由认为这是不合法的。 只是这有违于将接口作为抽象规范的初衷。
目前为止, 通常的做法都是将静态方法放在伴随类中。在标准库中, 你会看到成对出现的接口和实用工具类, 如 Collection/Collections 或 Path/Paths。
下面来看 Paths 类, 其中只包含两个工厂方法。可以由一个字符串序列构造一个文件或目录的路径, 如 Paths.getfjdk1.8.0”, “jre”, “bin”。) 在 Java SE 8 中, 可以为 Path 接口增加以下方法:
public interface Path
public static Path get(String first, String… more) {
return Fi1eSystems.getDefault().getPath(first, more);
这样一来, Paths 类就不再是必要的了。
不过整个 Java 库都以这种方式重构也是不太可能的, 但是实现你自己的接口时,不再需要为实用工具方法另外提供一个伴随类。 - SpringBoot中CommandLineRunner的作用
平常开发中有可能需要实现在项目启动后执行的功能,SpringBoot提供的一种简单的实现方案就是添加一个model并实现CommandLineRunner接口,实现功能的代码放在实现的run方法中 - 枚举:enum关键字是java5引入的,表示一种特殊的类,总是继承Enum类
- 枚举和常量类对比:枚举更具有可读性
- 枚举类中可以自定义方法
- 使用==比较枚举类型:由于枚举类型在JVM中只存在一个常量实例,所以可以使用==比较枚举
- 枚举可以使用在switch中
- CSRF(Cross-site request forgery),中文名称:跨站请求伪造
攻击者盗用了你的身份,利用你的身份发送请求。
要完成一次CSRF攻击,受害者必须依次完成两个步骤:
1.登录受信任网站A,并在本地生成Cookie。
2.在不登出A的情况下,访问危险网站B。
- @Order(0):设置优先级,值越低优先级越高
- java.net.ConnectException: Connection timed out
该异常原因一般是发出请求,响应超时,未收到响应
- 404原因:请求的资源(网页等)不存在,路径写的不对
- 用字符串调用方法时,想想要不要判断为不为空防止空指针
- TCP/IP协议是用于已连接因特网的计算机进行通信的通信协议
- 什么是TCP/IP协议?
- TCP/IP协议是指传输控制协议和网际协议
- TCP/IP协议是用于已连接因特网的计算机进行通信的通信协议
- TCP/IP协议定义了电子设备(比如:计算机)如何进入因特网,以及数据如何在他们之间传输的标准
- IP地址:
- 每个计算机必须有一个IP地址才能连入因特网
- 每个IP包必须有一个地址才能够发送到另一台计算机
- TCP/IP使用4组数字来为计算机编址,每个计算机必须有一个唯一的4组数字的地址
- 每组数字必须在0-255之间,并由点号隔开,比如:127.0.0.1
- 什么是TCP/IP协议?
- RPC
- RPC(Remote Procedure Call)—远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。比如两个不同的服务 A、B 部署在两台不同的机器上,那么服务 A 如果想要调用服务 B 中的某个方法该怎么办呢?使用 HTTP请求 当然可以,但是可能会比较慢而且一些优化做的并不好。 RPC 的出现就是为了解决这个问题。
- 最终解决的问题:让分布式或者微服务系统中不同服务之间的调用像本地调用一样简单。
- NIO:同步非阻塞IO,阻塞业务处理但不阻塞数据接收,适用于处理高并发但是业务处理简单的业务,比如:聊天软件
- BIO:同步阻塞IO,阻塞整个步骤,如果连接少,他的延迟是最低的,因为一个线程只处理一个连接,适用于少连接低延迟的场景,比如:数据库连接
- 幂等性:用户对于同一操作进行的一次或者多次请求的结果是一样的,不会因为多次点击请求产生负面效果。
- 状态码:
- 502:服务器暂时不可用,有时是为了防止发生系统过载
- FactoryBean接口:用户可以实现该接口来定制实例化bean。
- InitializingBean接口:该接口为bean提供了初始化方法的方式,它只包括afterPropertiesSet方法,实现该接口的bean在初始化的时候都会执行该方法。
- Git
工作区有一个隐藏目录.git,这个不算工作区,而是Git的版本库。
前面讲了我们把文件往Git版本库里添加的时候,是分两步执行的:
第一步是用git add把文件添加进去,实际上就是把文件修改添加到暂存区;
第二步是用git commit提交更改,实际上就是把暂存区的所有内容提交到当前分支。
Git跟踪并管理的是修改,而非文件。
每次修改,如果不用git add到暂存区,那就不会加入到commit中。
- WAR文件代表了一个Web应用程序,JAR是类的归档文件。
- @Configuration和@Bean结合使用可以创建bean实例,@Configuration相当于
@Bean相当于 - “3、接口默认继承java.lang.Object,所以如果接口显示声明覆盖了Object中方法,那么也不算抽象方法。” 这句描述有问题哈,接口并不继承Object类,而是隐式声明了Object类中的若干同名方法,目的是为了方便调用。接口隐含定义了一套与Object类中的方法签名完全相同的方法,所以,我们在程序中调用接口的那些与Object中具有相同签名的方法时,编译器不会报错
- ./表示当前路径
- 字符串驻留:当同时创建多个相同的字符串常量时,只有一个字符串常量存储在运行时常量池中。
- 常量折叠:String str=”hello”+”world”; 常量折叠完之后等价于String str=”helloworld”; 只会产生一个对象。