倒逼学习,继续沉淀

一、瑞友科技-银行外包

技术一面问题

1. Java的集合类有哪些,以及他们之间的关系是什么样的?

答:Java中的集合类主要分为四大类:List(列表)、Set(集合)、Map(映射表)和 Queue(队列),它们都是接口(interface),Java提供了基于这些接口实现的类。

  • List:列表是有序的集合,可以通过索引来访问集合中的元素,允许集合中存在重复元素。常见的实现类有 ArrayList、LinkedList 和 Vector 等。
  • Set:集合是无序的,不允许集合中存在重复的元素。常见的实现类有 HashSet、TreeSet 和 LinkedHashSet 等。
  • Map:映射表存储键值对,可以通过给出的键来访问对应的值,键不能重复。常见的实现类有 HashMap、TreeMap 和 LinkedHashMap 等。
  • Queue:队列是一种特殊的集合,它的特点是只能从集合的一端添加元素,并从另一端移除元素,遵循先进先出(FIFO)的原则。常见的实现类有 LinkedList 和 PriorityQueue 等。

这些集合类之间的关系是 Java 中继承和实现的关系。其中 List、Set、Map 和 Queue 都是集合接口(Collection)的子接口,集合接口又继承自根接口 Iterable。所有实现了 Collection 接口的类都具有集合的基本特性,如遍历、添加、删除等。因此,它们都具有一些共同的方法和属性,同时又有一些各自独特的方法和属性。

2. List集合中可以存储基本的数据类型吗?

​ 答:L**ist集合中不能直接存储基本数据类型,**而是需要转换成对应的包装类后再存储。*因为Java中的集合类都是泛型类,需要指定集合中存储的数据类型,而基本数据类型并不是对象,**不能作为Java的泛型参数,*因此需要使用对应的包装类进行转换。

例如,可以使用以下代码创建一个存储整数类型的ArrayList:

  List<Integer> list = new ArrayList<>();
   list.add(1);
   list.add(2);
   list.add(3);

在这个例子中,我们创建了一个泛型参数为Integer的ArrayList对象并向其中添加了三个整数。此时,自动将每个整数转换为对应的Integer对象,然后将对象添加到列表中。

如果需要获取列表中的整数,可以使用以下代码:

   int num = list.get(0);

这会自动将存储在列表中的Integer对象转换为对应的整数值并赋值给num变量。

需要注意的是,自动封箱和拆箱的过程可能会对程序的性能造成一定的影响,因为它涉及到额外的对象创建和销毁,因此在处理大量数据时应谨慎使用。

3. 工作中遇到哪些常见的运行时异常?

​ 答:在Java程序运行时,可能会抛出多种类型的异常,其中一些是运行时异常,也称为非受检异常(unchecked exception),它们通常是由程序的逻辑错误或错误的使用导致的。以下是工作中常见的运行时异常:

  1. NullPointerException(空指针异常):当尝试访问空对象或空对象引用的属性或方法时,会抛出该异常。
  2. ArrayIndexOutOfBoundsException(数组越界异常):当尝试访问数组中不存在的元素时,会抛出该异常。
  3. ClassCastException(类型转换异常):当尝试将一个对象强制转换为不兼容的类型时,会抛出该异常。
  4. IllegalArgumentException(非法参数异常):当调用方法时,传入了不合法的参数或无效的参数值,会抛出该异常。
  5. ArithmeticException(算术异常):当尝试执行一个不正确的数学运算时,比如除以零,会抛出该异常。
  6. ConcurrentModificationException(并发修改异常):在使用迭代器和并发集合类时,如果在迭代集合的同时进行了集合的修改操作,会抛出该异常。
  7. UnsupportedOperationException(不支持操作异常):当尝试对不支持特定操作的对象进行该操作时,会抛出该异常。

在编写Java程序时,应该尽可能避免抛出异常,特别是运行时异常,因为它们通常是可以通过改善程序逻辑或者更好的代码设计来避免的。

4. 深拷贝和浅拷贝有什么区别?

​ 答:在Java中,当一个对象被赋给另一个变量时,有两种方式可以进行拷贝:浅拷贝和深拷贝。

浅拷贝只复制对象的引用,而不是复制对象本身,因此拷贝前后对象的引用****指向同一个内存地址****,对拷贝对象的修改也会影响原对象。可以通过Object类中的clone()方法实现浅拷贝。

深拷贝则是对整个对象及其引用的对象进行拷贝因此拷贝前后两个对象是完全独立的,对拷贝对象的修改不会影响原对象。实现深拷贝的方法有很多种,包括自己实现clone()方法、序列化和反序列化等。

例如,假设有一个Person类和一个Address类,并且Person类中包含一个Address类型的成员变量, 示例代码如下:

   public class Address implements Cloneable {
      private String city;
      private String street;
   
      // 构造方法和 getter / setter 略
   }
   
   public class Person implements Cloneable {
      private String name;
      private Address address;
   
      // 构造方法和 getter / setter 略
      
      @Override
      public Object clone() throws CloneNotSupportedException {
          Person person = (Person) super.clone();
          person.address = (Address) address.clone();
          return person;
      }
   }

