Java架构-三天拿到阿里、头条跟美团的offer,我做了这些准备

1 回顾
透露一下,本人是双非二本,自从高考失利以后还以为自己要一直这么平凡下去,没想到过了三年终于又给我一个机会让我重新证明了自己,能给我去阿里、头条跟美团的面试机会,最后选择去到阿里这样的大厂工作,真的倍感荣幸,如果喜欢我的文章别忘了给我点个赞转发跟评论,拜托了
2 把自己训练成HashMap和复读机
这几次面试给我最大的感触就是,当你觉得自己像复读机能把面试题给复读出来并且对面试官所提的问题能像HashMap一样在常数时间内找到答案的时候,你就离成功很近了.
下面是我在准备面试的时候收集的一些知识点:
2.1 Java
2.1.1 volatile理解,JMM中主存和工作内存到底是啥?和JVM各个部分怎么个对应关系?
参考link
2.1.2 序列化
Serializable在序列化时使用了反射,从而导致GC的频繁调用,参考link
2.1.3 可见性,原子性,有序性(必考)

  • 可见性volatile,一个线程的修改对另外一个线程是马上可见的,
  • 原子性CAS操作,要么都做要么都不做
  • 有序性synchronized通过进入和退出Monitor(观察器)实现,CPU可能会乱序执行指令,如果在本线程内观察,所有操作都是有序的,如果在一个线程中观察另一个线程,所有操作都是无序的.参考link

2.1.4 Java锁机制
java锁机制其实是锁总线,同步关键字和Lock接口的优劣.
2.1.5 Java的常量池?不同String赋值方法,引用是否相等?
字面值是常量,在字节码中使用id索引,equals相等引用不一定相等,Android上String的构造函数会被虚拟机拦截,重定向到StringFactory
2.1.6 HashMap的实现?树化阈值?负载因子?
数组加链表加红黑树,默认负载因子0.75,树化阈值8,这部分比较常考,建议专门准备.(打个小广告OWO,你也可以关注我的专栏,里面有一篇文章分析了HashMap和ArrayMap)
2.1.7 Java实现无锁同步
CAS的实现如AtomicInteger等等
2.1.8 单例模式

  • 双重检查

public class Singleton {

    private static volatile Singleton singleton;

    private Singleton() {}

    public static Singleton getInstance() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

复制代码

  • 反序列化安全,反射安全 枚举级单例,类加载时由JVM保证单例,反序列化不会生成新对象,另外一种反射安全是在构造函数中对单例进行检查如果存在则抛出异常.

2.1.9 锁的条件变量
信号量要与一个锁结合使用,当前线程要先获得这个锁,然后等待与这个锁相关联的信号量,此时该锁会被解锁,其他线程可以抢到这个锁,如果其他线程抢到了这个锁,那他可以通知这个信号量,然后释放该锁,如果此时第一个线程抢到了该锁,那么它将从等待处继续执行(应用场景,将异步回调操作封装为变为同步操作,避免回调地狱)
信号量与锁相比的应用场景不同,锁是服务于共享资源的,而信号量是服务于多个线程间的执行的逻辑顺序的,锁的效率更高一些.
2.1.10 ThreadLocal原理
线程上保存着ThreadLocalMap,每个ThreadLocal使用弱引用包装作为Key存入这个Map里,当线程被回收或者没有其他地方引用ThreadLocal时,ThreadLocal也会被回收进而回收其保存的值
2.1.11 软引用,弱引用,虚引用

  • 软引用内存不够的时候会释放
  • 弱引用GC时释放
  • 虚引用,需要和一个引用队列联系在一起使用,引用了跟没引用一样,主要是用来跟GC做一些交互.

2.1.12 ClassLoader双亲委派机制
简单来说就是先把加载请求转发到父加载器,父加载器失败了,再自己试着加载
2.1.13 GC Roots有这些
通过System Class Loader或者Boot Class Loader加载的class对象,通过自定义类加载器加载的class不一定是GC Root:

