Java的修行之路

mac2026-08-26  12

我的求职之路第一天

第一次发帖,文档都不会编辑了! 在哪里我就不说了!!! 坐了一个小时的地跌,走了10分钟的路程,哦哦对了,还有骑了十分钟的电动车。 来到面试地点,很好找,就在大厦一楼。打了电话,人事经理不在,叫我去找长发小姐姐,一进公司一看,我的乖乖全都是长发小姐姐,心里有一点小小的激动。O(∩_∩)O哈哈~ 好不容易找到了面试的小姐姐,长长的秀发,带着一副黑色眼眶的眼镜,身上散发着一股幽香,哎哟,跑题了,o( ̄︶ ̄)o回到正题啦。 说明我的来意后,小姐姐给我一份应聘信息表让我填写,我去,一眼看去太多了,简直就是查户口的啊,我一一填写,填到一半后,小姐姐又给了我一份Java程序员面试题,一看诶呦,全都是我看过的,哈哈,心里太激动了,我奋笔疾书,一一填写完,下面我就来写写我的面试题。答案我也找出来给大家看看,都是站里的哦!!!

**

一、怎么判断两个Java对象是否相等?

** 1、java中的基本数据类型(int、double、short、long、byte、float、boolean、char)判断是否相等,直接使用"=="就行了,相等返回true,否则,返回false。

2、但是java中的引用类型的对象比较变态,假设有两个引用对象obj1,obj2, obj1==obj2 判断是obj1,obj2这两个引用变量是否相等,即它们所指向的对象是否为同一个对象。言外之意就是要求两个变量所指内存地址相等的时候,才能返回true,每个对象都有自己的一块内存,因此必须指向同一个对象才返回ture。

3、如果想要自定义两个对象(不是一个对象,即这两个对象分别有自己的一块内存)是否相等的规则,那么必须在对象的类定义中重写equals()方法,如果不重写equals()方法的话,默认的比较方式是比较两个对象是否为同一个对象。

在Java API中,有些类重写了equals()方法,它们的比较规则是:当且仅当该equals方法参数不是 null,两个变量的类型、内容都相同,则比较结果为true。这些类包括:String、Double、Float、Long、Integer、Short、Byte、、Boolean、BigDecimal、BigInteger等等,太多太多了,但是常见的就这些了,具体可以查看API中类的equals()方法,就知道了。

重写equals()方法的步骤一般如下: 1、先用“==”判断是否相等。 2、判断equals()方法的参数是否为null,如果为null,则返回false;因为当前对象不可能为null,如果为null,则不能调用其equals()方法,否则抛java.lang.NullPointerException异常。 3、当参数不为null,则如果两个对象的运行时类(通过getClass()获取)不相等,返回false,否则继续判断。 4、判断类的成员是否对应相等。往下就随意发挥了。

———————————————— 版权声明:本文为博主「格色情调1984」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/liuhaiabc/article/details/78085193

二、请列出Java中常用的集合类,这些集合类之间有什么区别

1.Collection

是最基本的集合类型,所有实现Collection接口的类都必须提供两个标准的构造函数:无参数的构造函数用于创建一个共的Collection,有一个Collection参数的构造函数用于创建一个新的Collection,这个新的Collection与传入的Collection有相同的元素。

若要检查Collection中的元素,可以使用foreach进行遍历,也可以使用迭代器,Collection支持iterator()方法,通过该方法可以访问Collection中的每一个元素。用法如下:

Iterator it=collection.iterator(); while(it.hasNext()){ Object obj=it.next(); }

Set和List是由Collection派生的两个接口

1.1 List接口