这里重写了Person类的clone()方法,实现了深拷贝,对于Person对象的拷贝,不仅仅拷贝了基本类型的数据,而且对于address对象也进行了拷贝。而如果Person类的clone()方法未进行重写,默认使用的是Object类的clone()方法,即进行浅拷贝。

需要注意的是,在进行深拷贝时,对象中引用的其他对象也必须支持深拷贝,否则会导致深拷贝失败。

5. 方法之间传递参数,传递的是值还是引用?

​ 答:Java 语言的参数传递只有**「按值传递」**在Java中,基本数据类型和对象类型在方法之间传递的方式是不同的。

对于基本数据类型(如int、double、char等),当它们作为参数传递给方法时,实际上是将它们的值传递给方法这意味着在方法内部对参数的修改不会影响原始的变量,因为方法中操作的是传递过来的值的副本。

对于对象类型,当它们作为参数传递给方法时,实际上是将一个引用传递给方法。这意味着方法中的操作会影响原始对象的状态,因为方法中的引用指向的是原始对象。也就是说,方法内部可以修改对象的属性和方法,这些修改是会反映到原始对象上的

需要注意的是,如果方法中修改了对象的引用,则原始对象实际上并没有被修改,只是修改了指向它的引用。例如:

   public class Person {
       private String name;
       
       public Person(String name) {
           this.name = name;
       }
       
       public void setName(String name) {
           this.name = name;
       }
       
       public String getName() {
           return name;
       }
   }
   
   public class Main {
       public static void main(String[] args) {
           Person p1 = new Person("John");
           Person p2 = new Person("Peter");
           changeName(p1, p2);
           System.out.println(p1.getName()); //输出 "John"
           System.out.println(p2.getName()); //输出 "Steve"
       }
       
       public static void changeName(Person p1, Person p2) {
           p1 = new Person("Mike");
           p2.setName("Steve");
       }
   }

在上面的例子中,changeName()方法中对p1进行了重新赋值,但并未修改原始的对象。而对p2引用的对象进行了修改,因此原始对象的状态发生了变化

7. 线程创建时有哪些状态?

​ 答:Java中的线程创建时有以下状态:

  1. 新建(New)状态:线程对象创建后,但还未启动线程时的状态,此时线程并没有分配到CPU时间片,即还没开始执行。

  2. 运行(Runnable)状态:线程对象调用start()方法后,线程就处于该状态。此时线程已经进入线程队列中,等待获取CPU时间片后执行。注意,线程并不是在此时就开始执行,要等到CPU选择该线程后,才会真正地执行线程代码。当然,在多线程环境下,线程的调度是由操作系统决定的。

  3. 阻塞(Blocked)状态:当一个正在运行的线程调用了某些需要等待结果的方法(如Thread.sleep()wait()join()等),此时该线程会进入阻塞状态。被阻塞的线程在等待某些条件满足时才能继续运行,因而不能立即执行。需要注意的是,在该状态下,线程是不会释放持有的锁的。

  4. 无限期等待(Waiting)状态:当一个线程调用了Object.wait()Thread.join()等方法时,该线程会进入等待状态,等待其它线程来唤醒。这些方法调用可以在有指定超时时间的情况下返回,也可以无限期等待。

  5. 有限期等待(Timed Waiting)状态:当一个线程调用了Thread.sleep()Object.wait(timeout)等方法时,该线程会进入有限期等待状态,在等待一定时间后会返回。需要注意的是,调用Thread.sleep()方法会使线程进入时间等待状态,但并不会释放持有的锁

  6. 结束(Terminated)状态:表示线程已经完成执行,线程执行完成后,将自动回收线程所占用的资源。

以上是线程的六种状态,并且在 Java 线程状态之间的切换是由 JVM 进行控制的。初学者可以通过 jconsole、jvisualvm 工具观察和分析自己的 Java 应用中的多线程状态水平。

8. 线程池在创建的时候需要哪些参数?

​ 答:Java中的线程池通过Executors类来创建。在创建线程池时,需要考虑以下参数:

  1. corePoolSize:线程池核心线程池的大小。当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使当前线程池中有空闲线程。如果当前线程数量小于corePoolSize,则即使有空闲线程,线程池也会新建线程来执行任务。

  2. maximumPoolSize:线程池维护线程的最大数量。当线程池中的线程数目达到corePoolSize后,依然有新任务提交,但是并不会继续创建线程,而是将新提交的任务加入到阻塞队列中。如果阻塞队列已满,则继续创建线程,直到线程数量达到maximumPoolSize

  3. keepAliveTime:当线程池中空闲线程数量超过corePoolSize时,多余线程的存活时间。即线程保持空闲的时间,在指定时间内如果没有新的任务还是会被回收掉的。

  4. unitkeepAliveTime的时间单位。

  5. workQueue:阻塞队列,用于存储等待执行任务的队列。关于阻塞队列的选择,可以根据不同的需求选择适合的阻塞队列类型。

  6. threadFactory:线程工厂,用于创建线程,这种方式可以便于线程的定制,例如可以设置线程的名称前缀、后缀等。

  7. handler:拒绝策略,线程池已经达到最大线程数量,并且阻塞队列已满,如果继续提交任务会触发拒绝策略来处理该请求

