首页 文章 精选 留言 我的

精选列表

搜索[面试],共5082篇文章
优秀的个人博客,低调大师

Java Spring常见面试

问:SpringIOC原理阐述 答:把对象的创建、初始化、销毁等工作交给Spring容器来完成。我们可以把IOC容器的工作模式看做是工厂模式的升华,可以把IOC容器看作是一个工厂,这个工厂里要生产的对象都在配置文件中给出定义,然后利用编程语言的的反射编程,根据配置文件中给出的类名生成相应的对象。从实现来看,IOC是把以前在工厂方法里写死的对象生成代码,改变为由配置文件来定义,也就是把工厂和对象生成这两者独立分隔开来,目的就是提高灵活性和可维护性。 问:SpringAOP原理 答:1)面向对象的设计没有办法解决重复代码的问题 2)SpringAOP使用动态代理技术在运行期植入增强的代码,aspectj是在编译器织入横切代码的形式来实现代理技术的 3)SpringAOP使用了两种代理机制,一种是基于JDK的动态代理,一种是基于CGLib的动态代理 4)JDK1.3以后java提供了动态代理的技术,运行开发者在运行期创建接口的代理实例 5)jdk的动态代理主要涉及java.lang.reflect包中的两个类ProxyInvcoationHandler 6)InvcoationHandler是一个接口,通过实行该接口可以订阅横切逻辑,并通过反射机制调用目标类的代码,动态讲横切逻辑和业务逻辑编织在一起 7)Proxy利用InvocationHandler动态创建一个符号某一接口的实例,生成目标类的代理对象 8)cglib采用非常底层的字节码技术,可以为一个类创建子类,并在子类中采用方法拦截技术拦截所有的父类方法的调用,并顺势织入横切逻辑 问:AOP中的概念解释 答:切面:Aspect,连接点:Joinpoint,增强:Advice,切入点:Pointcut,目标对象:Target,代理:Proxy,其中增强包括:前置增强,后置增强,返回会增强,环绕增强,抛出异常后增强 问:使用SpringAOP可以基于两种方式 答:一种是比较方便和强大的注解方式,使用注解配置SpringAOP总体分为两步,第一步是在xml文件中声明激活自动扫描组件功能,同时激活自动代理功能,一种则是中规中矩的xml配置方式 问:Spring的事务传播机制 答:1)REQUIRED(默认):支持使用当前事务,如果当前事务不存在,创建一个新事务。 2)SUPPORTS:支持使用当前事务,如果当前事务不存在,则不使用事务。 3)MANDATORY:中文翻译为强制,支持使用当前事务,如果当前事务不存在,则抛出Exception。 4)REQUIRES_NEW:创建一个新事务,如果当前事务存在,把当前事务挂起。 5)NOT_SUPPORTED:无事务执行,如果当前事务存在,把当前事务挂起。 6)NEVER:无事务执行,如果当前有事务则抛出Exception。 7)NESTED:嵌套事务,如果当前事务存在,那么在嵌套的事务中执行。如果当前事务不存在,则表现跟REQUIRED一样。 问:Spring的事务实现方式 答:1)编程式事务管理对基于POJO的应用来说是唯一选择。我们需要在代码中调用beginTransaction()、commit()、rollback()等事务管理相关的方法,这就是编程式事务管理。 2)基于TransactionProxyFactoryBean的声明式事务管理 3)基于@Transactional的声明式事务管理 4)基于AspectjAOP配置事务 问:Spring通过单实例化Bean简化多线程问题 答:由于Spring的事务管理器是通过线程相关的ThreadLocal来保存数据访问基础设施(也即Connection实例),web容器本身就是多线程的,web容器为一个http请求创建一个独立的线程(实际大多数采用线程池),所以bean也是运行在多线程的环境下,在大多数情况下,Spring的bean都是单例的,单例的好处就是线程无关性,不存在多线程并发问题,Spring是通过ThreadLocal将有状态的变量本地线程化,达到另一个层面上的线程无关。 问:SpringMVC工作原理 答:SpringMVC框架围绕dispactcherServlet这个核心展开,dispatcherServlet是SpringMVC的总导演,总策划,他负责拦截请求并将器分派给响应的处理器处理。SpringMVC框架包括注解驱动控制器,请求及响应的信息处理,表单标签绑定,视图解析,本地化解析,上传文件解析,异常处理。 SpringMVC通过一个前端servlet接收所有的请求,并将具体工作委托给其他组件进行处理 1)整个过程开始于客户端发送一个HTTP请求,如果匹配web.xml的映射路径,则进行处理 2)DispatcherServlet根据HandlerMapping找到对应的Handler,将处理权交给Handler 3)HandlerAdapter这个适配器对各种Hander方法进行调用 4)处理完了之后返回一个ModelAndView给DispatcherServelt 5)Handler返回的ModelAndView()只是一个逻辑视图并不是一个正式的视图,DispatcherSevlet通过ViewResolver将逻辑视图转化为真正的视图View 6)根据ModelAndView对模型数据进行视图渲染 7)最终客户端得到相应消息,可能是一个普通的HTML页面,也可能是一个XML或者JSON串 问:SpringMVC和Struts2的区别 答:1)拦截机制的不同 Struts2是类级别的拦截,每次请求就会创建一个Action,和Spring整合时Struts2的ActionBean注入作用域是原型模式prototype,然后通过setter,getter吧request数据注入到属性。Struts2中,一个Action对应一个request,response上下文,在接收参数时,可以通过属性接收,这说明属性参数是让多个方法共享的。Struts2中Action的一个方法可以对应一个url,而其类属性却被所有方法共享,这也就无法用注解或其他方式标识其所属方法了,只能设计为多例。 SpringMVC是方法级别的拦截,一个方法对应一个Request上下文,所以方法直接基本上是独立的,独享request,response数据。而每个方法同时又何一个url对应,参数的传递是直接注入到方法中的,是方法所独有的。处理结果通过ModeMap返回给框架。在Spring整合时,SpringMVC的ControllerBean默认单例模式Singleton,所以默认对所有的请求,只会创建一个Controller,有应为没有共享的属性,所以是线程安全的,如果要改变默认的作用域,需要添加@Scope注解修改。 Struts2有自己的拦截Interceptor机制,SpringMVC这是用的是独立的AOP方式,这样导致Struts2的配置文件量还是比SpringMVC大。 2)底层框架的不同 Struts2采用Filter(StrutsPrepareAndExecuteFilter)实现,SpringMVC(DispatcherServlet)则采用Servlet实现。Filter在容器启动之后即初始化;服务停止以后坠毁,晚于Servlet。Servlet在是在调用时初始化,先于Filter调用,服务停止后销毁。 3)性能方面 Struts2是类级别的拦截,每次请求对应实例一个新的Action,需要加载所有的属性值注入,SpringMVC实现了零配置,由于SpringMVC基于方法的拦截,有加载一次单例模式bean注入。所以,SpringMVC开发效率和性能高于Struts2。 4)配置方面 SpringMVC和Spring是无缝的。从这个项目的管理和安全上也比Struts2高。

