2020.01.07

1.怎么快速的把一个list集合中的元素去重?

(1)利用HashSet去重

package com.ggqq;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;

public class Test01 {
    public static void main(String[] args) {
        List list = new ArrayList();
        list.add("123");
        list.add("123");
        list.add("234");
        list.add("789");
        /*//遍历
        //方法一:for循环
        for(int i = 0; i<list.size();i++){
            System.out.println(list.get(i));
        }
        //方法二:iterator迭代器
        Iterator iterator = list.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }*/
        //方法三:增强for
        for(Object s:list){
            System.out.println(s);
        }

        //list添加到HashSet中去重
        HashSet hashSet = new HashSet(list);
        //清空list
        list.clear();
        //将HashSet添加到list中
        list.addAll(hashSet);
        //遍历
        for(int i = 0; i<list.size();i++){
            System.out.println(list.get(i));
        }
    }
}

 (2)通过List的contains()方法去重

package com.ggqq;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;

public class Test02 {
    public static void main(String[] args) {
        List list = new ArrayList();
        list.add("123");
        list.add("123");
        list.add("234");
        list.add("789");

        //遍历
        Iterator iterator = list.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }
        System.out.println("------------------------");
        //新建一个tempList,用于存放去重后的
        List tempList = new ArrayList();
        for(int i = 0 ; i <list.size();i++){
            if(!tempList.contains(list.get(i))){
                tempList.add(list.get(i));
            }
        }
        //遍历
        Iterator iterator2 = tempList.iterator();
        while(iterator2.hasNext()){
            System.out.println(iterator2.next());
        }
    }
}

(3) 通过两层for循环判断

package com.ggqq;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class Test03 {
    public static void main(String[] args) {
        List list = new ArrayList();
        list.add("123");
        list.add("123");
        list.add("234");
        list.add("789");

        //遍历
        Iterator iterator = list.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }
        System.out.println("------------------------");
        //从list中索引为0开始往后遍历
        for(int i = 0 ; i <list.size()-1;i++){
            for(int j = list.size()-1; j>i; j-- ){
                if(list.get(i).equals(list.get(j))){
                    //去重
                    list.remove(j);
                }
            }
        }
        //遍历
        Iterator iterator2 = list.iterator();
        while(iterator2.hasNext()){
            System.out.println(iterator2.next());
        }
    }
}

2.ThreadLocal是什么?有哪些使用场景?

ThreadLocal 是线程本地存储,在每个线程中都创建了一个 ThreadLocalMap 对象,每个线程可以访问自己内部 ThreadLocalMap 对象内的 value。

经典的使用场景是为每个线程分配一个 JDBC 连接 Connection。这样就可以保证每个线程的都在各自的 Connection 上进行数据库的操作,不会出现 A 线程关了 B线程正在使用的 Connection; 还有 Session 管理 等问题。

ThreadLocal 使用例子:

package com.ggqq;

public class TestThreadLocal {

    //线程本地存储变量
    private static final ThreadLocal<Integer> THREAD_LOCAL_NUM = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            return 0;
        }
    };

    public static void main(String[] args) {
        for (int i = 0; i < 3; i++) {//启动三个线程
            Thread t = new Thread() {
                @Override
                public void run() {
                    add10ByThreadLocal();
                }
            };
            t.start();
        }
    }

    /**
     * 线程本地存储变量加 5
     */
    private static void add10ByThreadLocal() {
        for (int i = 0; i < 5; i++) {
            Integer n = THREAD_LOCAL_NUM.get();
            n += 1;
            THREAD_LOCAL_NUM.set(n);
            System.out.println(Thread.currentThread().getName() + " : ThreadLocal num=" + n);
        }
    }

}

 结果:

3.什么是死锁?

 

死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。是操作系统层面的一个错误,是进程死锁的简称。

4.分布式锁的实现方式

 (1)为什么需要分布式锁?

首先我们应该先了解一下分布式锁的使用场景,然后再来理解为什么需要分布式锁。现我举两个例子来进行阐述:
应用场景:
1:银行转账问题(该场景不太好解释):A在上海,B在北京同时在建行转账给杭州C,A转账时,会修改C处服务器的表,B不能在此刻转账,同理,B转账时,A不能做处理,A,B的转账操作时同步,必须保证数据的一致性,这就需要分布式锁来进行处理。
2:取任务问题:某服务提供一组任务,A系统请求随机从任务组中获取一个任务;B系统请求随机从任务组中获取一个任务。 在理想的情况下,A从任务组中挑选一个任务,任务组删除该任务,B从剩下的的任务中再挑一个,任务组删除该任务。 同样的,在真实情况下,如果不做任何处理,可能会出现A和B挑中了同一个任务的情况。

(2)为什么分布式系统中不能用普通锁呢?那么普通锁和分布式锁有什么区别呢?

普通锁:单一系统中,同一个应用程序是有同一个进程,然后多个线程并发会造成数据安全问题,他们是共享同一块内存的,所以在内存某个地方做标记即可满足需求,例如synchronized和volatile+cas一样对具体的代码做标记,对应的就是在同一块内存区域作了同步的标记。
分布式锁:分布式系统中,最大的区别就是不同系统中的应用程序都是在各自机器上不同的进程中处理的,这里的线程不安全可以理解为多进程造成的数据安全问题,他们不会共享同一台机器的同一块内存区域,因此需要将标记存储在所有进程都能看到的地方。例如zookeeper作分布式锁,就是将锁标记存储在多个进程共同看到的地方,redis作分布式锁,是将其标记公共内存,而不是某个进程分配的区域。

(3)分布式锁的三种实现方式

a:zookeeper实现分布式锁(用的最多)

