2025年JAVA最新面试题

2023年JAVA最新面试题

1 JavaWeb基础

1.1 HashMap的底层实现原理?

HashMap的底层实现原理?以jdk7为例说明:
HashMap map = new HashMap();
在实例化以后,底层创建了长度是16的一维数组Entry[] table。 可能已经执行过多次putmap.put(key1,value1):

  • 首先调用key1所在类的hashCode()计算key1哈希值,此哈希值经过某种算法计算以后,得到在Entry数组中的存放位置。
    • 如果此位置上的数据为空,此时的key1-value1添加成功。 ----情况1
    • 如果此位置上的数据不为空,(意味着此位置上存在一个或多个数据(以链表形式存在))
      比较key1和已经存在的一个或多个数据的哈希值:
      • 如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1添加成功。----情况2
      • 如果key1的哈希值和已经存在的某一个数据(key2-value2)的哈希值相同,继续比较:调用key1所在类的equals(key2)方法,比较:
        • 如果equals()返回false:此时key1-value1添加成功。----情况3
        • 如果equals()返回true:使用value1替换value2
        • 补充:关于情况2和情况3:此时key1-value1和原来的数据以链表的方式存储。

在不断的添加过程中,会涉及到扩容问题,当超出临界值(且要存放的位置非空)时,扩容。默认的扩容方式:扩容为原来容量的2倍,并将原有的数据复制过来。
jdk8 相较于jdk7在底层实现方面的不同:

    1. new HashMap()jdk8底层没有创建一个长度为16的数组
    1. jdk8底层的数组是:Node[],而非Entry[]
    1. 首次调用put()方法时,底层创建长度为16的数组
    1. jdk7底层结构只有:数组+链表。jdk8中底层结构:数组+链表+红黑树。
    • 4.1 形成链表时,七上八下(jdk7:新的元素指向旧的元素。jdk8:旧的元素指向新的元素)
    • 4.2 jdk8中当数组的某一个索引位置上的元素以链表形式存在的数据个数 > 8 且当前数组的长度 > 64时,此时此索引位置上的所数据改为使用红黑树存储。
      DEFAULT_INITIAL_CAPACITY : HashMap的默认容量,16
      DEFAULT_LOAD_FACTORHashMap的默认加载因子:0.75
      threshold:扩容的临界值,=容量*填充因子:16 * 0.75 => 12
      TREEIFY_THRESHOLDBucket中链表长度大于该默认值,转化为红黑树:8
      MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量:64
      

1.2 HashMap 和 HashTable的异同?

HashMap:作为Map的主要实现类;线程不安全的,效率高;存储null的key和value,扩容的话是原来的二倍
Hashtable:作为古老的实现类;线程安全的,效率低;不能存储null的key和value
扩容的话是原来的二倍+1
Propertie:常用来处理配置文件。key和value都是String类型
HashMap的底层:数组+链表 (jdk7及之前) , 数组+链表+红黑树 (jdk 8)

相同点:
(1) 都是java.util包下的类
(2) 都实现了Map接口,存储方式都是key-value形式
(3) 同时也都实现了Serializable和Cloneable接口
(4) 负载因子都是0.75

负载因子(loadFactor):

当我们第一次创建 HashMap 的时候,就会指定其容量(如果未明确指定,默认是 16),随着我们不断的向 HashMap 中 put 元素的时候,就有可能会超过其容量,那么就需要有一个扩容机制。 所谓扩容,就是扩大 HashMap 的容量,在向 HashMap中添加元素过程中,如果 元素个数(size)超过临界值(threshold)的时候,就会进行自动扩容(resize),并且,在扩容之后,还需要对 HashMap 中原有元素进行rehash,即将原来桶中的元素重新分配到新的桶中。
在 HashMap 中,临界值(threshold) = 负载因子(loadFactor) * 容量(capacity)。 loadFactor 是装载因子(负载因子),表示 HashMap 满的程度,默认值为 0.75f,也就是说默认情况下,当 HashMap 中元素个数达到了容量的 3/4 的时候就会进行自动扩容。

