2021 Java面试题总结(更新中)

本文总结了Java面试中常见的SpringMVC流程、多线程处理、Redis使用及面试题,涵盖SpringMVC的注解、线程同步与异同、线程池实现、数据类型、缓存策略等内容,帮助开发者全面准备面试。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

一、面试题

1.SpringMVC的流程? 

 2.SpringMVC怎么设定 重定向 和 转发 的?

3.SpringMVC常用的注解有哪些? 

4.Equal 和 == 区别

==运算符的使用

equals()方法的使用

5.synchronized 和 lock 的异同

相同点:

不同点:

6.多线程中sleep()和wait()的异同

相同点:

不同点:

7.创建多线程的方式

        (1)继承Thread类

        (2)实现Runnable接口

        (3)实现Callable接口

        (4)创建线程池

8.线程安全问题

(1)Runnable的线程安全问题

(2)Thread类的线程安全问题

9.同步 异步 与 阻塞 非阻塞

(1)同步和异步

(2)阻塞和非阻塞

10.线程的 run()和 start()有什么区别?

11.线程有哪几种状态

12.notify()和 notifyAll()有什么区别?

13.在 java 程序中怎么保证多线程的运行安全?

14. runnable 和 callable 有什么区别?

15.悲观锁和乐观锁

16.线程池中 submit()和 execute()方法有什么区别?

17.多线程锁的升级原理是什么?

18.String、StringBuffer和StringBuilder的区别

19.三次握手和四次挥手

20.聚合函数

21.redis数据类型

22.redis数据持久化(RDB和AOF)

23.hashmap的put方法的原理

二、每日练习(一天三题)

1.JVM中内存结构模型。 

2.Java中多线程在项目中哪里用到了?

3.活锁死锁问题分析,什么是活锁,什么是死锁?出现的原因是什么?如何避免?

        1.什么是死锁

        2.什么是活锁

4.多线程下如何做到数据共享?(可拓展数据共享的使用场景)

5.多线程下如何保证线程安全?(可拓展两种锁)

6.HashMap的底层详细介绍下?(1.7和1.8前后区别)

7.ArrayList和LinkdList的区别?(使用场景和特性分析)

8.ConCurrentHashMap和HashMap的区别,分段锁机制?(理论)

9.HashMap和Hashtable的区别?

10.NIO和AIO、BIO三者区别?(理论)

11.介绍下Spring框架。(框架简介、框架核心、框架特性等)

框架简介

框架核心

框架特点:

Spring的优点

12.介绍下Spring框架IOC和AOP的实现原理。(理论)

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

AOP:即面向切面编程

13.Spring框架中beanFactory和FactoryBean有什么区别?

14.SpringBoot框架的项目启动流程?

15.SpringBoot如何实现自动装配。

16.阐述下security框架的授权以及认证流程(带入项目中角色管理模块的实现思路,来阐述此题)

17.Redis的常见数据类型有哪些?(带入具体类型具体使用场景)

字符串string:

列表list:

散列hash:

集合set:

有序集合sorted set:

最后,还有个对key的通用操作,所有的数据类型都可以使用的

18.Redis的缓存雪崩和击穿如何解决,穿透问题遇到过吗?(带入具体的项目场景)

缓存雪崩

缓存击穿

缓存穿透 

19.数据库中有2000w条数据,而Redis当中只有20w条数据,如何保证Redis中的数据一定是热点数据?(可考虑设置缓存过期时间)

20.Spring框架中出现的ABA问题(循环依赖)如何解决?(原理,缓存思路)

21.Spring框架中Bean对象的生命周期。(原理)

22.SpringMVC框架的工作原理(带入项目中功能请求来阐述,例如登录请求、查询请求)

23.SpringMVC框架从发起请求到接收请求发生了什么?(网络相关)

