校招 未知部门 / 数据 / 一面

[面试日期] 2023-03-30 [面试时长] 未知
[学校背景] 合肥工业大学 | 211 [学历背景] 未知
[发布人] 面试之星官方收录
<h2>阿里一面面经</h2> <h3>项目中使用到了es,es的作用?</h3><p><span>elasticsearch是一款非常强大的开源搜索引擎,具备非常多强大功能,可以帮助我们从海量数据中快速找到需要的内容</span></p><p><span></span></p><h3><span>es中的重要概念?</span></h3><p><span>群集:一个或多个节点(服务器)的集合,它们共同保存您的整个数据,并提供跨所有节点的联合索引和搜索功能。群集由唯一名称标识,默认情况下为“elasticsearch”。此名称很重要,因为如果节点设置为按名称加入群集,则该节点只能是群集的一部分。</span></p><p><span></span></p><p><span>节点:属于集群一部分的单个服务器。它存储数据并参与群集索引和搜索功能。</span></p><p><span></span></p><p><span>索引:就像关系数据库中的“数据库”。它有一个定义多种类型的映射。索引是逻辑名称空间,映射到一个或多个主分片,并且可以有零个或多个副本分片。</span></p><p><span>eg: MySQL =&gt;数据库    ElasticSearch =&gt;索引</span></p><p><span></span></p><p><span>文档:类似于关系数据库中的一行。不同之处在于索引中的每个文档可以具有不同的结构(字段),但是对于通用字段应该具有相同的数据类型。</span></p><p><span>MySQL =&gt; Databases =&gt;   Tables =&gt; Columns / Rows ElasticSearch =&gt; Indices =&gt; Types =&gt;具有属性的文档</span></p><p><span></span></p><p><span>类型:是索引的逻辑类别/分区,其语义完全取决于用户。</span></p><p><span></span></p><p><span></span></p><h3>es为什么快,相对于mysql有什么优势?</h3><p>数据库在查询时,如果查询id的话,会那么<span>直接走索引</span>,查询速度非常快。但如果是基于title做模糊查询,只能是逐行扫描数据,流程如下:</p><ol><li>用户搜索数据,条件是title符合 <strong><code>&quot;%手机%&quot;</code></strong></li><li>逐行获取数据,比如id为1的数据</li><li>判断数据中的title是否符合用户搜索条件</li><li>如果符合则放入结果集,不符合则丢弃。回到步骤1</li></ol><p>这种全表扫描的方法在数据量很多的情况下会消耗很多时间。为了解决这个问题,elesticsearch中采用了倒排索引的方法。</p><p>首先引入两个概念:</p><ul><li>文档(Document):用来搜索的数据,其中的每一条数据就是一个文档。例如一个网页、一个商品信息。</li><li>词条(Term):对文档数据或用户搜索数据,利用某种算法分词,得到的具备含义的词语就是词条。例如:我是中国人,就可以分为:我、是、中国人、中国、国人这样的几个词条。</li></ul><p></p><p><strong>创建倒排索引</strong>是对正向索引的一种特殊处理,流程如下:</p><ul><li>将每一个文档的数据利用算法分词,得到一个个词条。</li><li>创建表,每行数据包括词条、词条所在文档id或位置等信息。</li><li>因为词条唯一性,可以给词条创建索引,例如hash表结构索引。</li></ul><p><img src="https://uploadfiles.nowcoder.com/images/20230329/127966388_1680058153397/D2B5CA33BD970F64A6301FA75AE2EB22"></p><p>倒排索引的<strong>搜索流程</strong>如下(以搜索&quot;华为手机&quot;为例):</p><p>1)用户输入条件 <strong><code>&quot;华为手机&quot; </code></strong>进行搜索。</p><p>2)对用户输入内容<strong>分词</strong>,得到词条:<strong><code>华为</code></strong>、<strong><code>手机</code></strong>。</p><p>3)拿着词条在倒排索引中查找,可以得到包含词条的文档id:1、2、3。</p><p>4)拿着文档id到正向索引中查找具体文档。</p><p><img src="https://uploadfiles.nowcoder.com/images/20230329/127966388_1680058215266/D2B5CA33BD970F64A6301FA75AE2EB22"></p><p>虽然要先查询倒排索引,再查询倒排索引,但是无论是词条、还是文档id都建立了索引,查询速度非常快!无需全表扫描。</p><ul><li>优点:根据词条搜索、模糊搜索时,速度非常快</li><li>缺点:只能给词条创建索引,而不是字段无法根据字段做排序</li></ul><p></p><h3>项目中有用到事务吗?讲一下事务</h3><p><span>数据库事务( transaction)是访问并可能操作各种</span><a href="https://gw-c.nowcoder.com/api/sparta/jump/link?link=https%3A%2F%2Fbaike.baidu.com%2Fitem%2F%25E6%2595%25B0%25E6%258D%25AE%25E9%25A1%25B9%2F3227309%3FfromModule%3Dlemma_inlink">数据项</a><span>的一个数据库操作</span><a href="https://gw-c.nowcoder.com/api/sparta/jump/link?link=https%3A%2F%2Fbaike.baidu.com%2Fitem%2F%25E5%25BA%258F%25E5%2588%2597%2F1302588%3FfromModule%3Dlemma_inlink">序列</a><span>,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。事务由事务开始与事务结束之间执行的全部数据库操作组成</span></p><h4><span> 事务的特性(ACID)</span></h4><p><span>- 原子性(Atomicity):事务是应用中不可再分的最小执行体。</span></p><p><span>- 一致性(Consistency):事务执行的结果,须使数据从一个一致性状态,变为另一个一致性状态。</span></p><p><span> - 隔离性(Isolation):各个事务的执行互不干扰,任何事务的内部操作对其他的事务都是隔离的。</span></p><p><span> - 持久性(Durability):事务一旦提交,对数据所做的任何改变都要记录到永久存储器中。</span></p><h4><span>常见的并发异常</span></h4><p>第一类丢失更新 :某一个事务的回滚, 导致另外一个事务已更新的数据丢失了。</p><p>第二类丢失更新 :某一个事务的提交, 导致另外一个事务已更新的数据丢失了。</p><p></p><p>脏读 :某一个事务, 读取了另外一个事务未提交的数据</p><p>不可重复读 :某一个事务, 对同一个数据前后读取的结果不一致</p><p>幻读 :某一个事务, 对同一个表前后查询到的行数不一致</p><h4>常见的隔离级别</h4><p>Read Uncommitted:读取未提交的数据。</p><p>Read Committed:读取已提交的数据,<span>就是只能读到已经commit了的内容。</span></p><p>Repeatable Read:可重复读。在读已提交的基础上增大锁的粒度,在事务运行中,不允许其他事物update,解决了脏读和不可重复读的问题,但是无法解决幻读。</p><p>Serializable:串行化,<span>事务“串行化顺序执行”,也就是一个一个排队执行。</span></p><p><span></span></p><h4><span>不同数据库的默认隔离级别</span></h4><h3>MySQL:</h3><p>MySQL 的默认隔离级别是 <strong>&quot;Repeatable Read&quot;(可重复读)</strong>。这意味着在同一事务中多次读取相同的数据会得到相同的结果,并且事务期间其他事务不能修改这些数据。</p><h3>Oracle:</h3><p>Oracle 的默认隔离级别是 <strong>&quot;Read Committed&quot;(读提交)</strong>。在 &quot;Read Committed&quot; 隔离级别下,事务只能看到已经提交的数据。这是 Oracle 的默认行为,但 Oracle 也提供了其他隔离级别,如 &quot;Serializable&quot;(可串行化)等。</p><p><span></span></p><p><img src="https://uploadfiles.nowcoder.com/images/20230329/127966388_1680060842991/D2B5CA33BD970F64A6301FA75AE2EB22"></p><h4>锁</h4><p>• 悲观锁(数据库)</p><p>共享锁(S锁) 事务A对某数据加了共享锁后,其他事务只能对该数据加共享锁,但不能加排他锁。</p><p>排他锁(X锁) 事务A对某数据加了排他锁后,其他事务对该数据既不能加共享锁,也不能加排他锁。</p><p> • 乐观锁(自定义)</p><p>在更新数据前,检查版本号是否发生变化。若变化则取消本次更新,否则就更新数据(版本号+1)。</p><p></p><h4>spring中使用事务</h4><p>声明式事务:通过注解,声明某方法的事务特征。</p><p><span>在 service 类上面添加注解@Transactional,在这个注解里面可以配置事务相关参数</span></p><pre> // REQUIRED: 支持当前事务(外部事务),如果不存在则创建新事务.required // REQUIRES_NEW: 创建一个新事务,并且暂停当前事务(外部事务).requires_new // NESTED: 如果当前存在事务(外部事务),则嵌套在该事务中执行(独立的提交和回滚),否则就会REQUIRED一样. @Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED) public Object save1() { // 新增用户 User user = new User(); user.setUsername(&quot;alpha&quot;); user.setSalt(CommunityUtil.generateUUID().substring(0, 5)); user.setPassword(CommunityUtil.md5(&quot;123&quot; + user.getSalt())); user.setEmail(&quot;alpha@qq.com&quot;); user.setHeaderUrl(&quot;http://image.nowcoder.com/head/99t.png&quot;); user.setCreateTime(new Date()); userMapper.insertUser(user); // 新增帖子 DiscussPost post = new DiscussPost(); post.setUserId(user.getId()); post.setTitle(&quot;Hello&quot;); post.setContent(&quot;新人报道!&quot;); post.setCreateTime(new Date()); discussPostMapper.insertDiscussPost(post); //这步报错,观测是否回滚 Integer.valueOf(&quot;abc&quot;); return &quot;ok&quot;; } </pre><p>编程式事务:通过 TransactionTemplate 管理事务, 并通过它执行数据库的操作</p><pre> public Object save2() { transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED); transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); return transactionTemplate.execute(new TransactionCallback&lt;Object&gt;() { @Override public Object doInTransaction(TransactionStatus status) { // 新增用户 User user = new User(); user.setUsername(&quot;beta&quot;); user.setSalt(CommunityUtil.generateUUID().substring(0, 5)); user.setPassword(CommunityUtil.md5(&quot;123&quot; + user.getSalt())); user.setEmail(&quot;**********&quot;); user.setHeaderUrl(&quot;http://image.nowcoder.com/head/999t.png&quot;); user.setCreateTime(new Date()); userMapper.insertUser(user); // 新增帖子 DiscussPost post = new DiscussPost(); post.setUserId(user.getId()); post.setTitle(&quot;你好&quot;); post.setContent(&quot;我是新人!&quot;); post.setCreateTime(new Date()); discussPostMapper.insertDiscussPost(post); //这步报错,观测是否回滚 Integer.valueOf(&quot;abc&quot;); return &quot;ok&quot;; } }); </pre><p></p><h3>讲一下Java线程池以及相关参数?</h3><h4>相关流程:</h4><p><img src="https://uploadfiles.nowcoder.com/images/20230329/127966388_1680072221348/D2B5CA33BD970F64A6301FA75AE2EB22"></p><h4><span>线程池主要有如下6个参数:</span></h4><ol><li>corePoolSize(核心工作线程数):当向线程池提交一个任务时,若线程池已创建的线程数小于corePoolSize,即便此时存在空闲线程,也会通过创建一个新线程来执行该任务,直到已创建的线程数大于或等于corePoolSize时。</li><li>maximumPoolSize(最大线程数):线程池所允许的最大线程个数。当队列满了,且已创建的线程数小于maximumPoolSize,则线程池会创建新的线程来执行任务。另外,对于无界队列,可忽略该参数。</li><li>keepAliveTime(多余线程存活时间):当线程池中线程数大于核心线程数时,线程的空闲时间如果超过线程存活时间,那么这个线程就会被销毁,直到线程池中的线程数小于等于核心线程数。</li><li>workQueue(工作队列):用于传输和保存等待执行任务的阻塞队列。</li><li>threadFactory(线程创建工厂):用于创建新线程。threadFactory创建的线程也是采用new Thread()方式,threadFactory创建的线程名都具有统一的风格:pool-m-thread-n(m为线程池的编号,n为线程池内的线程编号)。</li><li>handler(拒绝策略):当线程池和队列都满了,再加入线程会执行此策略</li></ol><p></p><h4>四大拒绝策略</h4><ol><li>AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。</li><li>DiscardPolicy:也是丢弃任务,但是不抛出异常。</li><li>DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复该过程)。</li><li>CallerRunsPolicy:由调用线程处理被拒绝的run方法</li></ol><p></p><h3>如何创建一个线程池?</h3><p>使用Executors相关函数进行创建</p><p>1.newFixedThreadPool:创建一个固定大小的线程池</p><pre> public class ThreadPool1 { public static void main(String[] args) { //1.创建一个大小为5的线程池 ExecutorService threadPool= Executors.newFixedThreadPool(5); //2.使用线程池执行任务一 for (int i=0;i&lt;5;i++){ //给线程池添加任务 threadPool.submit(new Runnable() { @Override public void run() { System.out.println(&quot;线程名&quot;+Thread.currentThread().getName()+&quot;在执行任务1&quot;); } }); } //2.使用线程池执行任务二 for (int i=0;i&lt;8;i++){ //给线程池添加任务 threadPool.submit(new Runnable() { @Override public void run() { System.out.println(&quot;线程名&quot;+Thread.currentThread().getName()+&quot;在执行任务2&quot;); } }); } } } </pre><p>2.newCachedThreadPool:带缓存的线程池,适用于短时间有大量任务的场景,但有可能会占用更多的资源;线程数量随任务量而定。</p><pre> public class ThreadPool3 { public static void main(String[] args) { //创建线程池 ExecutorService service= Executors.newCachedThreadPool(); //有50个任务 for(int i=0;i&lt;50;i++){ int finalI = i; service.submit(()-&gt;{ System.out.println(finalI +&quot;线程名&quot;+Thread.currentThread().getName());//线程名有多少个,CPU就创建了多少个线程 }); } } } </pre><p>3.newSingleThreadExecuto:创建单个线程的线程池</p><pre> public class ThreadPool4 { public static void main(String[] args) { ExecutorService service= Executors.newSingleThreadExecutor(); for (int i=0;i&lt;5;i++){ int finalI = i; service.submit(()-&gt;{ System.out.println(finalI +&quot;线程名&quot;+Thread.currentThread().getName());//CPU只创建了1个线程,名称始终一样 }); } } } </pre><p>4.newSingleThreadScheduledExecutor:创建执行定时任务的单个线程的线程池</p><pre> public class ThreadPool5 { public static void main(String[] args) { ScheduledExecutorService service= Executors.newSingleThreadScheduledExecutor(); System.out.println(&quot;添加任务:&quot;+ LocalDateTime.now()); service.schedule(new Runnable() { @Override public void run() { System.out.println(&quot;执行任务:&quot;+LocalDateTime.now()); } },3,TimeUnit.SECONDS);//推迟3秒执行任务 } } </pre><p></p><h3>讲一下threadlocal</h3><p><span>ThreadLocal可以解释成线程的局部变量,也就是说一个ThreadLocal的变量只有当前自身线程可以访问,别的线程都访问不了,那么自然就避免了线程竞争。</span></p><p><span></span></p><p><span>使用:</span></p><p><span>创建一个ThreadLocal对象:</span></p><pre> private ThreadLocal&lt;Integer&gt; localInt = new ThreadLocal&lt;&gt;(); </pre><p><span>上述代码创建一个localInt变量,由于ThreadLocal是一个泛型类,这里指定了localInt的类型为整数。</span></p><p><span>下面展示了如果设置和获取这个变量的值:</span></p><pre> public int setAndGet(){ localInt.set(8); return localInt.get(); } </pre><p><span>上述代码设置变量的值为8,接着取得这个值。</span></p><p><span>由于ThreadLocal里设置的值,只有当前线程自己看得见,这意味着你不可能通过其他线程为它初始化值。为了弥补这一点,ThreadLocal提供了一个withInitial()方法统一初始化所有线程的ThreadLocal的值:</span></p><pre> private ThreadLocal&lt;Integer&gt; localInt = ThreadLocal.withInitial(() -&gt; 6); </pre><p><span>上述代码将ThreadLocal的初始值设置为6,这对全体线程都是可见的。</span></p><p><span></span></p><h3>hashmap底层是如何实现的</h3><h4>hashmap定义:</h4><p><span>HashMap是用数组+单链表+红黑树实现的map类。</span>同时它的数组的默认初始容量是 16、扩容因子为 0.75,每次采用 2 倍的扩容<span>。</span></p><p>HashMap 实现了 Map 接口,根据键的 HashCode 值存储数据,具有很快的访问速度,最多允许一条记录的键为 null,不支持线程同步。</p><p>HashMap 是无序的,即不会记录插入的顺序。</p><p></p><p>HashMap的存储过程:</p><p><span>HashMap 将将要存储的值按照 key 计算其对应的数组下标,如果对应的数组下标的位置上是没有元素的,那么就将存储的元素存放上去,但是如果该位置上已经存在元素了,那么这就需要用到我们上面所说的链表存储了,将数据按照链表的存储顺序依次向下存储就可以了。</span></p><p>当链表长度大于 8 时,我们会对链表进行“树化”操作,将其转换成一颗红黑树。</p><p>但是要注意只有当链表的长度小于 6 的时候,我们才会将红黑树重新转化为链表,这个过程就叫做“链化”<span>。</span></p><p></p><h3>散列表相关的知识?</h3><p><span>散列表(hash table),我们平时叫它哈希表。散列表是根据关键码值(Key value)而直接进行访问的数据结构。</span></p><p>散列函数本质就是一个函数,我们把它定义为 hash(key),key 就是元素的键值,通过 hash 函数得到的值就是散列值。</p><p>散列函数的要求</p><p>1.散列函数不能太复杂,太复杂肯定会消耗更多的时间,从而影响散列表的性能。</p><p>2.散列函数得到的散列值尽可能随机且均匀分布,这样才能减少散列冲突,即使有冲突,每个位置对应的元素也会比较平均,不会有的特别多,而有的特别少的情况。</p><p></p><p>我们在构造哈希表的时候不可避免的会产生哈希冲突,我们有两种方法去解决这个问题:</p><p>1.开放寻址法</p><p>开发寻址法就是但我们遇到了哈希冲突,我们就重新探索一个空闲位置,然后插入。常见的开放寻址法有<span>线性探测,二次探索</span></p><p>2.链表法</p><p>链表法是一种更为常用的解决散列冲突的方法,比开放寻址法更加简单。在散列表中每个下标位置对应一个链表,所有经过散列函数得到的散列值相同的元素,我们都放到对应下标位置的链表中。</p><p><span></span></p><h3><span>如何确定两个对象是不是相同的</span></h3><p><span>使用equal,equal默认情况下与==功能相同,会去比较两个对象的地址是否相同,我们可以通过重写的方式实现对象的比较</span></p><p><span></span></p><h3>讲一下设计模式中的工厂模式以及观察者模式</h3><h4>简单工厂模式</h4><p>工厂模式有一种非常形象的描述,建立对象的类就如一个工厂,而需要被建立的对象就是一个个产品;在工厂中加工产品,使用产品的人,不用在乎产品是如何生产出来的。从软件开发的角度来说,这样就有效的<strong>降低了模块之间的耦合。</strong></p><p><img src="https://uploadfiles.nowcoder.com/images/20230329/127966388_1680090740024/D2B5CA33BD970F64A6301FA75AE2EB22"></p><h4>工厂模式</h4><p><span>定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。</span></p><p><span>具体过程:创建一个接口;创建实现接口的实体类;创建一个工厂,生成基于给定信息的实体类的对象;使用该工厂,通过传递类型信息来创建相应的工厂并且实体类的对象;</span></p><p><span></span></p><h4><span>抽象工厂模式</span></h4><p><span>抽象工厂模式(Abstract Factory Pattern)是围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。这种类型的设计模式属于创建型模式。</span><span>与工厂方法不同,奔驰的工厂不只是生产具体的某一个产品,而是一族产品</span></p><p><span></span></p><h4>工厂模式的区别</h4><ul><li>简单工厂 : 使用一个工厂对象用来生产同一等级结构中的任意产品。(不支持拓展增加产品)</li><li>工厂方法 : 使用多个工厂对象用来生产同一等级结构中对应的固定产品。(支持拓展增加产品)</li><li>抽象工厂 : 使用多个工厂对象用来生产不同产品族的全部产品。(不支持拓展增加产品;支持增加产品族)</li></ul><p></p><h4>观察者模式</h4><p><span>定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。</span></p><p><span>例如:拍卖的时候,拍卖师观察最高标价,然后通知给其他竞价者竞价。redis 哨兵模式监督主节点</span></p><p><span></span></p><p><span></span></p><p></p>
AI 推荐面试题