不同点:
(1) HashMap是非线程安全,效率高;HashTable是线程安全的,效率低
(2) HashMap允许null作为键或值,HashTable不允许,运行时会报NullPointerException
(3) HashMap添加元素使用的是自定义hash算法,HashTable使用的是keyhashCode
(4) HashMap在数组+链表的结构中引入了红黑树,HashTable没有
(5) HashMap初始容量为16,HashTable初始容量为11
(6) HashMap扩容是当前容量翻倍,HashTable是当前容量翻倍+1
(7) HashMap只支持Iterator遍历,HashTable支持IteratorEnumeration
(8) HashMapHashTable的部分方法不同,比如HashTablecontains方法。

1.2 HashMap的线程不安全主要体现在下面两个方面:

  • 在JDK1.7中,当并发执行扩容操作时会造成环形链和数据丢失的情况。
  • 在JDK1.8中,在并发执行put操作时会发生数据覆盖的情况。

1.3 Collection 和 Collections的区别?

相同点:两者都可以在java.util包的类
不同点

  • Collection是接口
  • Collections是工具类

1.4 Collection和Map接口的区别

  • Collection接口:单列集合,用来存储一个一个的对象

    • List接口:存储有序的、可重复的数据。 -->“动态”数组,替换原的数组

      • ArrayList:作为List接口的主要实现类;线程不安全的,效率高;底层使用Object[ ] elementData存储,适用于查找频繁。
      • LinkedList:线程不安全的。对于频繁的插入、删除操作,使用此类效率比ArrayList高;底层使用双向链表存储
      • Vector:作为List接口的古老实现类;线程安全的,效率低;底层使用Object[ ] elementData存储
    • Set接口:存储无序的、不可重复的数据 -->高中讲的“集合”(无序互斥)

      • HashSet:作为Set接口的主要实现类;线程不安全的;可以存储null值
      • LinkedHashSet:作为HashSet的子类;遍历其内部数据时,可以按照添加的顺序遍历
        在添加数据的同时,每个数据还维护了两个引用,记录此数据前一个数据和后一个数据。 对于频繁的遍历操作,LinkedHashSet效率高于HashSet
      • TreeSet:可以照添加对象的指定属性,进行排序。
  • Map:双列数据,存储key-value对的数据 —类似于高中的函数:y = f(x)

    • HashMap:作为Map的主要实现类;线程不安全的,效率高;存储nullkeyvalue 扩容的话是原来的二倍
    • LinkedHashMap:保证在遍历map元素时,可以照添加的顺序实现遍历。
      原因:在原的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素。
      对于频繁的遍历操作,此类执行效率高于HashMap
    • TreeMap:保证照添加的key-value对进行排序,实现排序遍历。此时考虑key的自然排序或定制排序
      底层使用红黑树
    • HashTable:作为古老的实现类;线程安全的,效率低;不能存储nullkeyvalue扩容的话是原来的二倍+1
    • Properties:常用来处理配置文件。keyvalue都是String类型

1.5 ArrayList、LinkedList的异同?

ArrayList和LinkedList的异同
相同点:

  • 都实现了List接口, 都是存储有序、可重复的数据
  • 二者都线程不安全,相对线程安全的Vector,执行效率高。
  • ArrayList基于动态数组的数据结构,LinkedList基于链表的数据结构。

不同点

  • 对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。
  • 对于新增和删除操作add(特指插入)和remove,LinkedList比较占优势,因为ArrayList要移动数据。

1.6 HashMap怎么解决hash冲突

(1)什么是hash冲突?
哈希算法被计算的数据是无限的,而计算后的结果范围有限,所以总会存在不同的数据经过计算后得到的值相同,这就是哈希冲突
(2)HashMap如何解决hash冲突?

  • 线性探测法
    也称为开放定址法,就是从发生冲突的那个位置开始,按照一定的次序从hash表中找到一个空闲的位置,然后把发生冲突的元素存入到这个空闲位置中。
  • 链式寻址法
    这是一种非常常见的方法,简单理解就是把存在hash冲突的key,以单向链表的方式来存储,比如HashMap就是采用链式寻址法来实现的。
  • 再hash法
    就是当通过某个hash函数计算的key存在冲突时,再用另外一个hash函数对这个key做hash,一直运算直到不再产生冲突。这种方式会增加计算时间,性能影响较大。
  • 建立公共溢出区
    就是把hash表分为基本表和溢出表两个部分,凡事存在冲突的元素,一律放入到溢出表中。