  • 处于激活状态的线程
  • 栈中的对象
  • JNI栈中的对象
  • JNI中的全局对象
  • 正在被用于同步的各种锁对象
  • JVM自身持有的对象,比如系统类加载器等。

2.1.14 GC算法

2.2 ACM
对于ACM,比较常考链表的题,不常刷算法的同学一定不要对其有抵触心理.
你可能会问为什么要ACM?网上答案说的什么提高代码质量,能够更好地阅读别人的代码这些理由有一定道理,但对于我们去面试的人而言最重要的是ACM是面试官考察你编码能力的最直接的手段,所以不用说这么多废话刷题就够了.
刷题的话,建议去刷leetcode,题号在200以内的,简单和中等难度,不建议刷困难,因为面试的时候基本就不会出,没人愿意在那里等你想一个半个小时的.
在面试官面前现场白板编程时,可以先把思路告诉面试官,写不写得出来是另外一回事,时间复杂度和空间复杂度是怎么来的一定要搞清楚.在编码时也不一定要写出最佳的时间和空间的算法,但推荐你写出代码量最少,思路最清晰的,这样面试官看得舒服,你讲得也舒服.
下面是我在网上收集或者是在实际中遇到过的ACM题,基本上在leetcode上也都有类似的.
2.2.1 数组、链表

  • 链表逆序(头条几乎是必考的)
    public ListNode reverseList(ListNode head)
    {
        if (head == null)
        {
            return null;
        }
        if (head.next == null)
        {
            return head;
        }
        ListNode prev = null;
        ListNode current = head;
        while (current != null)
        {
            ListNode next = current.next;
            current.next = prev;
            prev = current;
            current = next;
        }
        return prev;
    }


复制代码

删除排序数组中的重复项

    public int removeDuplicates(int[] nums)
    {
        int length = nums.length;
        if (length == 0 || length == 1)
        {
            return length;
        }
        int size = 1;
        int pre = nums[0];
        for (int i = 1; i < length; )
        {
            if (nums[i] == pre)
            {
                i++;
            } else
            {
                pre = nums[size++] = nums[i++];
            }
        }
        return size;
    }
复制代码
  • 数组中找到重复元素
  • n个长为n的有序数组,求最大的n个数
  • 用O(1)的时间复杂度删除单链表中的某个节点 把后一个元素赋值给待删除节点,这样也就相当于是删除了当前元素,只有删除最后一个元素的时间为o(N)平均时间复杂度仍然为O(1)
      public void deleteNode(ListNode node) {
          ListNode next = node.next;
          node.val = next.val;
          node.next = next.next;
      }
复制代码

删除单链表的倒数第N个元素 两个指针,第一个先走N步第二个再走,时间复杂度为O(N),参考link

      public ListNode removeNthFromEnd(ListNode head, int n) {
          if (head == null)
          {
              return null;
          }
          if (head.next == null)
          {
              return n == 1 ? null : head;
          }
          int size = 0;
          ListNode point = head;
          ListNode node = head;
          do
          {
              if (size >= n + 1)
              {
                  point = point.next;
              }
              node = node.next;
              size++;
          } while (node != null);
          if (size == n)
          {
              return head.next;
          }
          node = point.next;
          point.next = node == null ? null : node.next;
          return head;
      }复制代码
  • 从长序列中找出前K大的数字
  • 用数组实现双头栈
  public static class Stack<T>
      {
          public Stack(int cap)
          {
              if (cap <= 0)
              {
                  throw new IllegalArgumentException();
              }
              array = new Object[cap];
              left = 0;
              right = cap - 1;
          }
          private Object[] array;
          private int left;
          private int right;
          public void push1(T val)
          {
              int index = left + 1;
              if (index < right)
              {
                  array[index] = val;
              }
              left = index;
          }
          @SuppressWarnings("unchecked")
          public T pop1()
          {
              if (left > 0)
              {
                  return (T)array[left--];
              }
              return null;
          }
          public void push2(T val)
          {
              int index = right - 1;
              if (index > left)
              {
                  array[index] = val;
              }
              right = index;
          }
          @SuppressWarnings("unchecked")
          public T pop2()
          {
              if (right < array.length)
              {
                 return (T)array[right++];
              }
              return null;
          }
      }复制代码

两个链表求和,返回结果也用链表表示 1 -> 2 -> 3 + 2 -> 3 -> 4 = 3 -> 5 -> 7

public ListNode addTwoNumbers(ListNode node1, ListNode node2)
      {
          ListNode head = null;
          ListNode tail = null;
          boolean upAdd = false;
          while (!(node1 == null && node2 == null))
          {
              ListNode midResult = null;
              if (node1 != null)
              {
                  midResult = node1;
                  node1 = node1.next;
              }
              if (node2 != null)
              {
                  if (midResult == null)
                  {
                      midResult = node2;
                  } else
                  {
                      midResult.val += node2.val;
                  }
                  node2 = node2.next;
              }
              if (upAdd)
              {
                  midResult.val += 1;
              }
              if (midResult.val >= 10)
              {
                  upAdd = true;
                  midResult.val %= 10;
              }
              else
              {
                  upAdd = false;
              }
              if (head == null)
              {
                  head = midResult;
                  tail = midResult;
              } else
              {
                  tail.next = midResult;
                  tail = midResult;
              }
          }
          if (upAdd)
          {
              tail.next = new ListNode(1);
          }
          return head;
      }

复制代码

交换链表两两节点