9、 数据库里事务的特性有哪些?

​ 答:数据库事务(Transaction)是指作为单个逻辑工作单元执行的一系列操作,具有以下四个特性(ACID特性):

  1. 原子性(Atomicity):事务是不可分割的最小工作单元,要么完成,要么不起作用。也就是说,整个事务中的所有操作要么全部完成,要么全部不执行,不能只执行其中的一部分操作。

  2. 一致性(Consistency):在事务开始之前和事务结束后,数据库的完整性约束没有被破坏。也就是说,事务前后的数据状态必须保持一致。

  3. 隔离性(Isolation):每个事务的操作在逻辑上是独立的,不能相互干扰。也就是说,在事务提交前,它所做的修改对其它事务不可见。隔离级别有多种,分别为读未提交(read uncommitted)、读已提交(read committed)、可重复读(repeatable read)和串行化(serializable)。

  4. 持久性(Durability):一旦事务完成,则其修改必须在所有情况下保存,不受任何失败的系统问题的影响,例如系统崩溃或停电。事务提交后,其修改即被永久保存在数据库中。

数据库的事务机制的主要目的是保证在并发访问数据库的情况下,不同的事务之间的修改互不干扰,并保证数据的一致性。要想保障数据的正确性和可靠性,ACID特性是数据库事务必须满足的基本条件。

10. 平时写SQL的时候在提高效率和SQL优化上有哪些经验吗?

​	答:写SQL时要注意一些技巧,以提高效率和优化SQL,下面列举一些常用的技巧:

1. **尽量避免使用 SELECT ***:使用 SELECT * 会查询所有列,而我们有时候并不需要所有的列,这会导致查询变慢且占用更多的内存空间。

2. 把大表放在连接子句的后面:在使用多个表进行 JOIN 操作时,把数据量大的表放在连接子句的后面,可以优化查询效率。

3. **使用 EXISTS 替代 IN**:使用 EXISTS 比使用 IN 更高效,因为 EXISTS 只检查是否存在记录,而*不需要返回全部匹配的结果集。*

4. **避免在 WHERE 子句中对列进行函数操作**:当在 WHERE 子句中对列进行函数操作时,**会导致查询优化器无法使用索引,从而使查询效率下降。**

5. **尽量使用 UNION ALL 替代 UNION**:在使用 UNION 操作时,如果可以保证两个 SELECT 的结果集中没有重复记录,就可以使用 UNION ALL 来代替 UNION,这样可以减少内存消耗。

6. **使用 LIMIT 限制结果集大小**:当查询结果集非常大时,可以使用 LIMIT 来限制结果集的大小,这样可以降低查询的内存消耗。

7. 使用覆盖索引:在有些场景下,可以使用覆盖索引替代查询,这样可以避免 MySQL 对记录的查找,提高查询效率。

8. **使用连接池**:连接池可以避免频繁地创建和关闭数据库连接,提高使用效率,同时也可以减少数据库压力。

9. **使用批量操作**:在需要同时对多条记录进行更新或删除时,通过批量操作可以减少网络传输的开销,提高数据更新或删除的效率。

总之,SQL的优化需要结合具体情况,寻找最佳的实现方式和设计思路,才能达到最优的查询效果。

11. 对于前端的HTTP请求,post请求和get请求的区别?

​	答:在前端中,HTTP请求通常有两种:GET和POST。

主要区别如下:

1. GET请求会将请求参数放在URL的QUERY STRING部分,而POST请求则会将请求参数放在请求体中。因此,GET请求就比POST请求的URL更长。

2. GET请求是幂等的,也就是说请求多次产生的结果都是一样的,不会改变服务器端的数据。而POST请求并不是幂等的,即同样的请求发送多次,服务器端的数据状态可能会发生变化。

3. GET请求通常用于**获取数据**,而POST请求通常用于**提交数据**。举个例子,GET请求可以用于查询一篇文章,而POST请求可以用于提交一篇文章。

4. **GET**请求可以**被缓存**,而POST请求不能。**因为GET请求不会对服务器端的资源产生任何副作用**,**而POST请求可能会经过一些中间的代理,例如浏览器缓存、Web服务器缓存等,这些代理可能不支持缓存POST请求。**

5. **GET请求的数据长度有限制**,**因为请求参数需要放在URL中**,**而URL的长度限制是浏览器和服务器端都可以设置的。而POST请求的数据长度较大,因为请求参数放在请求体中,不受URL长度限制。**

注意:**虽然GET请求可以用于获取数据,但不应该使用GET请求提交敏感数据,因为URL中的请求参数可能会被记录到日志中、浏览器的历史记录中、Web服务器的访问日志中,从而暴露敏感数据**。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值