1.7 String、StringBuffer、StringBuilder三者的对比

  • String:不可变的字符序列;底层使用char[ ]存储
  • StringBuffer:可变的字符序列;线程安全的,效率低;底层使用char[]存储
  • StringBuilder:可变的字符序列;jdk5.0新增的,线程不安全的,效率高;底层使用char[]存储

1.8 String长度为什么不可变

(1)不可变性:当你给一个字符串重新赋值之后,老值并没有在内存中销毁,而是重新开辟一块空间存储新值。
(2)String 类中使用 final 关键字修饰字符数组来保存字符串,private final char value[ ],所以String 对象是不可变的。
(3)线程安全

1.9 JDK1.8新增的功能

  • 1、Lambda表达式和函数式接口
    Lambda表达式也称为闭包,是Java 8中最大和最令人期待的语言改变。它允许我们将函数当成参数传递给某个方法,或者把代码本身当作数据处理,这就是典型的函数式开发。最简单的Lambda表达式可由逗号分隔的参数列表、-> 符号和语句块组成。

在Lambda表达式中,将其划分了几块。这一行就是lambda表达式。
() -> System.out.println(“使用Lambda表达式”);下面我们对lambda的格式进行一个介绍:

(1)左边括号:lambda的形参列表,就好比是我们定义一个接口,里面有一个抽象方法,这个抽象方法的形参列表。

(2)箭头:lambda的操作符,所以你看见这个箭头心中知道这是一个lambda表达式就可以了。

(3)右边lambda体:就好比是我们实现了接口中的抽象方法。

  • 2、接口的默认方法和静态方法
    默认方法使得开发者可以在不破坏二进制兼容性的前提下,往现存接口中添加新的方法,即不强制那些实现了该接口的实现类也同时实现这个新添加的方法。
    简单说,默认方法就是接口可以有实现方法,而且不需要实现类去实现其方法。

  • 3、方法引用
    方法引用使得开发者可以直接引用现存的方法、Java类的构造方法或者实例对象。方法引用和Lambda表达式配合使用,使得java类的构造方法看起来更加紧凑简洁,减少冗余代码。
    方法引用通过方法的名字来指向一个方法
    方法引用使用一对冒号 ::

    public class Test {
         
        public static void main(String[] args) {
         
    
            //传统的方式来实现MyFunction/得到一个实现接口的对象 可以使用
            //匿名内部类
            //MyFunction<Desk, String> hf = new MyFunction<Desk, String>() {
         
            //    @Override
            //    public String apply(Desk desk) {
         
            //        return "hello desk";
            //    }
            //};
            //String val = hf.apply(new Desk());
            //System.out.println("val-" + val);
    
            MyFunction<Desk,String> hf2 = Desk::getBrand;
    
            String val2 = hf2.apply(new Desk());
            System.out.println("val2-" + val2);//这里输出结果就是val2-北京牌
        }
    }
    
    //定义一个函数式接口: 有且只有一个抽象方法的接口
    //我们可以使用@FunctionalInterface 来标识一个函数式接口
    //MyFunction是一个函数式接口 (是自定义泛型接口)
    
    @FunctionalInterface
    interface MyFunction<T, R> {
         
        R apply(T t); //抽象方法: 表示根据类型T的参数,获取类型R的结果
    
        //public void hi();
    
        //函数式接口,依然可以有多个默认实现方法
        default public void ok() {
         
            System.out.println("ok");
        }
    }
    @FunctionalInterface
    interface MyInterface {
         
        public void hi();
    }
    
    class Desk {
          //Bean
        private String name = "my desk";
        private String brand = "北京牌";
        private Integer id = 10;
    	//getter   setter  tostring  方法
    }
    
  • 4、重复注解
    在Java 5中使用注解有一个限制,即相同的注解在同一位置只能声明一次,Java 8引入重复注解,这样相同的注解在同一地方也可以声明多次;Java 8在编译器层做了优化,相同注解会以集合的方式保存,因此底层的原理并没有变化。

  • 5、扩展注解的支持
    Java 8扩展了注解的上下文,几乎可以为任何东西添加注解,包括局部变量、泛型类、父类与接口的实现,连方法的异常也能添加注解。

  • 6、Optional
    Optional 类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。
    Optional 是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。
    Java应用中最常见的bug就是空指针异常,Optional 类的引入很好的解决空指针异常,从而避免源码被各种null检查污染,以便开发者写出更加整洁的代码。

  • 7、Stream(流)

