一、搜索
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、创建状态
Thread thread=new Thread();
当程序new Thread(),说明该线程处于创建状态
2、就绪状态
t.start当线程调用start方法时,该线程则处于就绪状态,等待着CPU的调度
3、运行状态
线程获得了CPU和相应资源,开始运行run方法
4、阻塞状态
线程由于某种原因暂时停止运行,sleep(),suspend(),wait()等方法都可以让线程处于阻塞状态
5、死亡状态
线程运行完毕,或者线程调用stop方法,则该线程处于死亡状态
(4)、几个常用方法
1、join方法 强制线程运行
在线程操作中,可以使用 join() 方法让一个线程强制运行,线程强制运行期间,其他线程无法运行,必须等待此线程完成之后才可以继续执行。
2、sleep方法 休眠线程
在程序中允许一个线程进行暂时的休眠,直接使用 Thread.sleep() 即可实现休眠。
3、interrupt方法 中断线程
当一个线程运行时,另外一个线程可以直接通过interrupt()方法中断其运行状态。
4、setDaemon方法 后台线程
在 Java 程序中,只要前台有一个线程在运行,则整个 Java 进程都不会消失,所以此时可以设置一个后台线程,这样即使 Java 线程结束了,此后台线程依然会继续执行,要想实现这样的操作,直接使用 setDaemon() 方法即可。
5、setPriority方法
设置线程优先级 值分别为Thread.MIN_PRIORITY Thread.MAX_PRIORITY Thread.NORM_PRIORITY 范围1-10
6、yield方法 线程礼让
在线程操作中,也可以使用 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
一、原理:
在java中,每一个对象有且仅有一个同步锁。这也意味着,同步锁是依赖于对象而存在。 当我们调用某对象的synchronized方法时,就获取了该对象的同步锁。例如,synchronized(obj)就获取了“obj这个对象”的同步锁。 不同线程对同步锁的访问是互斥的。也就是说,某时间点,对象的同步锁只能被一个线程获取到!通过同步锁,我们就能在多线程中,实现对“对象/方法”的互斥访问。 例如,现在有两个线程A和线程B,它们都会访问“对象obj的同步锁”。假设,在某一时刻,线程A获取到“obj的同步锁”并在执行一些操作;而此时,线程B也企图获取“obj的同步锁” —— 线程B会获取失败,它必须等待,直到线程A释放了“该对象的同步锁”之后线程B才能获取到“obj的同步锁”从而才可以运行。
二、sychronized使用规则
我们将synchronized的基本规则总结为下面3条,并通过实例对它们进行说明。
第一条: 当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程对**“该对象”的该“synchronized方法”或者“synchronized代码块”的访问将被阻塞。
第二条: 当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程仍然可以访问“该对象”的非同步代码块**。
第三条: 当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程对**“该对象”的其他的“synchronized方法”或者“synchronized代码块”的访问**将被阻塞。
三、实例锁和全局锁(对象锁和类锁)
实例锁:当时使用synchronized方法或者sychronized代码块时,如果不加上static的话,那么这个锁就是实例锁(对象锁),也就是说同一个对象共享同一个实例锁,不同的对象享受不同的实例锁,互不干涉。
全局锁:当使用static synchronized实现同步时,那么就是全局锁,即同一个类共享一个锁,不管有多少对象,只要他们属于同一个类,那么这些对象都共享一个锁
(7)、wait和notify
一、介绍
在Object.java中,定义了wait(), notify()和notifyAll()等接口。wait()的作用是让当前线程进入等待状态,同时,wait()也会让当前线程释放它所持有的锁。而notify()和notifyAll()的作用,则是唤醒当前对象上的等待线程;notify()是唤醒单个线程,而notifyAll()是唤醒所有的线程。
Object类中关于等待/唤醒的API详细信息如下:
notify() – 唤醒在此对象监视器上等待的单个线程。
notifyAll() – 唤醒在此对象监视器上等待的所有线程。
wait() – 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法”,当前线程被唤醒(进入“就绪状态”)。
wait(long timeout) – 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量”,当前线程被唤醒(进入“就绪状态”)。
wait(long timeout, int nanos) – 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量”,当前线程被唤醒(进入“就绪状态”)。
二、注意点:
- wait()方法的作用是让当前线程等待,当调用wait方法后,当前线程释放同步锁,其他线程可以获取该同步锁执行
- 只有和持有该对象同步锁的线程执行了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)、线程优先级
一、定义
用户线程:用户线程一般用于执行用户任务
守护线程:守护线程一般用于执行后台任务
当用户线程全部执行完毕后,守护线程也会自动结束。
我们可以用isDaemon()方法判断线程是否为守护线程,当isDaemon()返回false时,表明该线程为用户线程,可以用setDaemon()方法将一个线程设为守护线程
二、线程优先级
线程优先级范围1-10,数字越大,说明线程优先级越高,默认这个数字为5。优先级高的优先于优先级低执行
三、算法
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时再统一执行命令
1、正常执行:当所有命令正常执行时,提交事务时会将所有在队列中的命令正常提交,然后执行
2、放弃事务:执行DISCARD命令则放弃本次事务
3、全体连坐:有一个出错,全部都不提交
4、Watch监控:Watch监控时如果有其他人修改了数据,那么此次事务提交失败,需要获取到最新的数据才能继续
五、网络
TCP三次握手
1、首先客户端发送一个SYN=1和seq=x包给服务器
2、服务器确认收到后,发送给客户端SYN=1,ack=x+1,seq=y(这个是服务器端的序列号)
3、客户端收到服务器端的通知后发送ACK=1,seq=x+1,ack=y+1
TCP四次挥手