      public ListNode swapPairs(ListNode head)
      {
          if (head == null)
          {
              return null;
          }
          if (head.next == null)
          {
              return head;
          }
          ListNode current = head;
          ListNode after = current.next;
          ListNode nextCurrent;
          head = after;
          do
          {
              nextCurrent = after.next;
              after.next = current;
              if (nextCurrent == null)
              {
                  current.next = null;
                  break;
              }
              current.next = nextCurrent.next;
              after = nextCurrent.next;
              if (after == null)
              {
                  current.next = nextCurrent;
                  break;
              }
              current = nextCurrent;
          } while (true);
          return head;
      }
复制代码

找出数组中和为给定值的两个元素,如:[1, 2, 3, 4, 5]中找出和为6的两个元素。

      public int[] twoSum(int[]mun,int target)
      {
          Map<Integer, Integer> table = new HashMap<>();
          for (int i = 0; i < mun.length; ++i)
          {
              Integer value = table.get(target - mun[i]);
              if (value != null)
              {
                  return new int[]{i, value};
              }
              table.put(mun[i], i);
          }
          return null;
      }
复制代码

2.2.2 树

  • 二叉树某一层有多少个节点

2.2.3 排序

  • 双向链表排序(这个就比较过分了,遇到了就自求多福吧)
  public static void quickSort(Node head, Node tail) {
        if (head == null || tail == null || head == tail || head.next == tail) {
            return;
        }
        if (head != tail) {
            Node mid = getMid(head, tail);
            quickSort(head, mid);
            quickSort(mid.next, tail);
        }
    }
    public static Node getMid(Node start, Node end) {
        int base = start.value;
        while (start != end) {
            while(start != end && base <= end.value) {
                end = end.pre;
            }
            start.value = end.value;
            while(start != end && base >= start.value) {
                start = start.next;
            }
            end.value = start.value;
        }
        start.value = base;
        return start;
    }   // 使用如内部实现使用双向链表的LinkedList容器实现的快排   
    public static void quickSort(List<Integer> list) {
        if (list == null || list.isEmpty()) {
            return;
        }
        quickSort(list, 0, list.size() - 1);
    }
    private static void quickSort(List<Integer> list, int i, int j) {
        if (i < j) {
            int mid = partition(list, i, j);
            partition(list, i, mid);
            partition(list,mid + 1, j);
        }
    }
    private static int partition(List<Integer> list, int i, int j) {
        int baseVal = list.get(i);
        while (i < j) {
            while (i < j && baseVal <= list.get(j)) {
                j--;
            }
            list.set(i, list.get(j));
            while (i < j && baseVal >= list.get(i)) {
                i++;
            }
            list.set(j, list.get(i));
        }
        list.set(i, baseVal);
        return i;
    }复制代码


  • 常见排序,如堆排序,快速,归并,冒泡等,还得记住他们的时间复杂度.

2.3项目
2.3.1 视频聊天使用什么协议?
不要答TCP,答RTMP实时传输协议,RTMP在Github也有很多开源实现,建议面这方面的同学可以去了解一下.
2.3.2 你在项目中遇到的一些问题,如何解决,思路是什么?
这一块比较抽象,根据你自己的项目来,着重讲你比较熟悉,有把握的模块,一般面试官都会从中抽取一些问题来向你提问.
3 最后说一些个人认为比较重要的事
3.1 积极准备、不断试错

  • 机会都是留给有准备的人的,千万不要想着不准备上战场就能成功.
  • 多看面经,面经就是面试官们的招聘导向,透过阅读大量的面经,你能够感受得到面试官想要找到什么样的人,并且你可以有针对性地去准备.
  • 不断地去面试,如果你这次面试失败了,那一定要好好总结其中的原因,一定要想方设法地从面试官口中套出自己的不足,这样你下次面试成功的概率就会增加.


免费Java高级资料需要自己领取,涵盖了Java、Redis、MongoDB、MySQL、Zookeeper、Spring Cloud、Dubbo高并发分布式等教程!!!

​传送门:baminute.com:8082/activity.ht…


转载于:https://juejin.im/post/5d2ddfeae51d4510b71da69a

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值