什么是 Stream?

Stream API(java.util.stream)是把真正的函数式编程风格引入到Java库中,这是目前为止最大的一次对Java库的完善,以便开发者能够写出高效率、干净、简洁的代码。其实简单来说可以把Stream理解为MapReduce,当然Google的MapReduce的灵感也是来自函数式编程,它其实是一连串支持连续、并行聚集操作的元素,从语法上看,也很像linux的管道、或者链式编程,代码写起来简洁明了!
Stream(流)是一个来自数据源的元素队列并支持聚合操作,Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对
Java 集合运算和表达的高阶抽象。这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选,
排序,聚合等。元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal
operation)得到前面处理的结果

  • 8、日期时间 API
    Java 8通过发布新的Date-Time API (JSR 310)来进一步加强对日期与时间的处理。在旧版的 Java 中,日期时间 API 存在诸多问题,其中有:
    非线程安全 java.util.Date 是非线程安全的,所有的日期类都是可变的,这是Java日期类最大的问题之一。
    设计很差 Java的日期/时间类的定义并不一致,在java.util和java.sql的包中都有日期类,此外用于格式化和解析的类在java.text包中定义。java.util.Date同时包含日期和时间,而java.sql.Date仅包含日期,将其纳入java.sql包并不合理。另外这两个类都有相同的名字,这本身就是一个非常糟糕的设计。
    时区处理麻烦 日期类并不提供国际化,没有时区支持,因此Java引入了java.util.Calendar和java.util.TimeZone类,但他们同样存在上述所有的问题。
    Java8 在 java.time 包下提供了很多新的 API,新的java.time包涵盖了所有处理日期,时间,日期/时间,时区,时刻(instants),过程(during)与时钟(clock)的操作。以下仅介绍两个比较重要的 API:

    • Local(本地): 简化了日期时间的处理,没有时区的问题。
    • Zoned(时区) : 通过制定的时区处理日期时间。
  • 9、JavaScript引擎Nashorn
    从 JDK 1.8 开始,Nashorn取代Rhino(JDK 1.6, JDK1.7) 成为 Java 的嵌入式 JavaScript 引擎。Nashorn 完全支持 ECMAScript 5.1 规范以及一些扩展,它使用基于 JSR 292 的新语言特性,其中包含在 JDK 7 中引入的 invokedynamic,将 JavaScript 编译成 Java 字节码,与先前的 Rhino 实现相比,这带来了 2 到 10倍的性能提升。
    Nashorn的主要用途:允许在JVM上开发运行JavaScript应用,允许Java与JavaScript相互调用。

  • 10、Base64
    在Java 8中,Base64编码成为了Java类库的标准。Base64类同时还提供了对URL、MIME友好的编码器与解码器。
    除了这十大新特性之外,还有另外的一些新特性:

  • 11、更好的类型推测机制:
    Java 8在类型推测方面有了很大的提高,这就使代码更整洁,不需要太多的强制类型转换了。

  • 12、编译器优化:
    Java 8将方法的参数名加入了字节码中,这样在运行时通过反射就能获取到参数名,只需要在编译时使用-parameters参数。

  • 13、并行(parallel)数组:
    支持对数组进行并行处理,主要是parallelSort()方法,它可以在多核机器上极大提高数组排序的速度。

  • 14、并发(Concurrency):
    在新增Stream机制与Lambda的基础之上,加入了一些新方法来支持聚集操作。

  • 15、Nashorn引擎jjs:
    基于Nashorn引擎的命令行工具。它接受一些JavaScript源代码为参数,并且执行这些源代码。

  • 16、类依赖分析器jdeps:
    可以显示Java类的包级别或类级别的依赖。

  • 17、JVM的PermGen空间被移除:
    取代它的是Metaspace(JEP 122)。