24.MyBatis框架如何处理sql注入问题?(MyBatis中#和$符号区别)

25.MyBatis框架有几级缓存,默认开启几级?(理论性)

26.MySql数据库中索引的作用和优缺点?(理论内容)

作用:

优点:

缺点:

27.MySql中索引有几种,索引怎么使用?(带入具体的项目场景)

索引分类

索引的使用

28.什么是事务?事务的特性?MySql中事务的隔离级别?(理论内容)

什么是事务?

事务的特性

MySql中事务的隔离等级?

什么是脏读?什么是不可重复读?什么是幻读?什么是可重复读?

29.内存溢出是怎么回事?内存溢出和内存泄露的区别?

内存泄露

内存溢出

区别

30.项目中哪里使用多线程,哪里使用锁机制了?

三、应用

1.设计模式

2.单点登录


一、面试题

1.SpringMVC的流程? 

  1. 用户发送请求至前端控制器DispatcherServiet;
  2. 前端控制器收到请求后,调用处理器映射器HandlerMapping,请求获取Handler;
  3. 处理器映射器根据请求URL找到具体的处理器Handler,生成处理器对象及处理器拦截器(如果有则生成),一并返回给前端控制器;
  4. 前端控制器调用处理器适配器HandlerAdapter,请求执行Handler;
  5. 处理器适配器经过适配调用具体处理器进行处理业务逻辑;
  6. 处理器执行完成返回ModelAndView;
  7. 处理器适配器将处理器结果ModelAndView返回给前端控制器;
  8. 前端控制器将ModelAndView传给视图解析器ViewResolver进行解析;
  9. 视图解析器解析后返回具体视图View;
  10. 前端控制器对View进行渲染视图(即将模型数据填充至视图中);
  11. 前端控制器响应用户。

 2.SpringMVC怎么设定 重定向 和 转发 的?

  1. 重定向:在返回值前面加“redirect”,譬如“redirect:http://www.baidu.com”
  2. 转发:在返回值前面加“forward”,譬如“forward:user.do?name=method4”
  3. 转发与重定向的区别

    1.地址栏 
    转发:不变,不会显示出转向的地址 
    重定向:会显示转向之后的地址

    2.请求 

    转发:一次请求
    重定向:至少提交了两次请求

    3.数据 
    转发:对request对象的信息不会丢失,因此可以在多个页面交互过程中实现请求数据的共享 
    重定向:request信息将丢失

    4.原理 
    转发:是在服务器内部控制权的转移,是由服务器区请求,客户端并不知道是怎样转移的,因此客户端浏览器的地址不会显示出转向的地址。 
    重定向:是服务器告诉了客户端要转向哪个地址,客户端再自己去请求转向的地址,因此会显示转向后的地址,也可以理解浏览器至少进行了两次的访问请求。

3.SpringMVC常用的注解有哪些? 

  1. @RequestMapping:用于处理请求url映射的注解,可用于类或方法上,用于类上,则表示类中的所有响应请求的方法都是以该地址作为父路径。
  2. @Controller:用于标记在一个类上,使用它标记的类就是一个SpringMVC Controller 对象。分发处理器将会扫描使用了该注解的类的方法,并检测该方法是否使用了@RequestMapping 注解。@Controller 只是定义了一个控制器类,而使用@RequestMapping 注解的方法才是真正处理请求的处理器。
    @Controller 标记在一个类上还不能真正意义上的说它就是SpringMVC 的一个控制器类,因为这个时候Spring 还不认识它。这个时候就需要我们把这个控制器类交给Spring 来管理。有两种方式可以管理:
    <!--方式一-->
    <bean class="com.cqvie.handler.HelloWorld"/>
    <!--方式二-->
    < context:component-scan base-package = "com.cqvie" /> <!-- 路径写到controller的上一层 -->
  3. @PathVariable:用于将请求URL中的模板变量映射到功能处理方法的参数上,即取出uri模板中的变量作为参数。

  4. @Resource和@Autowired:都是做bean的注入时使用,其实@Resource并不是Spring的注解,它的包是javax.annotation.Resource,需要导入,但是Spring支持该注解的注入。

  5. @RequestBody:注解实现接收http请求的json数据,通过添加导入的jason或其他依赖将json转换为java对象
  6. @ResponseBody:注解实现将controller方法返回对象转化为json对象响应给客户。

4.Equal 和 == 区别

  1. ==运算符的使用

    (1)可以使用在基本数据类型变量和引用数据类型变量中 。

    (2)如果比较的是基本数据类型变量:比较两个变量保存的数据是否相等。(不一定类型要相同)

    (3)如果比较的是引用数据类型变量:比较两个对象的地址值是否相同,即两个引用是否指向同一个对象实体。

    (4)补充:== 符号使用时,必须保证符号左右两边的变量类型一致。

  2. equals()方法的使用

    (1)是一个方法,而非运算符

    (2)只能适用于引用数据类型

    (3)Object类中equals()的定义:

public boolean equals(Object obj){
	return (this == obj);
}

        说明:Object类中定义的equals()和==的作用是相同的,比较两个对象的地址值是否相同,即两个引用是否指向同一个对象实体。

        (4)像String、Date、File、包装类等都重写了Object类中的equals()方法。

                 不是比较两个引用的地址是否相同,而是比较两个对象的“实体内容”是否相同。

        (5)通常情况下,我们自定义的类如果使用equals()的话,也通常是比较两个对象的"实体内容"是否相同。那么,我们就需要对Object类中的equals()进行重写。

                  重写的原则:比较两个对象的实体内容是否相同。

5.synchronized 和 lock 的异同

相同点:

  • 都可以解决线程安全问题

不同点:

  • 首先synchronized是java内置关键字,在jvm层面,Lock是个java类;

  • synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器;lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock());

  • 用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;
  • synchronized有代码块锁和方法锁,lock只有代码块锁;

  • synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可中断、可公平(两者皆可);

  • Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。

  • 使用lock锁,JVM可以花费更少时间调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)。