List是有序的Collection,使用此接口能够精确的控制每个元素插入的位置。用户能够使用索引的位置来访问List中的元素,类似于Java数组。 List允许有相同的元素存在。 除了具有Collection接口必备的的iterator()方法外,还提供了listIterator()方法,放回一个 ListIterator接口。 实现List接口的常用类有LinkedList、ArrayList、Vector和Stack 1.1.1 LinkedList类 LinkedList实现了List类接口,允许null元素。此外LinkedList提供额外的get、remove、insert方法在LinkedList的首部或尾部。这些操作使LinkedList可被用作堆栈(stack),队列(queue)或双向队列(deque) LinkedList没有同步方法。如果多个线程想访问同一个List,则必须自己实现访问同步。一种解决办法是在创建List时构造一个同步的List: List list=Collection。synchronizedList(new LinkedList(…)) 1.1.2 AyyayList类 ArrayList实现了可变大小的数组。它允许所有元素,包括null。ArrayList没有同步。 size(),isEmpty(),get(),set()方法运行时间为常数。但是add()方法开销为分摊的常数,添加n个元素需要O(n)的时间。其他的方法运行时间为线性。 每个ArrayList实例都有一个容量(Capactity),即用于存储元素的数组的大小。这个容量可随着不断添加新元素而自动增加,但是增长算法并没有定义。当需要插入大量元素时,在插入之前可以调用ensureCapacity()方法来增加ArrayList容量已提高插入效率 1.2Vector类 Vector非常类似ArrayList,当时Vector是同步的。由Vector创建的iterator,虽然和ArrayLsit创建的iterator是同一接口,但是,因为Vector是同步的,当一个iterator被创建而且这在被使用,另一个线程改变了Vector状态,这时调用iterator的方法时将抛出ConcurrentModificationException,因此必须捕获该异常。 1.3 Stack类 Stack继承自Vector,实现了一个后进先出的堆栈。Stack提供了5个额外的方法使得Vector得以被当做堆栈使用。基本的push和pop方法,还有peek方法得到栈顶的元素,empty方法测试堆栈是否为空,serach方法检测一个元素在堆栈中的位置。Stack刚创建后是空栈。

1.4 Set接口 Set是一种不包含重复元素的Collection,即任意的两个元素e1和e2都有e1.equals(e2)=false,Set最多有一个null元素。 很明显,Set的构造函数有一个约束条件,传入的Collection参数不能包含重复的元素。 请注意:必须小心操作可变对象。如果一个Set中的可变元素改变了自身的状态导致Object.equals(Object)=true将导致一些问题

1.4.1 HashSet HashSet调用对象的hashCode(),获得哈希码,然后在集合中计算存放对象的位置。通过比较哈希码与equals()方法来判别是否重复。所以,重载了equals()方法同时也要重载hashCode();

1.4.2 TreeSet TreeSet 继承SortedSet接口,能够对集合中对象排序。默认排序方式是自然排序,但该方式只能对实现了Comparable接口的对象排序,java中对Integer、Byte、Double、Character、String等数值型和字符型对象都实现了该接口。

2.Map接口

Map没有继承Collection接口,Map提供key到value的映射。一个Map中不能包含相同的key,每个key只能映射一个value。Map接口提供了3中集合的视图,Map的内容可以被当作一组key集合,一组value集合,或者一组key–value映射。

2.1 HashTable类 HashTable继承Map接口,实现了一个key–value映射的哈希表。任何非空的对象都可作为key或者value。 添加数据使用put(key,value),取出数据使用get(key),这两个基本操作的时间开销为常数。 HashTable通过initial caoacity和load factor两个参数调整性能。通常缺省的load factor 0.75较好地实现了时间和空间的均衡。增大了load factor可以节省空间但相应的查找时间将增大,这回影响像get和put这样的操作 HashTable是同步的

2.2 HashMap类 HashMap和HashTable类似,不同之处在于HashMap是非同步的,并且允许null,即null value和null key,但是将HashMap视为Collection时,其迭子操作时间开销和HahMap的容量成比例。因此,如果迭代操作的性能相当重要的话,不要将HashMap的初始化容量设的过高,或者load factor过低

2.3 WeakHashMap类 WeakHashMap是一种改进的HashMap,他对key实行弱引用,如果一个key不再被外部所引用,那么该key可以被GC回收

