0
点赞
收藏
分享

微信扫一扫

Java 面试题

我是芄兰 2022-03-20 阅读 95
java

一、搜索

1、什么是Solr

Solr是一个Java开发的基于Lucene的 企业级 开源 全文搜索 平台。 它采用的是反向索引,即从关键字到文档的映射过程。 Solr的资源以Document为对象进行存储,每个文档由一系列的 Field 构成,每个Field 表示资源的一个属性。 文档的Field可以被索引, 以提工高性能的搜索效率。 一般情况下文档都包含一个能唯一表示该文档的id字段。

2、什么是Lucene

Lucene是apache软件基金会发布的一个开放源代码的全文检索引擎工具包,Lucene是根据关健字来搜索的文本搜索工具,只能在某个网站内部搜索文本内容,不能跨网站搜索

3、Solr的倒排索引

倒排索引就是从文档内容到文档序号的过程,将文档内容用solr自带分词器进行分词,然后作为索引,用二分法将关键字与排序号的索引进行匹配,进而查找到对应文档。倒排索引相对于正排而言,正排是从key查询value的一个过程,而倒排索引则是根据value查询key的一个过程,solr首先将数据进行分析,然后创建索引,将创建好的索引存储起来,查询时利用二分法去查询value,这个value对应一个Key,然后将这个Key返回。

4、 Solr和elasticsearch的区别?

https://www.cnblogs.com/jajian/p/9801154.html

共同点:solr和elasticsearch都是基于Lucene实现的!

不同点:

A. solr利用zookeeper进行分布式管理,而elasticsearch自身带有分布式协调管理功能;

B. solr比elasticsearch实现更加全面,solr官方提供的功能更多,而elasticsearch本身更注 重于核心功能,高级功能多由第三方插件提供;

C. solr在传统的搜索应用中表现好于elasticsearch,而elasticsearch在实时搜索应用方面比solr表现好!

二、Java基础知识

1、native关键字:

https://www.cnblogs.com/qian123/p/5702574.html

2、Java异常:

https://www.cnblogs.com/Qian123/p/5715402.html

3、static,final,this,super关键字总结:

https://gitee.com/SnailClimb/JavaGuide/blob/master/docs/java/basic/final,static,this,super.md

4、集合总结

https://www.cnblogs.com/skywang12345/p/3323085.html

1、fail-fast机制

ArrayList在用Iterator遍历数据时会产生fail-fast,导致异常,这是因为ArrayList遍历时会检查当前List的预期值和实际值是否相同,如果相同,则遍历,如果不同则产生异常

解决方案:

使用CopyOnWriteArrayList,他没有继承AbstractList,而是重写了iterator方法,遍历时新建一个数组,然后保存当前数据,由于是新数组,所以不会产生不一样的情况,自然不会有异常

2、HashMap详解:

https://zhuanlan.zhihu.com/p/21673805

3、我对于集合的理解

(1)、集合工具类Arrays

https://blog.csdn.net/samjustin1/article/details/52661862

//asList方法可以将数据转化为List
        List<String> list = Arrays.asList("壮壮", "小马","小六");
        for (String s : list) {
            System.out.println(s);
        }

        //binarySearch可以在数组中查找是否存在某个元素,存在则返回该元素的下标
        int[] arr = new int[]{1, 3, 4,56,4,5,1, 56, 67};
        int index = Arrays.binarySearch(arr, 3,6,4);
        System.out.println(index);

        //sort可以将数组中元素排序
        //toString可以将数组元素打印输出 
        String[] names = {"Eric", "John", "Alan", "Liz"};
        Arrays.sort(names);
        System.out.println(Arrays.toString(names));

        int[][] stuGrades = { { 80, 81, 82 }, { 84, 85, 86 }, { 87, 88, 89 } };
        System.out.println(Arrays.deepToString(stuGrades));

        //fill可以将数组中的元素填充为指定元素
        int[] arr2 = new int[8];
        Arrays.fill(arr, 78);
        System.out.println(Arrays.toString(arr));

(2)、集合工具类Collections

https://www.cnblogs.com/nayitian/p/3269585.html

(3)、ArrayList

继承关系:

java.lang.Object
   ↳     java.util.AbstractCollection<E>
         ↳     java.util.AbstractList<E>
               ↳     java.util.ArrayList<E>

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable {}

特点:底层是数组实现,查找快,增删慢,它是线程不安全的,如果需要线程安全,则可以使用Vector

