JAVA基础面试题大全 1

简介: JAVA基础面试题大全

1. JAVA基础

1.1 Java基本类型有哪些?它们分别占用多少字节?

Java中的基本类型包括:

  • byte(1字节)
  • short(2字节)
  • char(2字节)
  • int(4字节)
  • float(4字节)
  • double(8字节)
  • long(8字节)
  • boolean(未定,和具体Java虚拟机实现有关)

关于boolean的占用大小,事实上,1 bit是计算机存储的最小单位,用它来表示boolean的语义也完全足够。但是考虑到计算机处理数据的最小单位是1byte,因此实际的存储空间至少为1byte。而在《Java虚拟机规范》一书中描述,对于当下32位的处理器(CPU)来说,一次处理数据是32位,32 位 CPU 使用 4 个字节是最为节省的,哪怕你是1 bit信息也需要占用 4 个字节。因为 CPU 寻址系统只能 32 位 32 位地寻址,具有高效存取的特点。

1.2 什么是Java装箱和拆箱?其原理?

Java中每一种基本数据类型都对应着一种包装器类型。装箱是指Java能够自动将基本数据类型自动转化成包装器类型拆箱是指Java能够自动将包装器类型转化为基本数据类型。例如:


    Integer i = 10;  //装箱
    int j = i;  //拆箱

    装箱是自动调用了Integer.valueOf(int i)方法。拆箱则是调用了Integer.intValue(Integer i)方法。

    阅读下面一段代码:

      public class Main {  
      public static void main(String[] args) {      
      Integer i1 = 100;      
      Integer i2 = 100;      
      Integer i3 = 200;       
      Integer i4 = 200;    
      System.out.println(i1==i2);      
      System.out.println(i3==i4);  
      }
      }


      其输出值为:

        true
        false

        参考Integer.valueOf(int i)的实现:


          public static Integer valueOf(int i) {    
          if (i >= IntegerCache.low && i <= IntegerCache.high)    
          return IntegerCache.cache[i + (-IntegerCache.low)];    
          return new Integer(i);   
          }


          可以看到当i的范围在[-128, 127]时,Integer直接指向IntegerCache中的对象。否则,创建一个新的Integer对象。

            public static Double valueOf(double d) {   
            return new Double(d);
            }


            因此装箱后的Double对象的比较总是返回false。

            下面是Boolean.valueOf(boolean b)的实现:

            而Double.valueOf(double d)的实现如下:
              public static Boolean valueOf(boolean b) {     
              return (b ? TRUE : FALSE);
              }


              因此装箱后的Boolean对象的比较总是返回true。

              1.3 String,StringBuffer和StringBuilder的区别

              String类是不可变类,任何对于String对象的改变都会生成一个新的对象。StringBuffer和StringBuilder都是可变类,任何修改都会造成其内部的char数组的改变。StringBuffer是线程安全的,而StringBuilder则是线程不安全的。

              1.4 Java类加载机制

              • Java类加载机制

              1.5 LocalThread

              • ThreadLocal的实现

              1.6 引用类型

              从JDK 1.2版本开始,把对象的引用分为4种级别,从而使程序能更加灵活地控制对象的生命周期。这4种级别由高到低依次为:强引用、软引用、弱引用和虚引用。

              • 强引用(StrongReference):强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。
              • 软引用(SoftReference):如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。
              • 弱引用(WeakReference):只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
              • 虚引用(PhantomReference):虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。

              1.7 hashcode的设计

              Java String类的hashcode方法实现如下:

                public int hashCode() {
                        int h = hash;
                        if (h == 0 && value.length > 0) {
                            char val[] = value;
                            for (int i = 0; i < value.length; i++) {
                                h = 31 * h + val[i];
                            }
                            hash = h;
                        }
                        return h;
                }

                关于hashcode的计算为何选取质数31,下面是一个解释:https://www.zhihu.com/question/24381016/answer/160483854

                因此,一个常见的重写对象的hashcode的方法如下:

                A、初始化一个整形变量,为此变量赋予一个非零的常数值,比如int result = 17;

                B、选取equals方法中用于比较的所有域,然后针对每个域的属性进行计算:

                • 如果是boolean值,则计算 f ? 1:0
                • 如果是byte\char\short\int,则计算 (int)f
                • 如果是long值,则计算 (int)(f ^ (f >>> 32))
                • 如果是float值,则计算 Float.floatToIntBits(f)
                • 如果是double值,则计算 Double.doubleToLongBits(f),然后返回的结果是long,再用计算long值hashcode的方法去处理long,得到hashcode
                • 如果是对象应用,如果equals方法中采取递归调用的比较方式,那么hashCode中同样采取递归调用hashCode的方式。否则需要为这个域计算一个范式,比如当这个域的值为null的时候,那么hashCode 值为0
                • 如果是数组,那么需要为每个元素当做单独的域来处理。java.util.Arrays.hashCode方法包含了8种基本类型数组和引用数组的hashCode计算,算法同上。

                C、最后,把每个域的散列码合并到对象的哈希码中。

                下面是一个例子:

                public class Person {
                    private String name;
                    private int age;
                    private boolean gender;
                    public String getName() {
                        return name;
                    }
                    public void setName(String name) {
                        this.name = name;
                    }
                    public int getAge() {
                        return age;
                    }
                    public void setAge(int age) {
                        this.age = age;
                    }
                    public boolean isGender() {
                        return gender;
                    }
                    public void setGender(boolean gender) {
                        this.gender = gender;
                    }
                    @Override
                    public boolean equals(Object o) {
                        if (this == o) return true;
                        if (o == null || getClass() != o.getClass()) return false;
                        Person person = (Person) o;
                        return age == person.age &&
                                gender == person.gender &&
                                Objects.equals(name, person.name);
                    }
                    @Override
                    public int hashCode() {
                        int hash = 17;
                        hash = hash * 31 + getName().hashCode();
                        hash = hash * 31 + getAge();
                        return hash;
                    }

                1.8 重写和重载

                重写(Override)是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。

                而重载(overloading) 是指在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。

                1.9 Happens-Before规则

                • 重排序,可见性,内存屏障和Happens-Before

                1.10 NIO

                2. Java集合类

                2.1 ArrayList

                ArrayList本质上是一个动态数组,它是线程不安全的,读写时间为O(1)。

                它的内部数据结构主要为:

                //默认初始数组大小:10
                private static final int DEFAULT_CAPACITY = 10;
                //用于存储数据的数组
                transient Object[] elementData; 
                //记录size的变量
                private int size;

                transient表示elementData数组不参与序列化。然而在writeObject方法中,elementData数组又参与了序列化过程,这是为什么呢?因为elementData数组的length往往并不等于ArrayList.size,因此直接使用默认的序列化过程,会将elementData数组中的额外空间也序列化到输出流中。因此采用自定义writeObject方法,手动从elementData数组中取出size个元素进行序列化。具体实现如下:

                private void writeObject(java.io.ObjectOutputStream s)
                        throws java.io.IOException{
                    // Write out element count, and any hidden stuff
                    int expectedModCount = modCount;
                    s.defaultWriteObject();
                    // Write out size as capacity for behavioural compatibility with clone()
                    s.writeInt(size);
                    // Write out all elements in the proper order.
                    for (int i=0; i<size; i++) {
                        s.writeObject(elementData[i]);
                    }
                    if (modCount != expectedModCount) {
                        throw new ConcurrentModificationException();
                }
                }

                add(E element), addAll(Collection<E> elements)方法总结:1. 首先判断是否越界,是否需要扩容。2. 如果需要扩容,默认扩容当前大小的1.5倍。如果还不够,则直接扩容为所需要的最小size。然后复制数组。3. 修改modCount变量。

                  public boolean add(E e) {
                      ensureCapacityInternal(size + 1);  // Increments modCount!!   
                      elementData[size++] = e;//在数组末尾追加一个元素,并修改size   
                      return true;
                      }


                  private static final int DEFAULT_CAPACITY = 10;//默认扩容容量 10
                  private void ensureCapacityInternal(int minCapacity) {
                      //利用 == 可以判断数组是否是用默认构造函数初始化的
                      if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
                          minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
                      }
                      ensureExplicitCapacity(minCapacity);
                  }
                  private void ensureExplicitCapacity(int minCapacity) {
                      modCount++;//如果确定要扩容,会修改modCount 
                      // overflow-conscious code
                      if (minCapacity - elementData.length > 0)
                          grow(minCapacity);
                  }
                  //需要扩容的话,默认扩容一半
                  private void grow(int minCapacity) {
                      // overflow-conscious code
                      int oldCapacity = elementData.length;
                      int newCapacity = oldCapacity + (oldCapacity >> 1);//默认扩容一半
                      if (newCapacity - minCapacity < 0)//如果还不够 ,那么就用 能容纳的最小的数量。
                          newCapacity = minCapacity;
                      if (newCapacity - MAX_ARRAY_SIZE > 0)
                          newCapacity = hugeCapacity(minCapacity);
                      elementData = Arrays.copyOf(elementData, newCapacity);//拷贝,扩容,构建一个新数组
                  }
                  

                  E Remove(int index)方法总结:1. 首先判断是否越界 2. 修改modCount,读出要删除的值,然后将[index + 1, size]内的所有元素复制到[index, size - 1]位置 3. 将下标为size-1的元素置为null 4. 返回被删除的元素

                    public E remove(int index) {
                        rangeCheck(index);//判断是否越界
                        modCount++;//修改modeCount 因为结构改变了
                        E oldValue = elementData(index);//读出要删除的值
                        int numMoved = size - index - 1;
                        if (numMoved > 0)
                            System.arraycopy(elementData, index+1, elementData, index,numMoved);
                        elementData[--size] = null; // clear to let GC do its work 
                        return oldValue;
                    }

                    modCount变量用于记录List被修改的次数,因此在使用Iterator遍历时调用List.remove(E element)时会抛出java.util.ConcurrentModificationException。原因在于调用List.remove(E element)后会修改modCount变量,而Iterator遍历时会判断当前modCount值是否和expectedModCount(第一次遍历时的modCount)相同,如果不相同,则抛出java.util.ConcurrentModificationException。

                    增删改查中, add(E element)会导致扩容,因此会修改modCount,remove(E element)也会修改数组,因此也会修改modCount。set(int index, E element)和get(int index)则不会修改modCount。

                    Vector内部也通过数组实现,它和ArrayList别在于Vector在API上都加了synchronized,所以它是线程安全的,并且Vector扩容时,是翻倍size,而ArrayList则是扩容50%。

                    2.2 LinkedList

                    LinkedList是基于双向链表实现的有序序列,它可以在任何位置进行高效的插入和删除。

                    它的内部数据结构如下:

                    // 链表长度
                    transient int size = 0;
                    // 链表头节点
                    transient Node<E> first;
                    // 链表尾节点
                    transient Node<E> last;
                    其中链表节点的数据结构如下:
                    
                      private static class Node<E> {
                          E item;      // 此节点包含的数据
                          Node<E> next;  // 下一个节点的引用
                          Node<E> prev;  // 前一个节点的引用
                          Node(Node<E> prev, E element, Node<E> next) {
                              this.item = element;
                              this.next = next;
                              this.prev = prev;
                          }
                      }

                      下面是boolean add(E element)方法的实现:

                        public boolean add(E e) {
                            linkLast(e);
                            return true;
                        }
                        void linkLast(E e) {
                            final Node<E> l = last;  // 获取尾部元素
                            final Node<E> newNode = new Node<>(l, e, null);
                            last = newNode;  // 更新尾部节点为需要插入的节点
                            if (l == null)
                                first = newNode;
                            else
                                l.next = newNode;
                            size++;
                            modCount++;
                        }

                        boolean add(E element)方法总结:1. 创建一个新节点,将last节点设置为其前节点,后节点为null 2. 如果last节点为null,将first节点设置为当前节点 3. 将当前节点设置为last节点,并将当前节点设置为原有last节点的后节点 4. 链表长度加1

                        下面是E get(int index)方法的实现:

                          public E get(int index) {
                              checkElementIndex(index);
                              return node(index).item;
                          }
                           Node<E> node(int index) {
                              if (index < (size >> 1)) {
                                  Node<E> x = first;
                                  for (int i = 0; i < index; i++)
                                      x = x.next;
                                  return x;
                              } else {
                                  Node<E> x = last;
                                  for (int i = size - 1; i > index; i--)
                                      x = x.prev;
                                  return x;
                              }
                          }

                          由于链表不存在下标索引,因此要找出指定位置的元素,就需要遍历整个链表。这儿使用了一种加速手段,当index处于链表的前半段时,那么就从首节点开始遍历,反之则从尾节点遍历。

                          下面是E remove(int index)方法的实现:

                            public E remove(int index) {
                                // 移除元素索引的合理性检查
                                checkElementIndex(index);
                                // 将节点删除
                                return unlink(node(index));
                            }
                             E unlink(Node<E> x) {
                                 final E element = x.item;  // 得到指定节点的值
                                 final Node<E> next = x.next; // 得到指定节点的后继节点
                                 final Node<E> prev = x.prev; // 得到指定节点的前继节点
                                 // 如果prev为null表示删除是头节点,否则就不是头节点
                                 if (prev == null) {
                                     first = next;
                                 } else {
                                     prev.next = next;
                                     x.prev = null; // 置空需删除的指定节点的前置节点(null)
                                 }
                                 // 如果next为null,则表示删除的是尾部节点,否则就不是尾部节点
                                 if (next == null) {
                                     last = prev;
                                 } else {
                                     next.prev = prev;
                                     x.next = null; //  置空需删除的指定节点的后置节点
                                 }
                                 // 置空需删除的指定节点的值
                                 x.item = null;
                                 size--; // 数量减1
                                 modCount++;
                                 return element;
                            }

                            LinkedList与ArrayList在性能上各有优缺点,都有各自适用的地方,总结如下:

                            • ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。
                            • LinkedList不支持高效的随机元素访问。
                            • ArrayList的空间浪费主要体现在在list列表的结尾预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗相当的空间,就存储密度来说,ArrayList是优于LinkedList的。
                            • LinkedList能够或得更好的插入和删除表现,而ArrayList的随机读取表现更好。

                            2.3 HashMap

                            HashMap是一种底层为数组的哈希表实现。它是线程不安全的,允许key为null,value为null,遍历时无序。

                            它的内部数据结构如下:

                              transient Node<K,V>[] table;  //哈希桶,存放链表。初始大小为16
                              transient int size;     // HashMap中实际存在的Node数量
                              final float loadFactor;  //加载因子,用于计算哈希表元素数量的阈值。默认值为0.75。
                              int threshold;  //哈希表内元素数量的阈值,当元素数量超过阈值时,
                              
                              会触发扩容。threshold = 哈希桶.length * loadFactor

                              threshold是HashMap在此Load factor和length(数组长度)对应下允许的最大元素数目,超过这个数目就重新resize(扩容),扩容后的HashMap容量是之前容量的两倍。

                              下面是哈希表数据类的定义,它的本质是一个单向链表:

                                static class Node<K,V> implements Map.Entry<K,V> {
                                    final int hash;    //用来定位数组索引位置
                                    final K key;
                                    V value;
                                    Node<K,V> next;   //链表的下一个node
                                }

                                HashMap确定某个key所在哈希桶的位置方法如下:

                                  //计算key的hash值
                                  static final int hash(Object key) {
                                       int h;
                                       return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
                                  }
                                  //根据hash值确定哈希桶位置
                                  static int indexFor(int h, int length) {
                                       return h & (length-1);  
                                  }

                                  这里的Hash算法本质上就是三步:

                                  (1) 取key的hashCode值:h = key.hashCode()

                                  (2) 高位参与运算:h ^ (h >>> 16)

                                  (3) 取模运算获得数组下标:h & (length-1)

                                  首先我们先看HashMap如何根据一个hash值定位数组下标,它通过h & (table.length -1)来得到该对象的保存位,而HashMap底层数组的长度总是2的n次方,这是HashMap在速度上的优化。当length总是2的n次方时,h& (length-1)运算等价于对length取模,也就是h%length,但是&比%具有更高的效率

                                  而为什么不直接让key.hashCode()作为hash值呢?因为计算h & (table.length -1)时,由于length为2的n次方,因此table.length -1高位全部为0,以16为例,这样计算出来的index完全只保留hash值的低4位,所有hash的高位部分都丢失了。因此通过计算(h = key.hashCode()) ^ (h >>> 16),将自己的高半区和低半区做异或,就是为了混合原始哈希码的高位和低位,以此来加大低位的随机性,从而尽可能的减少碰撞。这也被称为扰动函数。

                                  更多关于hash函数的解释,请参考:https://www.zhihu.com/question/20733617

                                  关于put函数的流程图如下:


                                  put(K key, V value)函数流程图

                                  扩容机制:

                                  HashMap对象内部的数组无法装载更多的元素时,对象就需要扩大数组的长度,以便能装入更多的元素。当然Java里的数组是无法自动扩容的,方法是使用一个新的数组代替已有的容量小的数组,然后对所有的元素进行rehash。

                                  一个最简单的rehash思路是遍历所有的元素,一个一个重新计算hash值和数组下标,然后进行分配。然而Java8对rehash进行了优化。请看下面一个例子:Hash桶初始大小为4,现在有两个hash值为1和5的元素,它们都被分配到了table[1]中。计算下标的过程如下:

                                  1:(0000 0001)&(0000 0011)= 1  
                                  5:(0000 0101)&(0000 0011)= 1

                                  现在HashMap进行resize,那么哈希桶的大小变成了8,那么它们计算下标的过程如下:

                                  1:(0000 0001)&(0000 0111)= 1  
                                  5:(0000 0101)&(0000 0111)= 5

                                  我们可以看到,事实上由于HashMap进行resize,是原有size * 2,因此元素下标位置只可能产生两种变化,即元素的位置要么是在原位置,要么是在原位置再移动2次幂的位置(即加上原有桶大小)。

                                  因此,可以产生如下伪代码:

                                    if (hash & oldCapacity == 0) {
                                        //下标不变
                                    } else {
                                       //下标 = 原有下标 + oldCapacity
                                    }
                                    

                                    更多关于resize的内容,请参考:https://blog.csdn.net/login_sonata/article/details/76598675

                                    2.4 LinkedHashMap

                                    LinkedHashMap是HashMap的子类,它在HashMap的基础上维护了一个双向队列,因此能够按照顺序遍历Map。它的内部数据结构如下:

                                      public class LinkedHashMap<K, V> extends HashMap<K, V> {
                                          //链表头节点
                                          transient LinkedHashMap.Entry<K,V> head;
                                          //链表尾节点
                                          transient LinkedHashMap.Entry<K,V> tail;
                                          //遍历顺序,false表示按照插入顺序,true表示按照最近最少使用顺序
                                          final boolean accessOrder;
                                      }

                                      下面是LinkedHashMap节点的数据结构:

                                        static class Entry<K,V> extends HashMap.Node<K,V> {
                                            Entry<K,V> before, after;  //前继节点和后继节点
                                            Entry(int hash, K key, V value, Node<K,V> next) {
                                                super(hash, key, value, next);
                                            }
                                        }

                                        可以看到LinkedHashMap节点本质上是一个双向链表。

                                        LinkedHashMap在HashMap的基础上重写了其钩子方法,其中主要的钩子方法如下:

                                          // Callbacks to allow LinkedHashMap post-actions
                                          void afterNodeAccess(Node<K,V> p) { }
                                          void afterNodeInsertion(boolean evict) { }
                                          void afterNodeRemoval(Node<K,V> p) { }
                                          public V get(Object key) {
                                              Node<K,V> e;
                                              if ((e = getNode(hash(key), key)) == null)
                                                return null;
                                              //在取值后对参数accessOrder进行判断,如果为true,执行afterNodeAccess
                                              if (accessOrder)
                                                afterNodeAccess(e);
                                              return e.value;
                                          }

                                          afterNodeAccess(Node<K,V> p)方法用于将当前节点移动到双向队列尾部,因此accessOrder = true时,通过afterNodeAccess(Node<K,V> p)方法,就实现了LRU cache。

                                          afterNodeRemoval(Node<K,V> e)则是由remove方法调用的,它主要用于更新LinkedHashMap内部维护的双向队列。

                                          afterNodeInsertion(boolean evict)方法则是由put方法调用的,它的实现如下:

                                            void afterNodeInsertion(boolean evict) {
                                                LinkedHashMap.Entry<K,V> first;
                                                if (evict && (first = head) != null && removeEldestEntry(first)) {
                                                   K key = first.key;
                                                  removeNode(hash(key), key, null, false, true);
                                                }
                                            }
                                            protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
                                                return false;
                                            }

                                            我们可以看到,afterNodeInsertion(boolean evict)方法用于删除队首元素。默认情况下removeEldestEntry(Map.Entry<K,V> eldest)方法返回false,因此afterNodeInsertion(boolean evict)方法不会删除任何元素。

                                            我们可以通过重写removeEldestEntry(Map.Entry<K,V> eldest)方法来实现一个LRU Cache:

                                            class LRUCache extends LinkedHashMap<Integer, Integer> {
                                                private int capacity;
                                                public LRUCache(int capacity) {
                                                    super(capacity, 1, true);
                                                    this.capacity = capacity;
                                                }
                                                public int get(int key) {
                                                    if (containsKey(key)) {
                                                        return get(key);
                                                    }
                                                    return -1;
                                                }
                                                public void put(int key, int value) {
                                                    put(key, value);
                                                }
                                                @Override
                                                protected boolean removeEldestEntry(Map.Entry eldest) {
                                                    return size() > capacity;
                                                }
                                            }


                                            这样,每当这个数据量超出这个LRU Cache的capacity时,LinkedHashMap会删除队首元素。由于super(capacity, 1, true)将accessOrder设置为了true,因此队首元素就是最近最少用元素,从而实现了一个简易的LRU Cache。


                                            目录
                                            相关文章
                                            |
                                            1月前
                                            |
                                            监控 Java 应用服务中间件
                                            高级java面试---spring.factories文件的解析源码API机制
                                            【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
                                            77 2
                                            |
                                            28天前
                                            |
                                            Java 程序员
                                            Java社招面试题:& 和 && 的区别,HR的套路险些让我翻车!
                                            小米,29岁程序员,分享了一次面试经历,详细解析了Java中&和&&的区别及应用场景,展示了扎实的基础知识和良好的应变能力,最终成功获得Offer。
                                            67 14
                                            |
                                            1月前
                                            |
                                            存储 缓存 算法
                                            面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
                                            本文介绍了多线程环境下的几个关键概念,包括时间片、超线程、上下文切换及其影响因素,以及线程调度的两种方式——抢占式调度和协同式调度。文章还讨论了减少上下文切换次数以提高多线程程序效率的方法,如无锁并发编程、使用CAS算法等,并提出了合理的线程数量配置策略,以平衡CPU利用率和线程切换开销。
                                            面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
                                            |
                                            1月前
                                            |
                                            存储 算法 Java
                                            大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
                                            本文详解自旋锁的概念、优缺点、使用场景及Java实现。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
                                            大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
                                            |
                                            1月前
                                            |
                                            存储 缓存 Oracle
                                            Java I/O流面试之道
                                            NIO的出现在于提高IO的速度,它相比传统的输入/输出流速度更快。NIO通过管道Channel和缓冲器Buffer来处理数据,可以把管道当成一个矿藏,缓冲器就是矿藏里的卡车。程序通过管道里的缓冲器进行数据交互,而不直接处理数据。程序要么从缓冲器获取数据,要么输入数据到缓冲器。
                                            Java I/O流面试之道
                                            |
                                            1月前
                                            |
                                            Java 编译器 程序员
                                            Java面试高频题:用最优解法算出2乘以8!
                                            本文探讨了面试中一个看似简单的数学问题——如何高效计算2×8。从直接使用乘法、位运算优化、编译器优化、加法实现到大整数场景下的处理,全面解析了不同方法的原理和适用场景,帮助读者深入理解计算效率优化的重要性。
                                            36 6
                                            |
                                            1月前
                                            |
                                            存储 缓存 Java
                                            大厂面试必看!Java基本数据类型和包装类的那些坑
                                            本文介绍了Java中的基本数据类型和包装类,包括整数类型、浮点数类型、字符类型和布尔类型。详细讲解了每种类型的特性和应用场景,并探讨了包装类的引入原因、装箱与拆箱机制以及缓存机制。最后总结了面试中常见的相关考点,帮助读者更好地理解和应对面试中的问题。
                                            71 4
                                            |
                                            1月前
                                            |
                                            存储 Java 程序员
                                            Java基础的灵魂——Object类方法详解(社招面试不踩坑)
                                            本文介绍了Java中`Object`类的几个重要方法,包括`toString`、`equals`、`hashCode`、`finalize`、`clone`、`getClass`、`notify`和`wait`。这些方法是面试中的常考点,掌握它们有助于理解Java对象的行为和实现多线程编程。作者通过具体示例和应用场景,详细解析了每个方法的作用和重写技巧,帮助读者更好地应对面试和技术开发。
                                            135 4
                                            |
                                            2月前
                                            |
                                            存储 安全 算法
                                            Java面试题之Java集合面试题 50道(带答案)
                                            这篇文章提供了50道Java集合框架的面试题及其答案,涵盖了集合的基础知识、底层数据结构、不同集合类的特点和用法,以及一些高级主题如并发集合的使用。
                                            125 1
                                            Java面试题之Java集合面试题 50道(带答案)
                                            |
                                            2月前
                                            |
                                            存储 Java 程序员
                                            Java面试加分点!一文读懂HashMap底层实现与扩容机制
                                            本文详细解析了Java中经典的HashMap数据结构,包括其底层实现、扩容机制、put和查找过程、哈希函数以及JDK 1.7与1.8的差异。通过数组、链表和红黑树的组合,HashMap实现了高效的键值对存储与检索。文章还介绍了HashMap在不同版本中的优化,帮助读者更好地理解和应用这一重要工具。
                                            73 5