2.4TreeMap类 基于红黑树数据结构的实现。查看“键”或“键值对”时,它们会被排序(次序由Comparabel或Comparator决定)。TreeMap的特点在于,你得到的结果是经过排序的。TreeMap是唯一的带有subMap()方法的Map,它可以返回一个子树。

三、Get、Post的区别

HTTP定义了与服务器交互的不同方法,最常用的方法有四种Put,Delete、post,get,即增删改查。

1.Get,它用于获取信息,它只是获取、查询数据,也就是说它不会修改服务器上的数据,从这点来讲,它是数据安全的,而稍后会提到的Post它是可以修改数据的,所以这也是两者差别之一了。

2.Post,它是可以向服务器发送修改请求,从而修改服务器的,比方说,我们要在论坛上回贴、在博客上评论,这就要用到Post了,当然它也是可以仅仅获取数据的。

3.Delete 删除数据。可以通过Get/Post来实现。用的不多。

4.Put,增加、放置数据,可以通过Get/Post来实现。用的不多。

标准答案: 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://blog.csdn.net/qq_41939384/article/details/87628188

四、对spring的理解,项目中都用什么?怎么用的?对IOC和AOP的理解及实现原理

项目中都用什么?我都不清楚。。。。还怎么用的。

IOC(Inverse of Control):控制反转,也可以称为依赖倒置。

所谓依赖,从程序的角度看,就是比如A要调用B的方法,那么A就依赖于B,反正A要用到B,则A依赖于B。所谓倒置,你必须理解如果不倒置,会怎么着,因为A必须要有B,才可以调用B,如果不倒置,意思就是A主动获取B的实例:B b = new B(),这就是最简单的获取B实例的方法(当然还有各种设计模式可以帮助你去获得B的实例,比如工厂、Locator等等),然后你就可以调用b对象了。所以,不倒置,意味着A要主动获取B,才能使用B;到了这里,就应该明白了倒置的意思了。倒置就是A要调用B的话,A并不需要主动获取B,而是由其它人自动将B送上门来。 形象的举例就是: 通常情况下,假如你有一天在家里口渴了,要喝水,那么你可以到你小区的小卖部去,告诉他们,你需要一瓶水,然后小卖部给你一瓶水!这本来没有太大问题,关键是如果小卖部很远,那么你必须知道:从你家如何到小卖部;小卖部里是否有你需要的水;你还要考虑是否开着车去;等等等等,也许有太多的问题要考虑了。也就是说,为了一瓶水,你还可能需要依赖于车等等这些交通工具或别的工具,问题是不是变得复杂了?那么如何解决这个问题呢? 解决这个问题的方法很简单:小卖部提供送货上门服务,凡是小卖部的会员,你只要告知小卖部你需要什么,小卖部将主动把货物给你送上门来!这样一来,你只需要做两件事情,你就可以活得更加轻松自在: 第一:向小卖部注册为会员。 第二:告诉小卖部你需要什么。

这和Spring的做法很类似!Spring就是小卖部,你就是A对象,水就是B对象 第一:在Spring中声明一个类:A 第二:告诉Spring,A需要B

假设A是UserAction类,而B是UserService类

<bean id="userService" class="org.leadfar.service.UserService"/> <bean id="documentService" class="org.leadfar.service.DocumentService"/> <bean id="orgService" class="org.leadfar.service.OrgService"/> <bean id="userAction" class="org.leadfar.web.UserAction"> <property name="userService" ref="userService"/> </bean>

在Spring这个商店(工厂)中,有很多对象/服务:userService,documentService,orgService,也有很多会员:userAction等等,声明userAction需要userService即可,Spring将通过你给它提供的通道主动把userService送上门来,因此UserAction的代码示例类似如下所示:

package org.leadfar.web; public class UserAction{ private UserService userService; public String login(){ userService.valifyUser(xxx); } public void setUserService(UserService userService){ this.userService = userService; } }