定义:它继承于AbstractList,实现了List, RandomAccess, Cloneable

ArrayList包含了两个重要的对象:elementData 和 size。

(01) elementData 是"Object[]类型的数组",它保存了添加到ArrayList中的元素。实际上,elementData是个动态数组,我们能通过构造函数 ArrayList(int initialCapacity)来执行它的初始容量为initialCapacity;如果通过不含参数的构造函数ArrayList()来创建ArrayList,则elementData的容量默认是10。elementData数组的大小会根据ArrayList容量的增长而动态的增长,具体的增长方式,请参考源码分析中的ensureCapacity()函数。

(02) size 则是动态数组的实际大小。

遍历:

遍历ArrayList时,使用随机访问(即,通过索引序号访问)效率最高,而使用迭代器的效率最低!

//随机访问
Integer value = null;
int size = list.size();
for (int i=0; i<size; i++) {
    value = (Integer)list.get(i);        
}

(4)、LinkedList

继承关系:

java.lang.Object
   ↳     java.util.AbstractCollection<E>
         ↳     java.util.AbstractList<E>
               ↳     java.util.AbstractSequentialList<E>
                     ↳     java.util.LinkedList<E>

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable {}

特点:

底层是链表实现的,增删快,查找慢

简介:

LinkedList 是一个继承于AbstractSequentialList的双向链表。它也可以被当作堆栈、队列或双端队列进行操作。
LinkedList 实现 List 接口,能对它进行队列操作。
LinkedList 实现 Deque 接口,即能将LinkedList当作双端队列使用。
LinkedList 实现了Cloneable接口,即覆盖了函数clone(),能克隆。
LinkedList 实现java.io.Serializable接口,这意味着LinkedList支持序列化,能通过序列化去传输。
LinkedList 是非同步的。

定义:

LinkedList的本质是双向链表。
(01) LinkedList继承于AbstractSequentialList,并且实现了Dequeue接口。
(02) LinkedList包含两个重要的成员:header 和 size。
  header是双向链表的表头,它是双向链表节点所对应的类Entry的实例。Entry中包含成员变量: previous, next, element。其中,previous是该节点的上一个节点,next是该节点的下一个节点,element是该节点所包含的值。
  size是双向链表中节点的个数。

遍历:

遍历LinkedList时,使用removeFist()或removeLast()效率最高。但用它们遍历时,会删除原始数据;若单纯只读取,而不删除,应该使用第3种遍历方式。
无论如何,千万不要通过随机访问去遍历LinkedList!

//随机访问效率最慢
int size = list.size();
for (int i=0; i<size; i++) {
    list.get(i);        
}

(5)、vector

Vector简介

Vector 是矢量队列,它是JDK1.0版本添加的类。继承于AbstractList,实现了List, RandomAccess, Cloneable这些接口。
Vector 继承了AbstractList,实现了List;所以,它是一个队列,支持相关的添加、删除、修改、遍历等功能
Vector 实现了RandmoAccess接口,即提供了随机访问功能。RandmoAccess是java中用来被List实现,为List提供快速访问功能的。在Vector中,我们即可以通过元素的序号快速获取元素对象;这就是快速随机访问。
Vector 实现了Cloneable接口,即实现clone()函数。它能被克隆。

和ArrayList不同,Vector中的操作是线程安全的

特点:底层是数组实现,是线程安全的,Vector里面的方法加上了synchronized关键字

继承关系:

java.lang.Object
   ↳     java.util.AbstractCollection<E>
         ↳     java.util.AbstractList<E>
               ↳     java.util.Vector<E>

public class Vector<E>
    extends AbstractList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable {}

Vector的数据结构和ArrayList差不多,它包含了3个成员变量:elementData , elementCount, capacityIncrement。

(01) elementData 是"Object[]类型的数组",它保存了添加到Vector中的元素。elementData是个动态数组,如果初始化Vector时,没指定动态数组的>大小,则使用默认大小10。随着Vector中元素的增加,Vector的容量也会动态增长,capacityIncrement是与容量增长相关的增长系数,具体的增长方式,请参考源码分析中的ensureCapacity()函数。

(02) elementCount 是动态数组的实际大小。

(03) capacityIncrement 是动态数组的增长系数。如果在创建Vector时,指定了capacityIncrement的大小;则,每次当Vector中动态数组容量增加时>,增加的大小都是capacityIncrement。

遍历:

遍历Vector使用索引的方式随机访问最快,使用迭代器最慢

(6)HashMap

