1. Java单例模式
方式一:饿汉式实现方式
/**
* 饿汉的单例模式 效率低,不支持延迟加载
* 一上来就把单例对象创建出来了,要用的时候直接返回即可,这种可以说是单例模式中最简单的一种实现方式。但是问题也比较明显。单例在还没有使用到的时候,初始化就已经完成了。也就是说,如果程序从头到位都没用使用这个单例的话,单例的对象还是会创建。这就造成了不必要的资源浪费。所以不推荐这种实现方式。
*/
public class Singleton1 {
//类中的静态成员会随着类的加载而加载
private static Singleton1 instance = new Singleton1();
private Singleton1(){
}
public static Singleton1 getInstance(){
return instance;
}
}
方式二:懒汉式
/**
* Double CheckLock实现单例:DCL也就是双重锁判断机制(由于JVM底层模型原因,偶尔会出问题,不建议使用):
* 因为jvm有时候会进行编译优化,所以instance = new Singleton2();这段代码有时候会出现问题,可能还没实例话超过,就得到instance对象
* 这看上去一切都很完美,无懈可击,但实际上这个 getInstance() 方法并不完美。问题出在哪里呢?出在 new 操作上,我们以为的 new 操作应该是:
* 分配一块内存 M;
* 在内存 M 上初始化 Singleton 对象;
* 然后 M 的地址赋值给 instance 变量。
*
* 但是实际上优化后的执行路径却是这样的:
*
* 分配一块内存 M;
* 将 M 的地址赋值给 instance 变量;
* 最后在内存 M 上初始化 Singleton 对象
*/
public class Singleton2 {
private volatile static Singleton2 instance;
private Singleton2() {
}
public static Singleton2 newInstance() {
if (instance == null) {
synchronized (Singleton2.class) {
if (instance == null) {
instance = new Singleton2();
}
}
}
return instance;
}
}
方式三:内部类方式
/** * 通过静态内部类来实现单例模式 * 创建静态内部类的时候是不需要讲静态内部类的实例对象绑定到外部类的实例对象上。 * 静态内部类属于外部类,而不是属于外部类的对象。 * 只能访问外部类的静态成员变量或者静态方法。 * 生成静态内部类对象的方式:Outer.Inner inner = new Outer.Inner()。 */ public class Singleton3 { //要记得私有化构造函数 private Singleton3(){ }
//静态内部类和非静态内部类一样,都不会因为外部内的加载而加载,同时静态内部类的加载不需要依附外部类,在使用时才加载,不过在加载静态内部类的过程中也会加载外部类 private static class SingletonInstance{ private static Singleton3 instance = new Singleton3(); } public static Singleton3 getInstance() { return SingletonInstance.instance; } }
2. 计算n的阶乘的末尾有多少个0 ,假设n=100
public class TestFactorial {
public static BigInteger getFactorial(int n) {
//为result赋初始值,为1
BigInteger result = new BigInteger("1");
for (int i=1;i<=n;i++) {
BigInteger num = new BigInteger(String.valueOf(i));
result = result.multiply(num);
}
return result;
}
public static int getZeroNums(BigInteger result) {
String str = String.valueOf(result);
int size = str.length();
int zeroNums = 0;
for (int i=size-1;i>=0;i--) {
char c = str.charAt(i);
if (c != '0') {
break;
} else {
zeroNums++;
}
}
return zeroNums;
}
public static void main(String[] args) {
BigInteger result = getFactorial(100);
System.out.println("result = [" + result + "]");
int zeroNums = getZeroNums(result);
System.out.println("zeroNums = [" + zeroNums + "]");
}
}
BigInteger
是不可变的任意精度的整数。所有操作中,都以二进制补码形式表示 BigInteger
(如 Java 的基本整数类型).
BigInteger底层是以int数组的形式存放的,
下面看看BigInteger有哪些重点的属性,主要的有下面两个:
final int signum
signum属性是为了区分:正负数和0的标志位,整数用1表示,负数用-1表示,零用0表示。
final int[] mag
mag是magnitude的缩写形式,mag数组是存储BigInteger数值大小的,采用big-endian的顺序,也就是高位字节存入低地址,低位字节存入高地址,依次排列的方式。
Java中超出long的就用BigInteger.超过double就用BigDecimal.
面试的时候,脑子竟然都忘记了还有BigInteger,直接用的是long.别问住了。
3. 判断一个正整数是否为回文,比如1,11,121,1221等就是回文。
方式一:采用截取字符串的方式
/**
* 采用截取字符串的方式
*/
public class TestHuiWen {
public static boolean isHuiWen(int n) {
boolean flag = false;
String str = String.valueOf(n);
int size = str.length();
int mid = size /2 ;
if (size % 2 !=0) {
//如果是奇数个
String beforeHalfStr = str.substring(0,mid);
String afterHalfStr = str.substring(mid+1,size);
afterHalfStr = reverse(afterHalfStr);
if (beforeHalfStr.equals(afterHalfStr)){
flag = true;
}
} else {
//如果是偶数个
String beforeHalfStr = str.substring(0,mid);
String afterHalfStr = str.substring(mid,size);
afterHalfStr = reverse(afterHalfStr);
if (beforeHalfStr.equals(afterHalfStr)){
flag = true;
}
}
return flag;
}
public static String reverse(String str) {
return new StringBuilder(str).reverse().toString();
}
public static void main(String[] args) {
int n = 1221;
boolean flag = isHuiWen(n);
System.out.println("flag = [" + flag + "]");
}
}
方式二:采用字符串整体反转的方式
/**
* 通过字符串反转的方式,判断是否回文
*/
public class TestHuiWen2 {
public static boolean isHuiWen(int n) {
boolean flag = false;
String str = String.valueOf(n);
StringBuffer sb = new StringBuffer(str);
String reverseStr = sb.reverse().toString();
int count = 0;
for (int i =0;i<reverseStr.length();i++){
if (reverseStr.charAt(i) == str.charAt(i)) {
count++;
}
}
if (count == str.length()) {
System.out.println("此字符串是一个回文字符串");
flag = true;
}
return flag;
}
public static void main(String[] args) {
int n = 1221;
boolean flag = isHuiWen(n);
System.out.println("flag = [" + flag + "]");
}
}
4.给list排序,找到最大值,最小值。
public static void main(String[] args) {
List<Double> list = new ArrayList<>();
list.add(new Double(100.0));
list.add(new Double(200.1));
list.add(new Double(100.1));
list.add(new Double(300.1));
Collections.sort(list);//默认按照升序排序
System.out.println("list = [" + list + "]");
}
5. 斐波那契数列
/**
* 测试斐波那契数列
* 斐波拉契数为,Fib(N) = Fib(N-1)+Fib(N-2) F(0)=F(1)=1 用Java编写能求Fib(N)的程序 输入为N,须输出Fib(N)
*/
public class TestFabinacci {
/**
* 递归的写法
* @param n
* @return
*/
public static long getFabonacci(long n) {
long fabi = 0L;
if (n == 0 || n==1) {
return 1;
} else {
fabi = getFabonacci(n-1)+getFabonacci(n-2);
return fabi;
}
}
/**
* 此方法算是属于以空间换时间的做法了。
* 使用一个数组将所有的递归结果都记录,到时只需返回数组中的值即可。时间复杂度到了O(n)
* @param n
* @return
*/
public static long getFabonacci2(int n) {
if (n <= 1) {
return n; //考虑负数,和F(0)F(1)的情况
}
long[] mark = new long[n+1];//创建一个大小容纳一个斐波那契数列的数组
mark[0] = 1; //将前两位进行默认地初始化
mark[1] = 1;
for (int i = 2; i <= n; i++) {
mark[i] = mark[i - 1] + mark[i - 2];//使用递归
}
return mark[n];//传回数组末尾的值
}
/**
* for 循环的优化写法
* 采用for循环的形式,记录下之前两次的结果,能加快运算的速度:
* @param n
* @return
*/
public static long getFabonacci3(long n) {
long f1 = 1;
long f2 = 1;
long fabi = 0L;
if (n == 0 || n==1) {
return 1;
} else if (n>=2) {
for (int i=2;i<=n;i++) {
fabi = f1 + f2;
f1 = f2;
f2 = fabi;
}
}
return fabi;
}
public static void main(String[] args) {
long result1 = getFabonacci(10);
long result2 = getFabonacci2(10);
long result3 = getFabonacci3(10);
System.out.println("result1:"+result1 +" result2:"+result2+" result3:"+result3);
}
}
递归需要满足的三个条件 :
1)一个问题的解可以分解为几个子问题的解
何为子问题?子问题就是数据规模更小的问题。比如,去看电影院看电影,想知道“自己在哪一排”,可以拆解为“前一排的人在哪一排”这样的一个自问题。
2)原问题和子问题的求解思路一样,除了数据规模不一样,求解思路一样。
比如电影院那个例子,你求解“自己在哪一排”的思路,和前面一排求解“自己在哪一排”的思路”一模一样。
3)存在递归终止条件。
把问题分解为子问题,把子问题再分解为子子问题,一层一层分解下去,不能无限循环下去,所以要存在递归终止条件。
比如第一排的人已经知道自己在哪一排,不需要再继续拆解。比如:f(1)=1;
如何编写递归代码? 刚刚铺垫了这么多,现在我们来看,如何来写递归代码?个人觉得写递归代码最关键的就是,写出递归公式,找到终止条件,然后把递归公司转换为代码即可。
你先记住这个理论。我举一个例子,带你一步一步实现一个递归代码的实现过程。
假如这里有 n 个台阶,每次你可以跨 1 个台阶或者 2 个,如果有n个台阶,到底有多少种走法呢?
我们想一下,实际上我们走第一步可以分为2大类,一类第一次走一步,一类直接一下走2步,所以n个台阶的走法就是:先走一步后,剩下的f(n-1)个台阶的走法,加上 第一走走2步后,剩下的f(n-2)个台阶的走法只和。
递推公式为:
f(n)= f(n-1)+f(n-2).
终止条件为,f(1)=1,f(2)=2
所以最终的递归代码为:
/**
* 跳台阶问题求解。
* 总共有n个台阶,一次可以走一步或者走2步,求解总共有多少种走法。
*/
public class TestJump {
/**
* 递归代码写法技巧,写出递推公式,找到递推终止条件。
* 递推公式:f(n) = f(n-1)+f(n-2);
* 递归终止条件:f(1)=1,f(2)=2;
* @param n
* @return
*/
public static int jump(int n){
if ( n <= 0) {
return -1;
}
if (n == 1) {
return 1;
} else if (n == 2) {
return 1;
} else {
return jump(n-1)+jump(n-2);
}
}
public static void main(String[] args) {
int count = jump(10);
System.out.println("count = [" + count + "]");
}
}
写递归代码的关键就是找到把大问题拆分为小问题的思路,然后写出递推公式,推敲出来递归终止条件。然后把递推公式和递归终止条件翻译为代码。
/**
* 变态跳青蛙的变种
* 题目描述:
* 一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
* ###解题思路:
*
* 关于本题,前提是n个台阶会有一次n阶的跳法。分析如下:
* f(1) = 1
* f(2) = f(2-1) + f(2-2) //f(2-2) 表示2阶一次跳2阶的次数。
* f(3) = f(3-1) + f(3-2) + f(3-3)
* ...
* f(n) = f(n-1) + f(n-2) + f(n-3) + ... + f(n-(n-1)) + f(n-n)
* 说明:
* 1)这里的f(n) 代表的是n个台阶有一次1,2,...n阶的 跳法数。
* 2)n = 1时,只有1种跳法,f(1) = 1
* 3) n = 2时,会有两个跳得方式,一次1阶或者2阶,这回归到了问题(1) ,f(2) = f(2-1) + f(2-2)
* 4) n = 3时,会有三种跳得方式,1阶、2阶、3阶,
* 那么就是第一次跳出1阶后面剩下:f(3-1);第一次跳出2阶,剩下f(3-2);第一次3阶,那么剩下f(3-3)
* 因此结论是f(3) = f(3-1)+f(3-2)+f(3-3)
* 5) n = n时,会有n中跳的方式,1阶、2阶...n阶,得出结论:
* f(n) = f(n-1)+f(n-2)+...+f(n-(n-1)) + f(n-n) =>
* f(0) + f(1) + f(2) + f(3) + ... + f(n-1)
* 6) 由以上已经是一种结论,但是为了简单,我们可以继续简化:
* f(n-1) = f(0) + f(1)+f(2)+f(3) + ... + f((n-1)-1) = f(0) + f(1) + f(2) + f(3) + ... + f(n-2)
* f(n) = f(0) + f(1) + f(2) + f(3) + ... + f(n-2) + f(n-1) = f(n-1) + f(n-1)
* 可以得出:
* f(n) = 2*f(n-1)
* 7) 得出最终结论,在n阶台阶,一次有1、2、...n阶的跳的方式时,总得跳法为:
* | 1 ,(n=0 )
* f(n) = | 1 ,(n=1 )
* | 2*f(n-1),(n>=2)
* ---------------------
*
*/
public class TestJumpFrog {
public static int jumpFrog(int target){
if (target <= 0)
return -1;
if (target == 1)
return 1;
else{
return 2*jumpFrog(target - 1);
}
}
public static void main(String[] args) {
int count = jumpFrog(10);
System.out.println("count = [" + count + "]");
}
}
6.Java实现链表
链表分为单链表,循环链表,双向链表,双向循环链表。
单链表反转 链表中环的检测 两个有序的链表合并 ,删除链表倒数第 n 个结点,求链表的中间结点
/**
* java实现单链表
*/
public class TestSingleLink {
//头节点,头节点的数据与为空
private Node head = new Node(null);
private class Node{
//数据域
private Integer data;
//指针域
private Node next;
public Node(){
}
public Node(Integer data){
this.data = data;
}
public Node(Integer data,Node next){
this.data = data;
this.next = next;
}
}
public int getLinkSize() {
int size = 0;
//临时节点
Node temp = head;
while (temp != null) {
temp = temp.next;
size++;
}
return size;
}
/**
* 在链表的尾部添加元素
* @param value 要添加的节点的值
*/
public void addNode(int value){
Node newNode = new Node(value);
//临时节点
Node temp = head;
while (temp.next != null) {
temp = temp.next;
}
temp.next = newNode;
}
/**
* 在链表的指定位置插入value
* @param value 要添加的节点的值
*/
public void insertNode(int index,int value){
int size = getLinkSize();
if (index < 1 || index > size) {
System.out.println("不合法的位置参数");
return;
}
Node newNode = new Node(value);
//临时节点
Node temp = head;
//头节点不算正式节点,所以从-1开始
int i = -1;
while (temp.next != null) {
if (i == (index-1)) {
//找到了要插入位置的前一个节点
Node oldLink = temp.next;
temp.next = newNode;
newNode.next = oldLink;
break;
}
temp = temp.next;
i++;
}
temp.next = newNode;
}
/**
* 删除指定下标上的元素。下标从0开始算
* @param index
*/
public void deleteNode(int index) {
int size = getLinkSize();
if (index < 1 || index > size) {
System.out.println("不合法的位置参数");
return;
}
//临时节点
Node temp = head;
int i = -1;
while(temp != null) {
if (i == (index-1)) {
//先保存下将要删除的节点
Node deleteNode = temp.next;
deleteNode.next = deleteNode.next;
//置为空,便于垃圾回收
deleteNode.data = null;
}
temp = temp.next;
i++;
}
}
/**
* 获取指定位置的元素
* @param index
*/
public Node get(int index){
int size = getLinkSize();
if (index < 1 || index > size) {
System.out.println("不合法的位置参数");
return null;
}
//临时节点
Node temp = head;
//头节点不算正式节点,所以从-1开始
int i = -1;
while(temp.next != null) {
if (i == (index-1)) {
Node node = temp.next;
return node;
}
temp = temp.next;
i++;
}
return null;
}
/**
* 找到链表中倒数第k个元素
* 设置2个指针,p1,p2,p2每次比p1快k个节点,当p2走到链表尾部,p1正好就是倒数第K个元素
* @param k
* @return
*/
public Node findLastKNode(int k) {
int size = getLinkSize();
if (k < 1 || k > size) {
System.out.println("不合法的位置参数");
return null;
}
//临时节点
Node p1 = head;
//临时节点
Node p2 = head;
for (int i=0;i<k && p2!=null;i++) {
p2 = p2.next;
}
while (p2 != null) {
p2 = p2.next;
p1 = p1.next;
}
return p1;
}
public void printLink(Node head){
//临时节点
Node temp = head;
while (temp != null) {
System.out.println("data:"+temp.data);
temp = temp.next;
}
}
/**
* 递归实现链表的反转
* 递归实质上就是系统帮你压栈的过程,系统在压栈的时候会保留现场。
*
* 我们来看是怎样的一个递归过程:1->2->3->4
*
* 程序到达Node newHead = reverse(head.next);时进入递归
* 我们假设此时递归到了3结点,此时head=3结点,temp=3结点.next(实际上是4结点)
* 执行Node newHead = reverse(head.next);传入的head.next是4结点,返回的newHead是4结点。
* 接下来就是弹栈过程了
*
* 程序继续执行 temp.next = head就相当于4->3
* head.next = null 即把 3结点指向4结点的指针断掉。
* 返回新链表的头结点newHead
*
*
*
*
* 注意:当return后,系统会恢复2结点压栈时的现场,此时的head=2结点;temp=2结点.next(3结点),再进行上述的操作。最后完成整个链表的翻转。
*
* @param head
* @return
*/
public static Node reverseLink(Node head){
if(head == null || head.next == null){
return head;
}
Node temp = head.next;
Node newHead = reverseLink(head.next);
temp.next = head;
head.next = null;
return newHead;
}
/**
* 依旧是1->2->3->4
*
* 准备两个空结点 pre用来保存先前结点、next用来做临时变量
* 在头结点node遍历的时候此时为1结点
*
* next = 1结点.next(2结点)
* 1结点.next=pre(null)
* pre = 1结点
* node = 2结点
*
*
* 进行下一次循环node=2结点
*
* next = 2结点.next(3结点)
* 2结点.next=pre(1结点)=>即完成2->1
* pre = 2结点
* node = 3结点
*
* 进行循环...
* @param node
* @return
*/
public static Node reverse(Node node){
Node pre = null;
Node next = null;
while(node != null) {
next = node.next;
node.next = pre;
pre = node;
node = next;
}
return pre;
}
public static void main(String[] args) {
TestSingleLink testSingleLink = new TestSingleLink();
testSingleLink.addNode(1);
testSingleLink.addNode(2);
testSingleLink.addNode(3);
testSingleLink.addNode(4);
testSingleLink.addNode(5);
testSingleLink.addNode(6);
testSingleLink.addNode(7);
//testSingleLink.insertNode(2,4);
/* testSingleLink.get(3);
//testSingleLink.deleteNode(3);
testSingleLink.printLink();
Node lastKNode = testSingleLink.findLastKNode(2);
System.out.println("lastKNode = [" + lastKNode.data + "]");*/
Node newHead = reverse(testSingleLink.head);
testSingleLink.printLink(newHead);
}
}
求链表的中间节点:
/**
* 求单链表的中间节点,如果有两个中间节点,取的是靠后的那个
* @param head
* @return
*/
public static Node getMid(Node head){
if (head == null || head.next == null) {
return null;
}
//快指针每次走2步,慢指针每次走1步,快指针走到头,慢指针正好在中间。如果是偶数个,中间节点会有2个,那么这种情况下返回的是中间靠后的那个
Node fast = head.next;
Node slow = head.next;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
}
return slow;
}
/**
* 求单链表的中间节点,如果有两个中间节点,取的是靠前的那个
* @param head
* @return
*/
public static Node getMid2(Node head){
if (head == null || head.next == null) {
return null;
}
//快指针每次走2步,慢指针每次走1步,快指针走到头,慢指针正好在中间。如果是偶数个,中间节点会有2个,那么这种情况下返回的是中间靠后的那个
Node fast = head.next.next;
Node slow = head.next;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
}
return slow;
}
java实现2个单链表合并:
/**
* 两个有序的链表合并,递归的实现
* @param link1
* @param link2
* @return
*/
public Node mergeTwoLink(Node link1,Node link2){
if (link1 == null ) {
return link2;
}
if (link2 == null) {
return link1;
}
Node head = null;
if (link1.data <= link2.data) {
head = link1;
head.next = mergeTwoLink(link1.next,link2);
} else {
head = link2;
head.next = mergeTwoLink(link1,link2.next);
}
return head;
}
/**
* 两个有序的链表合并,非递归的实现
* @param link1
* @param link2
* @return
*/
public Node mergeTwoLink2(Node link1,Node link2){
if (link1 == null ) {
return link2;
}
if (link2 == null) {
return link1;
}
//head用于存放新链表的头节点
Node newhead = null;
//temp用于存放新链表中最后添加进去的那个节点
Node temp = null;
while (link1 != null && link2 != null) {
if (link1.data <= link2.data) {
if (newhead == null) {
newhead = temp = link1;
} else {
temp.next = link1;
temp = temp.next;
}
link1 = link1.next;
} else {
if (newhead == null) {
newhead = temp = link2;
} else {
temp.next = link2;
temp = temp.next;
}
link2 = link2.next;
}
}
if (link1 == null) {
temp.next = link2;
}
if (link2 == null) {
temp.next = link1;
}
return newhead;
}
public Node mergeTwoLists3(Node l1, Node l2) {
// maintain an unchanging reference to node ahead of the return node.
Node prehead = new Node(-1);
Node prev = prehead;
while (l1 != null && l2 != null) {
if (l1.data <= l2.data) {
prev.next = l1;
l1 = l1.next;
} else {
prev.next = l2;
l2 = l2.next;
}
prev = prev.next;
}
// exactly one of l1 and l2 can be non-null at this point, so connect
// the non-null list to the end of the merged list.
prev.next = l1 == null ? l2 : l1;
return prehead.next;
}
java实现链表的中环检测:
public boolean hasCycle(ListNode head) {
if (head == null || head.next == null) {
return false;
}
ListNode slow = head;
ListNode fast = head.next;
while(fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (fast == null) {
return false;
} else if (fast == slow) {
return true;
}
}
return false;
}
6.mysql建立索引的时候,需要注意什么。比如如果我给一个url,而且url要精确匹配,能建立索引吗?如果能,怎么建?
7.规则引擎为什么不用第三方的,为什么要自己实现。规则引擎的并发量是多大
8.Thread.sleep(),sleep时间到了,会立刻进入到running状态吗?线程的状态。
9.报表抽取数据到elasticsearch,如何保证实时性的问题。
10.http状态码问题
11.jvm内存管理
12.如何处理多张表复杂的关联查询。
13.mysql数据库的隔离级别,以及脏读,不可重复读,幻读的区别。
14.如何设计工作流的表
15.hashmap为什么8的时候会转为红黑树,一直揪着这个问题问。hashmap中到底存放的是什么,回答node节点,又问node节点里面的数据结构。
16.面试一定要主题规则,和系统任务调度。还有报表。重点好好说一下。
面试过国企,整体感觉特别不好,先是做笔试题,然后面试来了一个和我差不多大的女的,一直揪着笔试题不放,看来国企性质的笔试题相当重要啊,问的问题都相当老套,一板一眼,面试问了我http状态码,说了不记得了,还一直问我,看来自己要多适应国企的面试风格。