在这段代码里面,你无需自己创建UserService对象(Spring作为背后无形的手,把UserService对象通过你定义的setUserService()方法把它主动送给了你,这就叫依赖注入!),当然咯,我们也可以使用注解来注入。Spring依赖注入的实现技术是:动态代理

AOP:即面向切面编程

面向切面编程的目标就是分离关注点。什么是关注点呢?就是你要做的事,就是关注点。假如你是个公子哥,没啥人生目标,天天就是衣来伸手,饭来张口,整天只知道玩一件事!那么,每天你一睁眼,就光想着吃完饭就去玩(你必须要做的事),但是在玩之前,你还需要穿衣服、穿鞋子、叠好被子、做饭等等等等事情,这些事情就是你的关注点,但是你只想吃饭然后玩,那么怎么办呢?这些事情通通交给别人去干。在你走到饭桌之前,有一个专门的仆人A帮你穿衣服,仆人B帮你穿鞋子,仆人C帮你叠好被子,仆人C帮你做饭,然后你就开始吃饭、去玩(这就是你一天的正事),你干完你的正事之后,回来,然后一系列仆人又开始帮你干这个干那个,然后一天就结束了!

AOP的好处就是你只需要干你的正事,其它事情别人帮你干。也许有一天,你想裸奔,不想穿衣服,那么你把仆人A解雇就是了!也许有一天,出门之前你还想带点钱,那么你再雇一个仆人D专门帮你干取钱的活!这就是AOP。每个人各司其职,灵活组合,达到一种可配置的、可插拔的程序结构。

从Spring的角度看,AOP最大的用途就在于提供了事务管理的能力。事务管理就是一个关注点,你的正事就是去访问数据库,而你不想管事务(太烦),所以,Spring在你访问数据库之前,自动帮你开启事务,当你访问数据库结束之后,自动帮你提交/回滚事务! ———————————————— 版权声明:本文为博主「mmh_csdn」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/mmh_csdn/article/details/79535872

讲的通俗易懂,很不错!!!!

五、你知道哪些缓存,该如何选择?

当时看到这个题,大脑当机了一会,我擦,还缓存。。。。 缓存:就是存贮数据(使用频繁的数据)的临时地方,因为取原始数据的代价太大了,所以我可以取得快一些

为什么我们需要缓存?

很久很久以前,在还没有缓存的时候……用户经常是去请求一个对象,而这个对象是从数据库去取,然后,这个对象变得越来越大,这个用户每次的请求时间也越来越长了,这也把数据库弄得很痛苦,他无时不刻不在工作。所以,这个事情就把用户和数据库弄得很生气,接着就有可能发生下面两件事情:

1.用户很烦,在抱怨,甚至不去用这个应用了(这是大多数情况下都会发生的)

2.数据库为打包回家,离开这个应用,然后,就出现了大麻烦(没地方去存储数据了)(发生在极少数情况下)

上帝派来了缓存

在几年之后,IBM(60年代)的研究人员引进了一个新概念,它叫“缓存”。

什么是缓存?

正如开篇所讲,缓存是“存贮数据(使用频繁的数据)的临时地方,因为取原始数据的代价太大了,所以我可以取得快一些。”

缓存可以认为是数据的池,这些数据是从数据库里的真实数据复制出来的,并且为了能别取回,被标上了标签(键 ID)。太棒了

programmer one 已经知道这点了,但是他还不知道下面的缓存术语。 后面还有好多,不写了!给个链接看看

版权声明:本文为博主「s277738720」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/s277738720/article/details/84809466

六、java之数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。

题目:

数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。

思路:

先用Boyer-Moore Majority Vote Algorithm算法,即多数投票问题,找到序列中出现次数超过一半的的元素

再判断题目的条件,看是否出现次数超过yi一半,超过则存在,不超过则不存在

Boyer-Moore Majority Vote Algorithm算法:

使用 count来统计一个元素出现的次数,当遍历到的元素和统计元素相等时,令 count++,否则令 count–。如果前面查找了 i 个元素,且 count == 0,说明前 i 个元素没有 most,或者有 most,但是出现的次数少于 i / 2 ,因为如果多于 i / 2 的话 count就一定不会为 0 。此时剩下的 n - i 个元素中,most 的数目依然多于 (n - i) / 2,因此继续查找就能找出 majority。

代码:

public class Solution { public int MoreThanHalfNum_Solution(int [] array) { //养成好的习惯,开始的时候一定要判断为空的情况 if(array==null||array.length==0) { return 0; } //Boyer-Moore Majority Vote Algorithm算法 //找到序列中出现次数最多的元素 int most=array[0]; int count=1; for(int i=1;i<array.length;i++) { count=most==array[i]?count+1:count-1; if(count==0) { most=array[i]; count=1; } } //判断题目的条件,看是否出现次数超过了一半 count=0; for(int i=0;i<array.length;i++) { if(most==array[i]) { count++; } } return count>array.length/2?most:0; } }

写的很精辟!!!!!借用一下下。复习专用!

七、如何分析一个SQL语句的执行性能并给出优化方案

如何写出高性能的SQL语句,及如何进行SQL性能分析与调优 本文链接:https://blog.csdn.net/weixin_41660162/article/details/86429175 这个也是很详细的文章,可以看看,很好!!!很强大。

八、什么是悲观锁、乐观锁,应用场景是什么?

悲观锁 & 乐观锁的原理及应用场景 本文链接:https://blog.csdn.net/wx1528159409/article/details/88318139

1. 悲观锁

顾名思义就是很悲观,每次拿数据都会认为别的线程会修改该数据,所以会给数据上锁;

这样抢到锁的线程运行,取到数据做操作,

这期间其他线程想要访问该数据时,都是阻塞block挂起状态,操作不了;

核心就是不支持多并发,是单线程操作,通过抢占时间片的方式来抢锁的使用权,把并发变成了串行。

共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程。

应用场景: 悲观锁适用于多写的场景,保证线程安全和数据安全

mysql的行锁、表锁、读锁、写锁;

java中的synchronized。

2. 乐观锁

顾名思义就是很乐观,每次拿数据都认为别的线程不会修改数据,因此不会给数据上锁;

但会在数据更新时判断一下,在此期间其他线程有没有对该数据做更新,最终通过多个线程的逐一更新获取数据的最终值;

判断单一线程操作数据期间,其他线程有没有对该数据做修改用的是,version版本号机制、CAS算法。

乐观锁支持多线程并发,每个线程在不同的时间节点对数据做更新操作,每次更新时候都会判断其他线程是否对数据做了更新。

(1)version版本号机制

一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。

当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。

sql源码:

update table set x=x+1, version=version+1 where id=#{id} and version=#{version};

(2)CAS算法机制

即compare and swap 或者 compare and set,涉及到三个操作数,数据所在的内存值,预期值,新值。当需要更新时,判断当前内存值与之前取到的值是否相等,若相等,则用新值更新,若失败则重试,一般情况下是一个自旋操作,即不断的重试。

应用场景: 乐观锁适用于多读的场景,获取数据不再创建、销毁锁,减少了锁的开销,加大了数据的吞吐量,

Redis等非关系型数据库

ps:Redis是单线程操作,把事务封闭在单一线程中,避免了线程的安全问题,所以里面没有加悲观锁; 不过对于依赖多个Redis操作的复合操作来说,还是需要加锁的,而且有可能是分布式锁,也可以用LUA脚本,用任务队列的方式解决多任务并发的问题。

以上就是我面试的题目及答案。

后面项目经理还问了我很多项目上的问题,我也一一列出,算自我复习了。

1. List集合是怎么排序的

a.自然排序

List<Integer> list = new ArrayList<Integer>(); list.add(3); list.add(1); list.add(2); // 自然顺序 Collections.sort(list); System.out.println(list.toString());

运行结果:

[1, 2, 3]

b.List[Map]依据map的key值排序 使用Collections.sort排序,原理和冒泡排序相同。

List<Map<String, Object>> list = new ArrayList<Map<String, Object>>(); Map<String, Object> map1 = new HashMap<String, Object>(); map1.put("name", "p"); map1.put("cj", "5"); Map<String, Object> map2 = new HashMap<String, Object>(); map2.put("name", "h"); map2.put("cj", "36"); Map<String, Object> map3 = new HashMap<String, Object>(); map3.put("name", "f"); map3.put("cj", "31"); list.add(map1); list.add(map2); list.add(map3); // 排序前 for (Map<String, Object> map : list) { System.out.println(map.get("cj")); } Collections.sort(list, new Comparator<Map<String, Object>>() { @Override public int compare(Map<String, Object> o1, Map<String, Object> o2) { //冒泡排序法 Integer sort1 = Integer.valueOf(o1.get("cj").toString());// sort1是从list里面拿出来的前一个 Integer sort2 = Integer.valueOf(o2.get("cj").toString()); // sort2是从list里面拿出来的后一个 return sort1.compareTo(sort2); } }); System.out.println("-------------------"); // 排序后 for (Map<String, Object> map : list) { System.out.println(map.get("cj")); }

运行结果:

5 36 31 ------------------- 5 31 36

2.事务是什么,在什么时候用到事务?

事务是为解决数据安全操作提出的,事务控制实际上就是控制数据的安全访问。 事务必须服从ISO/IEC所制定的ACID原则。ACID是原子性(atomicity)、一致性(consistency)、隔离性 (isolation)和持久性(durability)的缩写。

事务的原子性:表示事务执行过程中的任何失败都将导致事务所做的任何修改失效。 事务的一致性:表示当事务执行失败时,所有被该事务影响的数据都应该恢复到事务执行前的状态。 事务的隔离性:表示在事务执行过程中对数据的修改,在事务提交之前对其他事务不可见。 事务的持久性:表示已提交的数据在事务执行失败时,数据的状态都应该正确。

通俗的理解,事务是一组原子操作单元,从数据库角度说,就是一组SQL指令,要么全部执行成功,若因为某个原因其中一条指令执行有错误,则撤销先前执行过的所有指令。更简答的说就是:要么全部执行成功,要么撤销不执行。

与事务相关的理论

1.事务(Transaction)的四个属性(ACID)

原子性(Atomic) 对数据的修改要么全部执行,要么全部不执行。 一致性(Consistent) 在事务执行前后,数据状态保持一致性。 隔离性(Isolated) 一个事务的处理不能影响另一个事务的处理。 持续性(Durable) 事务处理结束,其效果在数据库中持久化。

2.事务并发处理可能引起的问题

脏读(dirty read):一个事务读取了另一个事务尚未提交的数据, 不可重复读(non-repeatable read) :一个事务的操作导致另一个事务前后两次读取到不同的数据 幻读(phantom read) :一个事务的操作导致另一个事务前后两次查询的结果数据量不同。

举例:

事务A、B并发执行时,当A事务update后,B事务select读取到A尚未提交的数据,此时A事务rollback,则B读到的数据是无效的”脏”数据。 当B事务select读取数据后,A事务update操作更改B事务select到的数据,此时B事务再次读去该数据,发现前后两次的数据不一样。 当B事务select读取数据后,A事务insert或delete了一条满足A事务的select条件的记录,此时B事务再次select,发现查询到前次不存在的记录(“幻影”),或者前次的某个记录不见了。

看看原文链接,帮助很大。 注:原文链接:http://blog.csdn.net/sinat_33536912/article/details/51200630

还有一些小的问题,我就不说了,自己太嘴贱了,说自己了解Redis,最后问个Redis的缓存穿透就崩了,哈哈,路还很远,继续努力中。。。。。

最新回复(0)