HashMap简介

HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。
HashMap 继承于AbstractMap,实现了Map、Cloneable、java.io.Serializable接口。
HashMap 的实现不是同步的,这意味着它不是线程安全的。它的key、value都可以为null。此外,HashMap中的映射不是有序的。

HashMap 的实例有两个参数影响其性能:“初始容量” 和 “加载因子”。容量 是哈希表中桶的数量,初始容量 只是哈希表在创建时的容量。加载因子 是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行 rehash 操作(即重建内部数据结构),从而哈希表将具有大约两倍的桶数。
通常,默认加载因子是 0.75, 这是在时间和空间成本上寻求一种折衷。加载因子过高虽然减少了空间开销,但同时也增加了查询成本(在大多数 HashMap 类的操作中,包括 get 和 put 操作,都反映了这一点)。在设置初始容量时应该考虑到映射中所需的条目数及其加载因子,以便最大限度地减少 rehash 操作次数。如果初始容量大于最大条目数除以加载因子,则不会发生 rehash 操作。

继承关系:

java.lang.Object
   ↳     java.util.AbstractMap<K, V>
         ↳     java.util.HashMap<K, V>

public class HashMap<K,V>
    extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable { }

特点:

HashMap是数组加链表的方式实现的,首先根据hash值计算出元素存储的位置,如果该位置上已经有元素,则判断key是否相同,如果相同则覆盖,如果不同则在该节点创建链表,当链表长度超过8时,将链表改为红黑树

(7)、HashTable

特点:线程安全

(8)、TreeMap

特点:有序的key-value集合,它是通过红黑树实现的

(9)、set

set和list集合相同,但是set集合中不允许有重复的元素

HashSet:无序集合

TreeSet:有序集合

5、多线程

(1)、创建多线程

//创建多线程有两种方法
//第一种 实现Runnable接口
class MyThread implements Runnable{
    @Override//重写run方法
    public void run() {
        for (int i = 0; i < 50; i++) {
            System.out.println(Thread.currentThread().getName()+i);
        }
    }
}

//第二种方法 继承Thread类
class MyThread extends Thread{
    //Thread的底层也是通过实现Runnable接口来实现多线程,并且Thread类的run方法调用的是接口的方法,因此继承Thread也需要实现run方法
    @Override//重写run方法
    public void run() {
        for (int i = 0; i < 50; i++) {
            System.out.println(Thread.currentThread().getName()+i);
        }
    }
}

(2)、Join方法

join()方法可以让一个线程强制运行

MyThread thread = new MyThread();
        Thread t = new Thread(thread);
        t.start();
        for (int i = 0; i < 50; i++) {
            if (i > 10) {
                try {
                    t.join();  //程序开始时,t线程和main线程交替运行,当i>10时,main停止运行,当t线程运行完成后,main线程才可以接着运行,Join()方法就是一个线程强制运行直至其死亡
                } catch (InterruptedException e) {
                }

            }
            System.out.println("Main线程开始运行"+i);
        }

(3)、线程的状态

1、创建状态

2、就绪状态

3、运行状态

4、阻塞状态

5、死亡状态

(4)、几个常用方法

1、join方法 强制线程运行

2、sleep方法 休眠线程

3、interrupt方法 中断线程

4、setDaemon方法 后台线程

5、setPriority方法

6、yield方法 线程礼让

(5)、start()和run()区别

//这里调用run方法并没有新建线程,而是直接用当前线程
thread.run();
//start方法则新建了一个线程来调用run
thread.start();

// Demo.java 的源码
class MyThread extends Thread{  
    public MyThread(String name) {
        super(name);
    }

    public void run(){
        System.out.println(Thread.currentThread().getName()+" is running");
    } 
}; 

public class Demo {  
    public static void main(String[] args) {  
        Thread mythread=new MyThread("mythread");

        System.out.println(Thread.currentThread().getName()+" call mythread.run()");
        mythread.run();

        System.out.println(Thread.currentThread().getName()+" call mythread.start()");
        mythread.start();
    }  
}

结果:
main call mythread.run()
main is running
main call mythread.start()
mythread is running

(6)、synchronized

一、原理:

二、sychronized使用规则

三、实例锁和全局锁(对象锁和类锁)

(7)、wait和notify

一、介绍

二、注意点:

  1. wait()方法的作用是让当前线程等待,当调用wait方法后,当前线程释放同步锁,其他线程可以获取该同步锁执行
  2. 只有和持有该对象同步锁的线程执行了notify()方法并且释放了同步锁以后,wait线程才可以获取锁继续执行