实现方式:
方案1:利用节点名称的唯一性来实现共享锁。
算法思路: 利用名称唯一性,加锁操作时,只需要所有客户端一起创建/test/Lock节点,只有一个创建成功,成功者获得锁。解锁时,只需删除/test/Lock节点,其余客户端再次进入竞争创建节点,直到所有客户端都获得锁。
方案2:利用临时顺序节点实现共享锁。(主要是用这种方式实现)
算法思路:对于加锁操作,可以让所有客户端都去/lock目录下创建临时顺序节点,如果创建的客户端发现自身创建节点序列号是/lock/目录下最小的节点,则获得锁。否则,监视比自己创建节点的序列号小的节点(比自己创建的节点小的最大节点),进入等待。
比如创建节点:/lock/0000000001、/lock/0000000002、/lock/0000000003。则节点/lock/0000000001会先获得锁,因为zk上的节点是有序的,且都是最小的节点先获得锁。
注:临时顺序节点比持久顺序节点的好处是:当zookeeper宕机后,临时顺序节点会自动删除,获取锁的客户端会释放锁,不会一直造成锁等待,而持久节点会造成锁等待。
两种方式的区别
方案1会产生惊群效应:假如许多客户端在等待一把锁,当锁释放时候所有客户端都被唤醒,然后竞争分布式锁,仅仅有一个客户端得到锁。
方案2是按照创建顺序排队的实现,多个客户端共同等待锁,当锁释放时只有一个客户端会被唤醒,在zk上注册节点最小的客户端会被唤醒,避免了惊群效应。

b:redis实现分布式锁(用的次之)

redis实现分布式锁主要靠四个命令:
setnx(set if not exits 维护着是乐观锁):当不存在key的时候,才为key设置值为value。setnx与set的区别:set是存在key,则去覆盖value;setnx是不存在key,则重新给key和value赋值。
getset:根据key得到旧的值,并set新的值。
expire:设置过期时间。
del:删除

实现方式:
1:获取锁的时候,使用setnx加锁,并使用expire命令为锁添加一个超时时间,超过该时间则自动释放锁,锁的value值为一个随机生成的UUID,通过此在释放锁的时候进行判断。
2:获取锁的时候还设置一个获取的超时时间,若超过这个时间则放弃获取锁。
3:释放锁的时候,通过UUID判断是不是该锁,若是该锁,则执行delete进行锁释放。

c:数据库实现分布式锁(用的最少)

实现方式:利用的是乐观锁和悲观锁
乐观锁:在表中添加版本号的字段,每次更新前都先查询出带版本号的数据,然后再更新的时候where条件语句后带版本号条件,更新成功表示锁已占用,更新不成功表示锁没被占用。
悲观锁:利用select...for update(X锁)/select...lock in share mode(S锁),一般来说用X锁的较多,因为后续多会做写功能的实现。
注:当实现悲观锁的时候,需要关闭数据库的事务自动提交机制不然不会生效。因此java代码中应该选择主动关闭数据库的事务自动提交功能。

 

5.说一下HashMap的实现原理

 hashset是无序的,不可重复的。

Hashset底层使用了哈希表(哈希表是将数组和单向链表的优点集成在一起)实现的。特点是存储快

往hashset添加元素的时候,hashset会先调用元素的hashcode方法得到元素的哈希值,然后通过与水泥素的哈希值经过异或或移位等运算,就可以算出该元素在哈希表中的存储位置。

运行原理:

如果算出的元素的存储的位置目前没有任何元素储存,那么该元素可以直接存储在该位置上,如果算出的元素的存储位置上目前已经有了其他的元素没那么还会调用该元素的equals方法,与该位置的元素进行比较一次,如果equals方法返回的是true,那么该位置上的元素就会被视为重复元素,不允许被添加,如果false,则允许添加。

实现原理

Hashset是基于hashmap实现的,默认构造函数是构建一个初始容量为16,负载因子为0.75的hashmap。封装了一个hashmap对象来储存所有的集合元素,所有放在hashset中的集合元素实际上由hashmap的key来保存。

6.在Queue中poll()和remove()有什么区别?

poll()和remove()都将移除并且返回队头,但是在poll()在队列为空时返回null,而remove()会抛出NoSuchElementException异常。 

7.JDK1.8的新特性:(在外面的博客中详细叙述)

8.application和bootstrap的应用场景:

application:配置文件这个容易理解,主要用于SpringBoot项目的自动化配置。

bootstrap:配置文件有以下几个应用场景:

  • 使用SpringCloud Config配置中心时,这时需要在bootstrap配置文件中添加连接到配置中心的配置属性来加载外部配置中心的配置信息;
  • 一些固定的不能被覆盖的属性;
  • 一些加密/解密的场景。
application.yml 是用户级别的配置,而bootstrap.yml 是系统级别的配置

9.HashMap和HashTable的key和value是否可以为null?

HashMap可以存储一个Key为null,多个value为null的元素,但是Hashtable却不可以存储

(看源码)

10.什么是Spring?

Spring是于2003年兴起的一个轻量级java开发框架,他是为了解决企业应用开发的复杂性而创建的。Spring的核心是控制反转(IOC)和面向切面编程(AOP)。

Spring的作用就是为代码解耦合,降低代码的耦合度。就是让对象和对象(模块和模块)之间关系不是使用代码关联,而是通过配置来说明,

IOC又称自动注入,注入即赋值,IOC使的主业务在相互调用的过程中,不用再自己维护关系了,即不用自己再创建要使用的对象了,而是右Spring容器统一管理。

AOP是动态代理的规范化,AOP是Spring框架中的一个重要内容,利用AOP可以对业务逻辑的各个部分进行隔离,从而使业务逻辑的各个部分之间的耦合度降低,提高程序的可重复性,同时提高了开发的效率。

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值