优秀的个人博客,低调大师

BAT面试必问HashMap源码分析

HashMap 简介 HashMap 主要用来存放键值对,它基于哈希表的Map接口实现,是常用的Java集合之一。 JDK1.8 之前 HashMap 由 数组+链表 组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突).JDK1.8 以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)时,将链表转化为红黑树,以减少搜索时间。 底层数据结构分析 JDK1.8之前 JDK1.8 之前 HashMap 底层是数组和链表结合在一起使用也就是链表散列。HashMap 通过 key 的 hashCode 经过扰动函数处理过后得到 hash 值,然后通过(n - 1) & hash判断当前元素存放的位置(这里的 n 指的是数组的长度),如果当前位置存在元素的话,就判断该元素与要存入的元素的 hash 值以及 key 是否相同,如果相同的话,直接覆盖,不相同就通过拉链法解决冲突。 所谓扰动函数指的就是 HashMap 的 hash 方法。使用 hash 方法也就是扰动函数是为了防止一些实现比较差的 hashCode() 方法 换句话说使用扰动函数之后可以减少碰撞。 JDK 1.8 HashMap 的 hash 方法源码: JDK 1.8 的 hash方法 相比于 JDK 1.7 hash 方法更加简化,但是原理不变。 1 2 3 4 5 6 7 static final int hash(Object key) { int h; // key.hashCode():返回散列值也就是hashcode // ^ :按位异或 // >>>:无符号右移,忽略符号位,空位都以0补齐 return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); } 对比一下 JDK1.7的 HashMap 的 hash 方法源码. 1 2 3 4 5 6 7 8 static int hash(int h) { // This function ensures that hashCodes that differ only by // constant multiples at each bit position have a bounded // number of collisions (approximately 8 at default load factor). h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); } 相比于 JDK1.8 的 hash 方法 ,JDK 1.7 的 hash 方法的性能会稍差一点点,因为毕竟扰动了 4 次。 所谓“拉链法”就是:将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。 JDK1.8之后 相比于之前的版本,jdk1.8在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。 类的属性: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable { // 序列号 private static final long serialVersionUID = 362498820763181265L; // 默认的初始容量是16 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 最大容量 static final int MAXIMUM_CAPACITY = 1 << 30; // 默认的填充因子 static final float DEFAULT_LOAD_FACTOR = 0.75f; // 当桶(bucket)上的结点数大于这个值时会转成红黑树 static final int TREEIFY_THRESHOLD = 8; // 当桶(bucket)上的结点数小于这个值时树转链表 static final int UNTREEIFY_THRESHOLD = 6; // 桶中结构转化为红黑树对应的table的最小大小 static final int MIN_TREEIFY_CAPACITY = 64; // 存储元素的数组,总是2的幂次倍 transient Node<k,v>[] table; // 存放具体元素的集 transient Set<map.entry<k,v>> entrySet; // 存放元素的个数,注意这个不等于数组的长度。 transient int size; // 每次扩容和更改map结构的计数器 transient int modCount; // 临界值 当实际大小(容量*填充因子)超过临界值时,会进行扩容 int threshold; // 填充因子 final float loadFactor; } loadFactor加载因子 loadFactor加载因子是控制数组存放数据的疏密程度,loadFactor越趋近于1,那么 数组中存放的数据(entry)也就越多,也就越密,也就是会让链表的长度增加,load Factor越小,也就是趋近于0, loadFactor太大导致查找元素效率低,太小导致数组的利用率低,存放的数据会很分散。loadFactor的默认值为0.75f是官方给出的一个比较好的临界值。 threshold threshold = capacity * loadFactor,当Size>=threshold的时候,那么就要考虑对数组的扩增了,也就是说,这个的意思就是衡量数组是否需要扩增的一个标准。 Node节点类源码: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 // 继承自 Map.Entry<K,V> static class Node<K,V> implements Map.Entry<K,V> { final int hash;// 哈希值,存放元素到hashmap中时用来与其他元素hash值比较 final K key;//键 V value;//值 // 指向下一个节点 Node<K,V> next; Node(int hash, K key, V value, Node<K,V> next) { this.hash = hash; this.key = key; this.value = value; this.next = next; } public final K getKey() { return key; } public final V getValue() { return value; } public final String toString() { return key + "=" + value; } // 重写hashCode()方法 public final int hashCode() { return Objects.hashCode(key) ^ Objects.hashCode(value); } public final V setValue(V newValue) { V oldValue = value; value = newValue; return oldValue; } // 重写 equals() 方法 public final boolean equals(Object o) { if (o == this) return true; if (o instanceof Map.Entry) { Map.Entry<?,?> e = (Map.Entry<?,?>)o; if (Objects.equals(key, e.getKey()) && Objects.equals(value, e.getValue())) return true; } return false; } } 树节点类源码: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> { TreeNode<K,V> parent; // 父 TreeNode<K,V> left; // 左 TreeNode<K,V> right; // 右 TreeNode<K,V> prev; // needed to unlink next upon deletion boolean red; // 判断颜色 TreeNode(int hash, K key, V val, Node<K,V> next) { super(hash, key, val, next); } // 返回根节点 final TreeNode<K,V> root() { for (TreeNode<K,V> r = this, p;;) { if ((p = r.parent) == null) return r; r = p; } HashMap源码分析 构造方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 // 默认构造函数。 public More ...HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted } // 包含另一个“Map”的构造函数 public More ...HashMap(Map<? extends K, ? extends V> m) { this.loadFactor = DEFAULT_LOAD_FACTOR; putMapEntries(m, false);//下面会分析到这个方法 } // 指定“容量大小”的构造函数 public More ...HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } // 指定“容量大小”和“加载因子”的构造函数 public More ...HashMap(int initialCapacity, float loadFactor) { if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); this.loadFactor = loadFactor; this.threshold = tableSizeFor(initialCapacity); } putMapEntries方法: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) { int s = m.size(); if (s > 0) { // 判断table是否已经初始化 if (table == null) { // pre-size // 未初始化,s为m的实际元素个数 float ft = ((float)s / loadFactor) + 1.0F; int t = ((ft < (float)MAXIMUM_CAPACITY) ? (int)ft : MAXIMUM_CAPACITY); // 计算得到的t大于阈值,则初始化阈值 if (t > threshold) threshold = tableSizeFor(t); } // 已初始化,并且m元素个数大于阈值,进行扩容处理 else if (s > threshold) resize(); // 将m中的所有元素添加至HashMap中 for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) { K key = e.getKey(); V value = e.getValue(); putVal(hash(key), key, value, false, evict); } } } put方法 HashMap只提供了put用于添加元素,putVal方法只是给put方法调用的一个方法,并没有提供给用户使用。 对putVal方法添加元素的分析如下: ①如果定位到的数组位置没有元素 就直接插入。 ②如果定位到的数组位置有元素就和要插入的 key 比较,如果key相同就直接覆盖,如果 key 不相同,就判断 p 是否是一个树节点,如果是就调用e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value)将元素添加进入。如果不是就遍历链表插入。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 public V put(K key, V value) { return putVal(hash(key), key, value, false, true); } final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; // table未初始化或者长度为0,进行扩容 if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; // (n - 1) & hash 确定元素存放在哪个桶中,桶为空,新生成结点放入桶中(此时,这个结点是放在数组中) if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); // 桶中已经存在元素 else { Node<K,V> e; K k; // 比较桶中第一个元素(数组中的结点)的hash值相等,key相等 if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) // 将第一个元素赋值给e,用e来记录 e = p; // hash值不相等,即key不相等;为红黑树结点 else if (p instanceof TreeNode) // 放入树中 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); // 为链表结点 else { // 在链表最末插入结点 for (int binCount = 0; ; ++binCount) { // 到达链表的尾部 if ((e = p.next) == null) { // 在尾部插入新结点 p.next = newNode(hash, key, value, null); // 结点数量达到阈值,转化为红黑树 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); // 跳出循环 break; } // 判断链表中结点的key值与插入的元素的key值是否相等 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) // 相等,跳出循环 break; // 用于遍历桶中的链表,与前面的e = p.next组合,可以遍历链表 p = e; } } // 表示在桶中找到key值、hash值与插入元素相等的结点 if (e != null) { // 记录e的value V oldValue = e.value; // onlyIfAbsent为false或者旧值为null if (!onlyIfAbsent || oldValue == null) //用新值替换旧值 e.value = value; // 访问后回调 afterNodeAccess(e); // 返回旧值 return oldValue; } } // 结构性修改 ++modCount; // 实际大小大于阈值则扩容 if (++size > threshold) resize(); // 插入后回调 afterNodeInsertion(evict); return null; } 我们再来对比一下 JDK1.7 put方法的代码 对于put方法的分析如下: ①如果定位到的数组位置没有元素 就直接插入。 ②如果定位到的数组位置有元素,遍历以这个元素为头结点的链表,依次和插入的key比较,如果key相同就直接覆盖,不同就采用头插法插入元素。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public V put(K key, V value) if (table == EMPTY_TABLE) { inflateTable(threshold); } if (key == null) return putForNullKey(value); int hash = hash(key); int i = indexFor(hash, table.length); for (Entry<K,V> e = table[i]; e != null; e = e.next) { // 先遍历 Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(hash, key, value, i); // 再插入 return null; } get方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public V get(Object key) { Node<K,V> e; return (e = getNode(hash(key), key)) == null ? null : e.value; } final Node<K,V> getNode(int hash, Object key) { Node<K,V>[] tab; Node<K,V> first, e; int n; K k; if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) { // 数组元素相等 if (first.hash == hash && // always check first node ((k = first.key) == key || (key != null && key.equals(k)))) return first; // 桶中不止一个节点 if ((e = first.next) != null) { // 在树中get if (first instanceof TreeNode) return ((TreeNode<K,V>)first).getTreeNode(hash, key); // 在链表中get do { if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) return e; } while ((e = e.next) != null); } } return null; } resize方法 进行扩容,会伴随着一次重新hash分配,并且会遍历hash表中所有的元素,是非常耗时的。在编写程序中,要尽量避免resize。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 final Node<K,V>[] resize() { Node<K,V>[] oldTab = table; int oldCap = (oldTab == null) ? 0 : oldTab.length; int oldThr = threshold; int newCap, newThr = 0; if (oldCap > 0) { // 超过最大值就不再扩充了,就只好随你碰撞去吧 if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; } // 没超过最大值,就扩充为原来的2倍 else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) newThr = oldThr << 1; // double threshold } else if (oldThr > 0) // initial capacity was placed in threshold newCap = oldThr; else { signifies using defaults newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } // 计算新的resize上限 if (newThr == 0) { float ft = (float)newCap * loadFactor; newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE); } threshold = newThr; @SuppressWarnings({"rawtypes","unchecked"}) Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; table = newTab; if (oldTab != null) { // 把每个bucket都移动到新的buckets中 for (int j = 0; j < oldCap; ++j) { Node<K,V> e; if ((e = oldTab[j]) != null) { oldTab[j] = null; if (e.next == null) newTab[e.hash & (newCap - 1)] = e; else if (e instanceof TreeNode) ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); else { Node<K,V> loHead = null, loTail = null; Node<K,V> hiHead = null, hiTail = null; Node<K,V> next; do { next = e.next; // 原索引 if ((e.hash & oldCap) == 0) { if (loTail == null) loHead = e; else loTail.next = e; loTail = e; } // 原索引+oldCap else { if (hiTail == null) hiHead = e; else hiTail.next = e; hiTail = e; } } while ((e = next) != null); // 原索引放到bucket里 if (loTail != null) { loTail.next = null; newTab[j] = loHead; } // 原索引+oldCap放到bucket里 if (hiTail != null) { hiTail.next = null; newTab[j + oldCap] = hiHead; } } } } } return newTab; } HashMap常用方法测试 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 package map; import java.util.Collection; import java.util.HashMap; import java.util.Set; public class HashMapDemo { public static void main(String[] args) { HashMap<String, String> map = new HashMap<String, String>(); // 键不能重复,值可以重复 map.put("san", "张三"); map.put("si", "李四"); map.put("wu", "王五"); map.put("wang", "老王"); map.put("wang", "老王2");// 老王被覆盖 map.put("lao", "老王"); System.out.println("-------直接输出hashmap:-------"); System.out.println(map); /** * 遍历HashMap */ // 1.获取Map中的所有键 System.out.println("-------foreach获取Map中所有的键:------"); Set<String> keys = map.keySet(); for (String key : keys) { System.out.print(key+" "); } System.out.println();//换行 // 2.获取Map中所有值 System.out.println("-------foreach获取Map中所有的值:------"); Collection<String> values = map.values(); for (String value : values) { System.out.print(value+" "); } System.out.println();//换行 // 3.得到key的值的同时得到key所对应的值 System.out.println("-------得到key的值的同时得到key所对应的值:-------"); Set<String> keys2 = map.keySet(); for (String key : keys2) { System.out.print(key + ":" + map.get(key)+" "); } /** * 另外一种不常用的遍历方式 */ // 当我调用put(key,value)方法的时候,首先会把key和value封装到 // Entry这个静态内部类对象中,把Entry对象再添加到数组中,所以我们想获取 // map中的所有键值对,我们只要获取数组中的所有Entry对象,接下来 // 调用Entry对象中的getKey()和getValue()方法就能获取键值对了 Set<java.util.Map.Entry<String, String>> entrys = map.entrySet(); for (java.util.Map.Entry<String, String> entry : entrys) { System.out.println(entry.getKey() + "--" + entry.getValue()); } /** * HashMap其他常用方法 */ System.out.println("after map.size():"+map.size()); System.out.println("after map.isEmpty():"+map.isEmpty()); System.out.println(map.remove("san")); System.out.println("after map.remove():"+map); System.out.println("after map.get(si):"+map.get("si")); System.out.println("after map.containsKey(si):"+map.containsKey("si")); System.out.println("after containsValue(李四):"+map.containsValue("李四")); System.out.println(map.replace("si", "李四2")); System.out.println("after map.replace(si, 李四2):"+map); } }

优秀的个人博客,低调大师

Golang面试题解析(五)

41.执行下面的代码发生什么? package main type Param map[string]interface{} type Show struct { *Param } func main() { s := new(Show) s.Param["RMB"] = 10000 } 考点:map初始化 map需要初始化后才能使用。 编译错误:invalid operation: s.Param["RMB"] (type *Param does not support indexing) 42.执行下面的代码发生什么? package main import "fmt" type student struct { Name string } func zhoujielun(v interface{}) { switch msg := v.(type) { case *student, student: msg.Name = "qq" fmt.Print(msg) } } 考点:类型转换 msg不属于student类型,所以没有Name字段。 改为: s := v.(student) s.Name = "qq" 43.执行下面的代码发生什么? package main import ( "encoding/json" "fmt" ) type People struct { name string `json:"name"` } func main() { js := `{ "name":"11" }` var p People err := json.Unmarshal([]byte(js), &p) if err != nil { fmt.Println("err: ", err) return } fmt.Println("people: ", p) } 考点:结构体访问控制 这道题坑很大,很多同学一看就以为是p的初始化问题,实际上是因为name首字母是小写,导致其他包不能访问,所以输出为空结构体。 改为: type People struct { Name string `json:"name"` } 44.以下代码有什么问题? package main func Stop(stop <-chan bool) { close(stop) } 考点:close channel 有方向的channel不可被关闭 45.实现一个函数可以根据指定的size切割切片为多个小切片 解析 func main() { lenth := 11 size := 5 list := make([]int, 0, lenth) for i := 0; i < lenth; i++ { list = append(list, i) } SpiltList(list, size) } func SpiltList(list []int, size int) { lens := len(list) mod := math.Ceil(float64(lens) / float64(size)) spliltList := make([][]int, 0) for i := 0; i < int(mod); i++ { tmpList := make([]int, 0, size) fmt.Println("i=", i) if i == int(mod)-1 { tmpList = list[i*size:] } else { tmpList = list[i*size : i*size+size] } spliltList = append(spliltList, tmpList) } for i, sp := range spliltList { fmt.Println(i, " ==> ", sp) } } 46.实现两个go轮流输出:A1B2C3.....Z26 解析 方法一:有缓冲chan func ChannelFunc() { zimu := make(chan int, 1) suzi := make(chan int, 1) zimu <- 0 // zimu go func() { for i := 65; i <= 90; i++ { <-zimu fmt.Printf("%v", string(rune(i))) suzi <- i } return }() go func() { for i := 1; i <= 26; i++ { <-suzi fmt.Printf("%v", i) zimu <- i } return }() time.Sleep(1 * time.Second) fmt.Println() } 方法二:无缓冲chan func Channel1Func() { zimu := make(chan int) suzi := make(chan int) // zimu go func() { for i := 65; i <= 90; i++ { fmt.Printf("%v", string(rune(i))) zimu <- i <-suzi } return }() go func() { for i := 1; i <= 26; i++ { <-zimu fmt.Printf("%v", i) suzi <- i } return }() time.Sleep(10 * time.Second) fmt.Println() } 方法三:使用锁 大家可以自己实现,把结果留言给我,答案后续公布。 47.执行下面代码输出什么? package main // 47.执行下面代码输出什么? import "fmt" func main() { five := []string{"Annie", "Betty", "Charley", "Doug", "Edward"} for _, v := range five { five = five[:2] fmt.Printf("v[%s]\n", v) } } 考点:range副本机制 循环内的切片值会缩减为2,但循环将在切片值的自身副本上进行操作。 这允许循环使用原始长度进行迭代而没有任何问题,因为后备数组仍然是完整的。 结果: v[Annie] v[Betty] v[Charley] v[Doug] v[Edward] 48.for 和 for range有什么区别? 考点:for range 使用场景不同 for可以 遍历array和slice 遍历key为整型递增的map 遍历string for range可以完成所有for可以做的事情,却能做到for不能做的,包括 遍历key为string类型的map并同时获取key和value 遍历channel 实现不同 for可以获取到的是被循环对象的元素本身,可以对其进行修改; for range使用值拷贝的方式代替被遍历的元素本身,是一个值拷贝,而不是元素本身。 49.解决下面问题:输出MutilParam= [ssss [1 2 3 4]]如何做到输出为[ssss 1 2 3 4]? package main import "fmt" func MutilParam(p ...interface{}) { fmt.Println("MutilParam=", p) } func main() { MutilParam("ssss", 1, 2, 3, 4) //[ssss 1 2 3 4] iis := []int{1, 2, 3, 4} MutilParam("ssss", iis) //输出MutilParam= [ssss [1 2 3 4]]如何做到输出为[ssss 1 2 3 4] } 考点:函数变参 这样的情况会在开源类库如xorm升级版本后出现Exce函数不兼容的问题。 解决方式有两个: 方法一:interface[] tmpParams := make([]interface{}, 0, len(iis)+1) tmpParams = append(tmpParams, "ssss") for _, ii := range iis { tmpParams = append(tmpParams, ii) } MutilParam(tmpParams...) 方法二:反射 f := MutilParam value := reflect.ValueOf(f) pps := make([]reflect.Value, 0, len(iis)+1) pps = append(pps, reflect.ValueOf("ssss")) for _, ii := range iis { pps = append(pps, reflect.ValueOf(ii)) } value.Call(pps) 50.编译并运行如下代码会发生什么? package main // 50.编译并运行如下代码会发生什么? import "fmt" func main() { mmap := make(map[map[string]string]int, 0) mmap[map[string]string{"a": "a"}] = 1 mmap[map[string]string{"b": "b"}] = 1 mmap[map[string]string{"c": "c"}] = 1 fmt.Println(mmap) } 考点:map key类型 golang中的map,的 key 可以是很多种类型,比如 bool, 数字,string, 指针, channel , 还有 只包含前面几个类型的 interface types, structs, arrays。 显然,slice, map 还有 function 是不可以了,因为这几个没法用 == 来判断,即不可比较类型。 可以将map[map[string]string]int改为map[struct]int。

优秀的个人博客,低调大师

web前端系列面试题(一)

html&css 行内元素有哪些?块级元素有哪些?空元素有那些? link和@import的区别是? 请描述一下cookies ,sessionStorage 和 localStorage 的区别? display 与 与 visibility 有何异同? js JavaScript 中,this 关键字的作用是什么? 什么是正则表达式?在 JavaScript 中,如何应用正则表达式? JavaScript原型,原型链 ? 有什么特点? 编程题:编写一个数组去重的方法。 介绍JavaScript的基本数据类型。 说说写JavaScript的基本规范? JavaScript原型,原型链 ? 有什么特点? JavaScript有几种类型的值?(堆:原始数据类型和 栈:引用数据类型),你能画一下他们的内存图吗? Javascript如何实现继承? Javascript创建对象的几种方式? Javascript作用链域? 谈谈This对象的理解。 eval是做什么的? 什么是window对象? 什么是document对象? null,undefined的区别? 写一个通用的事件侦听器函数(机试题)。 [“1”, “2”, “3”].map(parseInt) 答案是多少? 关于事件,IE与火狐的事件机制有什么区别? 如何阻止冒泡? 什么是闭包(closure),为什么要用它? javascript 代码中的”use strict”;是什么意思 ? 使用它区别是什么? 如何判断一个对象是否属于某个类? new操作符具体干了什么呢? 用原生JavaScript的实现过什么功能吗? Javascript中,有一个函数,执行时对象查找时,永远不会去查找原型,这个函数是? 对JSON的了解? [].forEach.call($$(""),function(a){ a.style.outline="1px solid #"+(~~(Math.random()(1<<24))).toString(16) }) 能解释一下这段代码的意思吗? js延迟加载的方式有哪些? Ajax 是什么? 如何创建一个Ajax? 同步和异步的区别? 如何解决跨域问题? 页面编码和被请求的资源编码如果不一致如何处理? 模块化开发怎么做? AMD(Modules/Asynchronous-Definition)、CMD(Common Module Definition)规范区别? requireJS的核心原理是什么?(如何动态加载的?如何避免多次加载的?如何 缓存的?) 让你自己设计实现一个requireJS,你会怎么做? 谈一谈你对ECMAScript6的了解? ECMAScript6 怎么写class么,为什么会出现class这种东西? 异步加载的方式有哪些? documen.write和 innerHTML的区别? DOM操作——怎样添加、移除、移动、复制、创建和查找节点? .call() 和 .apply() 的含义和区别? 数组和对象有哪些原生方法,列举一下? JS 怎么实现一个类。怎么实例化这个类 JavaScript中的作用域与变量声明提升? 如何编写高性能的Javascript? 那些操作会造成内存泄漏? JQuery的源码看过吗?能不能简单概况一下它的实现原理? jQuery.fn的init方法返回的this指的是什么对象?为什么要返回this? jquery中如何将数组转化为json字符串,然后再转化回来? jQuery 的属性拷贝(extend)的实现原理是什么,如何实现深拷贝? jquery.extend 与 jquery.fn.extend的区别? jQuery 的队列是如何实现的?队列可以用在哪些地方? 谈一下Jquery中的bind(),live(),delegate(),on()的区别? JQuery一个对象可以同时绑定多个事件,这是如何实现的? 是否知道自定义事件。jQuery里的fire函数是什么意思,什么时候用? jQuery 是通过哪个方法和 Sizzle 选择器结合的?(jQuery.fn.find()进入Sizzle) 针对 jQuery性能的优化方法? Jquery与jQuery UI有啥区别? JQuery的源码看过吗?能不能简单说一下它的实现原理? jquery 中如何将数组转化为json字符串,然后再转化回来? jQuery和Zepto的区别?各自的使用场景? 针对 jQuery 的优化方法? Zepto的点透问题如何解决? jQueryUI如何自定义组件? 需求:实现一个页面操作不会整页刷新的网站,并且能在浏览器前进、后退时正确响应。给出你的技术实现方案? 如何判断当前脚本运行在浏览器还是node环境中?(阿里) 移动端最小触控区域是多大? jQuery 的 slideUp动画 ,如果目标元素是被外部事件驱动, 当鼠标快速地连续触发外部元素事件, 动画会滞后的反复执行,该如何处理呢? 把 Script 标签 放在页面的最底部的body封闭之前 和封闭之后有什么区别?浏览器会如何解析它们? 移动端的点击事件的有延迟,时间是多久,为什么会有? 怎么解决这个延时?(click 有 300ms 延迟,为了实现safari的双击事件的设计,浏览器要知道你是不是要双击操作。) 知道各种JS框架(Angular, Backbone, Ember, React, Meteor, Knockout…)么? 能讲出他们各自的优点和缺点么? Underscore 对哪些 JS 原生对象进行了扩展以及提供了哪些好用的函数方法? 解释JavaScript中的作用域与变量声明提升? 那些操作会造成内存泄漏? JQuery一个对象可以同时绑定多个事件,这是如何实现的? Node.js的适用场景? (如果会用node)知道route, middleware, cluster, nodemon, pm2, server-side rendering么? 解释一下 Backbone 的 MVC 实现方式? 什么是“前端路由”?什么时候适合使用“前端路由”? “前端路由”有哪些优点和缺点? 知道什么是webkit么? 知道怎么用浏览器的各种工具来调试和debug代码么? 如何测试前端代码么? 知道BDD, TDD, Unit Test么? 知道怎么测试你的前端工程么(mocha, sinon, jasmin, qUnit..)? 前端templating(Mustache, underscore, handlebars)是干嘛的, 怎么用? 简述一下 Handlebars 的基本用法? 简述一下 Handlerbars 的对模板的基本处理流程, 如何编译的?如何缓存的? 用js实现千位分隔符?(来源:前端农民工,提示:正则+replace) 检测浏览器版本版本有哪些方式? 我们给一个dom同时绑定两个点击事件,一个用捕获,一个用冒泡,你来说下会执行几次事件,然后会先执行冒泡还是捕获

优秀的个人博客,低调大师

阿里巴巴JAVA面试真题

一面(36分钟) 芝麻信用 hashmap和hashtable区别 为什么产生死锁 jvm类加载 java反射获取私有属性,改变值 反射用途 所用数据库 项目难点,问题 如何解决项目中遇到的问题 项目中遇到最自豪的地方 会什么算法 二面(44分钟) 支付宝创新 讲项目 数据库乐观锁使用 状态机 如何解决状态机不对问题 如何分库分表 MySQL极限 HashMap源码 设计一个线程安全的HashMap 快排的实现,时间复杂度和空间复杂度 会什么算法 如何把项目变成SOA架构 Spring源码,最深刻的模块,aop用途 JVM内存模型 垃圾回收机制 项目中查看垃圾回收 三面(33分钟) 菜鸟国际 项目中的权限管理 登录状态如何储存 session和cookie的区别,session如何管理 HashMap底层结构 synchronized关键字的用法 synchronized修饰类方法和普通方法的锁区别,获取类锁之后还能获取对象锁吗 类加载器的双亲委派模型的作用,能重复加载某个类吗 类加载器的类的缓存,key是什么 介绍Redis 如何将数据分布在不同的Redis 有了解过取余算法? spring的apo实现 字节码结构 浏览器输入网址过程,结合springmvc 四面(36分钟) 菜鸟国际供应链 HashMap在大量哈希冲突该怎么处理 红黑树比BST优点 MySQL为什么使用B+树 多个索引会有多份数据吗 数据库的隔离级别和解决的问题 数据库默认隔离级别,一定会产生幻读吗,怎么解决 输入网址到展示的整个过程,结合springmvc来讲 负载均衡的算法 哈弗曼编码,如何解决译码问题 实习会对工作有影响吗 用英文介绍一个项目 如何查看系统负载 描述一个解决问题的过程 如何把文件从服务器复制到本地,用什么命令 五面(27分钟) 菜鸟技术部 当时怎么找到百度这个机会的 项目中用的哪些技术 项目如何设计流程流转,如果是你的话该怎么设计 MySQL使用的索引结构,查找效率 MySQL查询优化 MySQL慢查询开启,语句分析 HashMap查找效率 JVM内存模型 设计模式,策略模式的使用场景 如何确保单例线程安全 Spring的bean的默认范围 对Netty的了解 未来发展规划 如何让代码可读性更加复杂

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册