三、示例:

// WaitTest.java的源码
class ThreadA extends Thread{

    public ThreadA(String name) {
        super(name);
    }

    public void run() {
        synchronized (this) {
            System.out.println(Thread.currentThread().getName()+" call notify()");
            // 唤醒当前的wait线程
            notify();
        }
    }
}

public class WaitTest {

    public static void main(String[] args) {

        ThreadA t1 = new ThreadA("t1");

        synchronized(t1) {
            try {
                // 启动“线程t1”
                System.out.println(Thread.currentThread().getName()+" start t1");
                t1.start();

                // 主线程等待t1通过notify()唤醒。
                System.out.println(Thread.currentThread().getName()+" wait()");
                t1.wait();

                System.out.println(Thread.currentThread().getName()+" continue");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

/**
结果:
main start t1
main wait()
t1 call notify()
main continue

结果说明:
如下图,说明了“主线程”和“线程t1”的流程。

(01) 注意,图中"主线程" 代表“主线程main”。"线程t1" 代表WaitTest中启动的“线程t1”。 而“锁” 代表“t1这个对象的同步锁”。
(02) “主线程”通过 new ThreadA("t1") 新建“线程t1”。随后通过synchronized(t1)获取“t1对象的同步锁”。然后调用t1.start()启动“线程t1”。
(03) “主线程”执行t1.wait() 释放“t1对象的锁”并且进入“等待(阻塞)状态”。等待t1对象上的线程通过notify() 或 notifyAll()将其唤醒。
(04) “线程t1”运行之后,通过synchronized(this)获取“当前对象的锁”;接着调用notify()唤醒“当前对象上的等待线程”,也就是唤醒“主线程”。
(05) “线程t1”运行完毕之后,释放“当前对象的锁”。紧接着,“主线程”获取“t1对象的锁”,然后接着运行。
*/

(8)、线程优先级

一、定义

二、线程优先级

三、算法

1、链表:

Java链表的实现:https://blog.csdn.net/jianyuerensheng/article/details/51200274

tips:双指针法,快慢指针(一个指针速度慢,一个指针速度快)

eg1:查找链表中间元素

fast指针每次往后走两步,slow每次向后走一步,这样当fast走到链表结尾时,slow刚好走到链表的中间位置

class Solution {
public:
    ListNode* getKthFromEnd(ListNode* head, int k) {
        ListNode *p = head, *q = head; //初始化
        while(k--) {   //将 p指针移动 k 次
            p = p->next;
        }
        while(p != nullptr) {//同时移动,直到 p == nullptr
            p = p->next;
            q = q->next;
        }
        return q;
    }
};

eg2:倒数第k个元素问题

先来看"倒数第k个元素的问题"。设有两个指针 p 和 q,初始时均指向头结点。首先,先让 p 沿着 next 移动 k 次。此时,p 指向第 k+1个结点,q 指向头节点,两个指针的距离为 k 。然后,同时移动 p 和 q,直到 p 指向空,此时 p 即指向倒数第 k 个结点

public:
    ListNode* middleNode(ListNode* head) {
        ListNode *p = head, *q = head;
        while(q != nullptr && q->next != nullptr) {
            p = p->next;
            q = q->next->next;
        }
        return p;
    } 
};

eg3:链表是否存在环

类似于两个人在操场上跑步,其中一个快,一个慢,那么最终快和慢的一定会相遇

public:
    bool hasCycle(ListNode *head) {
        ListNode *slow = head;
        ListNode *fast = head;
        while(fast != nullptr) {
            fast = fast->next;
            if(fast != nullptr) {
                fast = fast->next;
            }
            if(fast == slow) {
                return true;
            }
            slow = slow->next;
        }
        return nullptr;
    }
};

四:Redis

1、Redis持久化:

RDB和AOF

2、Redis事务

MULTI:开启事务 EXEC:提交事务 DISCARD:放弃事务

开启事务后,所有操作命令都会加入到队列中,并不会立即执行,等到执行EXEC时再统一执行命令

五、网络

TCP三次握手

1、首先客户端发送一个SYN=1和seq=x包给服务器

2、服务器确认收到后,发送给客户端SYN=1,ack=x+1,seq=y(这个是服务器端的序列号)

3、客户端收到服务器端的通知后发送ACK=1,seq=x+1,ack=y+1

TCP四次挥手

举报

相关推荐

0 条评论