1.10 字符流和字节流

  • 字符流 getWriter(): 常用于回传字符串(常用)
  • 字节流 getOutputStream():常用于下载(传递二进制数据)

两个流同时只能使用一个。 使用了字节流,就不能再使用字符流,反之亦然,否则就会报错

1.11 序列化和反序列化

  • 序列化:将 Java 对象转换成字节流的过程
  • 反序列化:将字节流转换成 Java 对象的过程。

为什么需要序列化与反序列化?

当两个进程进行远程通信时,可以相互发送各种类型的数据,包括文本、图片、音频、视频等, 而这些数据都会以二进制序列的形式在网络上传送。当两个
Java 进程进行通信时,需要 Java 序列化与反序列化实现进程间的对象传送。换句话说,一方面,发送方需要把这个 Java
对象转换为字节序列,然后在网络上传送;另一方面,接收方需要从字节序列中恢复出 Java 对象。

优点:

  • 实现了数据的持久化,序列化可以把数据永久地保存到硬盘上(通常存放在文件里)。
  • 通过序列化以字节流的形式使对象在网络中进行传递和接收。
  • 通过序列化在进程间传递对象。

1.12 多线程有几种实现方法,都是什么?同步有几种实现方法,都是什么?

多线程有两种实现方法,

  • 继承Thread 类
  • 实现Runnable 接口,

同步的实现方面有两种

  • synchronized,wait
  • notify。

1.13 创建线程安全(arraylist为例)

在线程上添加锁。Lock 或者synchronized 可以将线程不安全变为线程安全,
Lock 能完成synchronized 所实现的所有功能;
Collections.synchronizedList();也能实现线程安全
主要不同点:

  • Lock 有比synchronized 更精确的线程语义和更好的性能。
  • synchronized 会自动释放锁,而Lock 一定要求程序员手工释放,并且必须在finally 从句中释放。

1.14 创建对象除了new 还有什么方式?

  • 使用new关键字

  • Class对象的newInstance()方法
    Class的newInstance()方法可以在运行时创建一个类的新实例。它等效于使用new操作符,但是语法更加动态。

  • 构造函数对象的newInstance()方法
    Constructor的newInstance()方法可以在运行时创建一个类的新实例,并且可以传入构造函数的参数。这种方式比Class的newInstance()方法更加灵活,因为可以选择不同的构造函数。

  • 对象反序列化
    反序列化是将对象从字节流中恢复的过程。通过序列化后,可以把对象存储到文件或网络中,然后再通过反序列化的方式恢复成对象。

  • Object对象的clone()方法
    clone( )方法可以创建对象的一个副本,并且可以重写clone()方法来实现深克隆。

  • 使用工厂模式
    可以将对象的创建和使用解耦。通过定义一个对象工厂,可以更加灵活地产生对象。

1.15 什么是死锁?死锁产生的必要条件?怎么避免死锁?

1)死锁定义:
死锁是指两个或多个进程在同一资源上相互占用,并请求锁定对方的资源,从而导致恶性循环的现象。

2)产生死锁的必要条件

  • 互斥条件:进程要求对所分配的资源(如打印机)在一段时间内仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。
  • 不可剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能由获得该资源的进程自己来释放(只能是主动释放)。
  • 请求和保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。
  • 循环等待条件:存在一种进程资源的循环等待链,链中每一个进程已获得的资源同时被链中下一个进程所请求。