6.多线程中sleep()和wait()的异同

相同点:

  • 一旦执行方法,都可以使得当前线程进入阻塞状态

不同点:

  • sleep()申明在Thread类中,wait()申明在Object类中;
  • 调用要求不同:sleep()可以在任何需要场景下调用;wait()必须使用在同步代码块或同步方法中。
  • 如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放同步监视器(锁),而wait()会。

7.创建多线程的方式

        (1)继承Thread类

  1. 创建一个继承于Thread类的子类
  2. 重写Thread的run()方法 ——> 将此线程的方法声明在run()中
  3. 创建Thread类的子对象
  4. 通过此对象调用start()
class MyThread extends Thread{
    //重写Thread类的run()
    @Override
    public void run() {
        for(int i = 1;i < 100;i++){
            if(i % 2 == 0){
                System.out.println(i);
            }
        }
    }
}

public class ThreadTest {
    public static void main(String[] args) {
        //3.创建Thread类的子对象
        MyThread t1 = new MyThread();

        //4.通过此对象调用start():①启动当前线程 ②调用当前线程的run()
        t1.start();

        //如下操作仍在main线程中执行的
        for(int i = 1;i < 100;i++){
            if(i % 2 == 0){
                System.out.println(i + "***main()***");
            }
        }
    }
}

        (2)实现Runnable接口

  1. 创建一个实现了Runnable接口得类
  2. 实现类去实现Runnable中的抽象方法:run()
  3. 创建实现类的对象
  4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
  5. 通过Thread类的对象调用start()
class MThread implements Runnable{

    //2.实现类去实现Runnable中的抽象方法:run()
    @Override
    public void run() {
        for(int i = 0;i < 100;i++){
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}

public class ThreadTest1 {
    public static void main(String[] args) {
        //3.创建实现类的对象
        MThread m1 = new MThread();
        //4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
        Thread t1 = new Thread(m1);
        //5.通过Thread类的对象调用start():①启动线程 ②调用当前线程的run() --> 调用了Runnable类型的target的run()
        t1.start();

        //再启动一个线程,遍历100以内的偶数
        Thread t2 = new Thread(m1);
        t2.setName("线程2");
        t2.start();
    }
}

        (3)实现Callable接口

  1. 创建一个实现Callable的实现类
  2. 实现call方法,将此线程需要执行的操作声明在call()中
  3. 创建Callable接口实现类的对象
  4. 将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
  5. 将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
  6. 获取Callable中call方法的返回值,get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。
class NumThread implements Callable{
    @Override
    public Object call() throws Exception {
        int sum = 0;
        for(int i = 1;i <= 100;i++){
            if(i % 2 == 0){
                System.out.println(i);
                sum += i;
            }
        }
        return sum;
    }
}

public class ThreadNew {
    public static void main(String[] args) {
        NumThread numThread = new NumThread();
        FutureTask futureTask = new FutureTask(numThread);
        new Thread(futureTask).start();
        try {
            Object sum = futureTask.get();
            System.out.println("总和为:" + sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

        (4)创建线程池

  1. newCachedThreadPool(),它是用来处理大量短时间工作任务的线程池,具有几个鲜明特点:它会试图缓存线程并重用,当无缓存线程可用时,就会创建新的工作线程;如果线程闲置时间超过60秒,则被终止并移除缓存;长时间闲置时,这种线程池,不会消耗什么资源。其内部使用SynchronousQueue作为工作队列。 

    private static void createCachedThreadPool() {
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            final int index = i;
            executorService.execute(() -> {
                // 获取线程名称,默认格式:pool-1-thread-1
                System.out.println(DateUtil.now() + " " + Thread.currentThread().getName() + " " + index);
                // 等待2秒
                sleep(2000);
            });
        }
    }

            因为SynchronousQueue队列不保持它们,直接提交给线程,相当于队列大小为0,而最大线程数为Integer.MAX_VALUE,所以线程不足时,会一直创建新线程,等到线程空闲时,又有60秒存活时间,从而实现了一个可缓存的线程池。

  2. newFixedThreadPool(int nThreads),重用指定数目(nThreads)的线程,其背后使用的是无界的工作队列,任何时候最多有nThreads个工作线程是活动的。这意味着,如果任务数量超过了活动线程数目,将在工作队列中等待空闲线程出现;如果工作线程退出,将会有新的工作线程被创建,以补足指定数目nThreads。

    private static void createFixedThreadPool() {
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 10; i++) {
            final int index = i;
            executorService.execute(() -> {
                // 获取线程名称,默认格式:pool-1-thread-1
                System.out.println(DateUtil.now() + " " + Thread.currentThread().getName() + " " + index);
                // 等待2秒
                sleep(2000);
            });
        }
    }

            因为核心线程数与最大线程数相同,所以线程池的线程数是固定的,而且没有限制队列的大小,所以多余的任务均会被放到队列排队,从而实现一个固定大小,可控制并发数量的线程池。

  3. newSingleThreadExecutor(),它的特点在于工作线程数目限制为1,操作一个无界的工作队列,所以它保证了所有的任务都是被顺序执行,最多会有一个任务处于活动状态,并且不予许使用者改动线程池实例,因此可以避免改变线程数目。

    private static void createSingleThreadPool() {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 10; i++) {
            final int index = i;
            executorService.execute(() -> {
                // 获取线程名称,默认格式:pool-1-thread-1
                System.out.println(DateUtil.now() + " " + Thread.currentThread().getName() + " " + index);
                // 等待2秒
                sleep(2000);
            });
        }
    }

           因为核心线程数与最大线程数相同,均为1,所以线程池的线程数是固定的1个,而且没有限制队列的大小,所以多余的任务均会被放到队列排队,从而实现一个单线程按指定顺序执行的线程池。 

  4. newSingleThreadScheduledExecutor()和newScheduledThreadPool(int corePoolSize),创建的是个ScheduledExecutorService,可以进行定时或周期性的工作调度,区别在于单一工作线程还是多个工作线程。

    private static void createScheduledThreadPool() {
        ScheduledExecutorService executorService = Executors.newScheduledThreadPool(3);
        System.out.println(DateUtil.now() + " 提交任务");
        for (int i = 0; i < 10; i++) {
            final int index = i;
            executorService.schedule(() -> {
                // 获取线程名称,默认格式:pool-1-thread-1
                System.out.println(DateUtil.now() + " " + Thread.currentThread().getName() + " " + index);
                // 等待2秒
                sleep(2000);
            }, 3, TimeUnit.SECONDS);
        }
    }

           因为使用了延迟队列,只有在延迟期满时才能从中提取到元素,从而实现定时执行的线程池。而周期性执行是配合上层封装的其他类来实现的,可以看ScheduledExecutorService类的scheduleAtFixedRate方法。

  5. newWorkStealingPool(int parallelism)简单翻译是任务窃取线程池,Java 8 才加入这个创建方法,其内部会构建ForkJoinPool,利用Work-Stealing算法,并行地处理任务,不保证处理顺序。
    使用ForkJoinPool的好处是,把1个任务拆分成多个“小任务”,把这些“小任务”分发到多个线程上执行。这些“小任务”都执行完成后,再将结果合并
    当线程发现自己的队列没有任务了,就会到别的线程的队列里获取任务执行。可以简单理解为”窃取“。
    一般是自己的本地队列采取LIFO(后进先出),窃取时采用FIFO(先进先出),一个从头开始执行,一个从尾部开始执行,由于偷取的动作十分快速,会大量降低这种冲突,也是一种优化方式。
    ①无参创建方式:

    public static ExecutorService newWorkStealingPool() {
        return new ForkJoinPool
            //Runtime.getRuntime().availableProcessors()是获取当前系统可以的CPU核心数。
            (Runtime.getRuntime().availableProcessors(),
             ForkJoinPool.defaultForkJoinWorkerThreadFactory,
             null, true);
    }

    ②有参创建方式

    public static ExecutorService newWorkStealingPool(int parallelism) {
        return new ForkJoinPool
            (parallelism,
             ForkJoinPool.defaultForkJoinWorkerThreadFactory,
             null, true);
    }
    
  6. ThreadPoolExecutor类创建线程

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        // 省略...
    }

    ① 共7个参数如下:
    (1)corePoolSize:核心线程数,线程池中始终存活的线程数。
    (2)maximumPoolSize: 最大线程数,线程池中允许的最大线程数。
    (3)keepAliveTime: 存活时间,线程没有任务执行时最多保持多久时间会终止。
    (4)unit: 单位,参数keepAliveTime的时间单位,7种可选。

    (5)workQueue: 一个阻塞队列,用来存储等待执行的任务,均为线程安全,7种可选。

    较常用的是LinkedBlockingQueue和Synchronous。线程池的排队策略与BlockingQueue有关。
    (6)threadFactory: 线程工厂,主要用来创建线程,默及正常优先级、非守护线程。
    (7)handler:拒绝策略,拒绝处理任务时的策略,4种可选,默认为AbortPolicy。

线程池的执行规则:

(1)当线程数小于核心线程数时,创建线程。

(2)当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。

(3)当线程数大于等于核心线程数,且任务队列已满:

        若线程数小于最大线程数,创建线程。

        若线程数等于最大线程数,抛出异常,拒绝任务。

阿里代码规范《阿里巴巴Java开发手册》中明确不建议使用Executors类提供的这4种方法:

【强制】线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

Executors返回的线程池对象的弊端如下:

FixedThreadPool和SingleThreadPool:允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。

CachedThreadPool和ScheduledThreadPool:允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。

所以我们应该使用ThreadPoolExecutor类来创建线程池,根据自己需要的场景来创建一个合适的线程池。

8.线程安全问题

(1)Runnable的线程安全问题

/**
 *  例子:创建三个窗口卖票,总票数为100张.使用实现Runnable接口的方式
 *  1.卖票过程中出现重票、错票 ---》出现了线程的安全问题
 *  2.问题出现的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票
 *  3.如何解决:当一个线程在操作ticket的时候,其他线程不能参与进来。直到线程a操作完ticket时,其他线程才可以操作ticket。这种情况即使线程a出现了阻塞,也不能被改变。
 *  4.在java中,我们通过同步机制,来解决线程的安全问题。
 *  5.同步的方式,解决了线程的安全问题。---好处
 *    操作同步代码时,只能有一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低。---局限性
 *
 *  方式一:同步代码块
 *  synchronized(同步监视器){
 *      //需要被同步的代码
 *  }
 *
 *  说明:1.操作共享数据的代码,即为需要被同步的代码 --->不能包含代码多了,也不能包含代码少了。
 *       2.共享数据:多个线程共同操作的变量。比如:ticket就是共享数据
 *       3.同步监视器,俗称:锁。任何一个类的对象,都可以来充当锁。
 *          要求:多个线程必须要共用同一把锁。
 *
 *       补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。
 *
 *  方式二:同步方法
 *      如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步的
 *
 */

class Windows1 implements Runnable{

    private int ticket = 100;

    @Override
    public void run() {
        while(true){
            synchronized (this) {//此时的this:唯一的windows1的对象 
                if (ticket > 0) {

                    try{
                        Thread.sleep(100);
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName() + ":卖票,票号为: " + ticket);
                    ticket--;
                } else {
                    break;
                }
            }
        }
    }
}

public class WindowsTest1 {
    public static void main(String[] args) {
        Windows1 w = new Windows1();

        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}

/**
 * 2.使用同步方法解决实现Runnable接口的线程安全问题
 *
 * 关于同步方法的总结:
 *  1. 同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。
 *  2. 非静态的同步方法,同步监视器是:this
 *     静态的同步方法,同步监视器是:当前类本身
 */

class Windows3 implements Runnable {

    private int ticket = 100;

    @Override
    public void run() {
        while (true) {
            show();
        }
    }

    public synchronized void show() { //方法上加synchronized
//        synchronized (this){
            if (ticket > 0) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + ":卖票,票号为: " + ticket);
                ticket--;
            }
//        }
    }
}

public class WindowsTest3 {
    public static void main(String[] args) {
        Windows3 w3 = new Windows3();

        Thread t1 = new Thread(w3);
        Thread t2 = new Thread(w3);
        Thread t3 = new Thread(w3);

        t1.setName("窗
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值