3)怎么避免死锁

  • 如果不同程序会并发存取多个表,尽量约定以相同的顺序访问表,可以大大降低死锁机会。
  • 在同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁产生概率。
  • 对于非常容易产生死锁的业务部分,可以尝试使用升级锁定颗粒度,通过表级锁定来减少死锁产生的概率。
  • 如果业务处理不好,可以用分布式事务锁或者使用乐观锁。

1.16 Java 中常用的设计模式?说明工厂模式?

答:Java 中的23 种设计模式:Builder( 建造模式),Factory( 工厂模式),Factory Method(工厂方法模式),Prototype(原始模型模式),Singleton(单例模式),Facade(门面模式),Adapter(适配器模式), Bridge(桥梁模式), Composite(合成模式),Decorator(装饰模式), Flyweight(享元模式), Proxy(代理模式),Command(命令模式), Interpreter(解释器模式), Visitor(访问者模式),Iterator(迭代子模式), Mediator(调停者模式), Memento(备忘录模式),Observer(观察者模式),State(状态模式),Strategy(策略模式),Template Method(模板方法模式), Chain Of Responsibleity(责任链模式)。
工厂模式
根据工厂模式实现的类可以根据提供的数据生成一组类中某一个类的实例,通常这一组类有一个公共的抽象父类并且实现了相同的方法,但是这些方法针对不同的数据进行了不同的操作。
首先需要定义一个基类,该类的子类通过不同的方法实现了基类中的方法。然后需要定义一个工厂类,工厂类可以根据条件生成不同的子类实例。当得到子类的实例后,开发人员可以调用基类中的方法而不必考虑到底返回的是哪一个子类的实例。

1.17 什么是线程安全

如果一段代码可以保证多个线程访问的时候正确操作共享数据,那么它是线程安全的。
当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象就是线程安全的。

1.18 并行与并发的理解

  • 并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事。
  • 并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事

1.19 重载和重写的区别

二者都是java多态的体现
(1) 重载:
两同一不同:同一个类里面、相同方法名
参数列表不同:参数个数不同,参数类型不同
(2)重写
子类重写父类的方法。

1.20 数组的排序算法

  • 选择排序:
    直接选择排序,堆排序
  • 交换排序
    冒泡排序、快速排序
  • 插入排序
    直接插入排序,折半插入排序,Shell排序
  • 归并排序
    桶式排序、基数排序

1.21 截取字符串

  • 通过substring()截取字符串
    • 只传入一个参数 substring(int beginIndex)
      传一个参数,含义为将字符串从索引号为beginIndex开始截取,一直到字符串末尾。注意第一个字符的索引值为零,截取时包含索引beginIndex的字符;示例代码如下:

      String oldStr = "zifu截取练习ing";
      String str = oldStr.substring(5);
      System.out.println(str);
      运行结果:
      取练习ing
      
    • 传入两个参数 substring(int beginIndex, int endIndex)
      从索引号beginIndex开始到索引号endIndex结束,返回结果包含索引为beginIndex的字符,不包含索引endIndex的字符;

       String oldStr = "zifu截取练习ing";
       String str = oldStr.substring(0,5);
       System.out.println(str);
       运行结果:
       zifu截
      
      
  • 通过split()切割字符串,返回结果为字符串数组
    • 只传一个参数:split(String regex)
      参数支持正则或普通字符,根据给定正则表达式或字符匹配拆分此字符串。示例代码如下:

      String oldStr = "China,Japan,美国,俄罗斯";
      String[] strs = oldStr.split(",");//根据,切分字符串
      for(int i = 0;i < strs.length; i++){
             
          System.out.println(strs[i]);
      }
      
      
    • 传入两个参数:split(String regex,int limit)
      regex正则表达式分隔符。limit 分割的份数。根据正则表达式或者字符和想要分割的份数来拆分此字符串。示例代码如下:

      String oldStr = "China,Japan,美国,俄罗斯";
      String[] strs = oldStr.split(",",2);//根据,切分字符串;切两份
      for(int i = 0;i < strs.length; i++){
             
          System.out.println
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

梦幻蔚蓝

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值