java知识点总结

天下间,岂有长生不灭者

对于很多初学者或则刚毕业的学生来说,面试找到一份好的工作是相当必要的,其实很多大的互联网公司或则优秀的传统企业都是比较看重基础的,所以,在此,对面试经常被问到的基础知识点做一个总结。

大纲:

  • java入门
  • 集合
  • JVM 内存管理
  • Java 8 知识点
  • 网络协议相关
  • 数据库相关
  • MVC 框架相关
  • git常规操作
  • memcache redis缓存
  • 分布式数据集


1.java入门

a.面向对象的特征:封装,继承,抽象,多态

b.覆盖和重载的区别:这个主要是从使用注意点和重载条件两方面进行一个分析

注意点:子类抛出的异常不能多于父类,访问权限不能比父类小,比如说父类的访问权限是平protected那么子类只可能是public或则default,如果父类是private那么子类就不能重载这个方法了

重载条件:参数个数不同,参数类型不同,参数顺序不同    注意了,返回值的类型并不能构成重载,原因是java中调用方法不需要强制赋值,也就没法区分所以不能构成重载。

c.java继承的初始化顺序

  • 按定义时的顺序初始化基类的static成员
  • 按定义时的顺序初始化扩展类的static成员
  • (以上两条是对第一次定义扩展类对象时运行)
  • 按定义时顺序执行基类的指定初始化
  • 执行基类的构造函数
  • 按定义时的顺序执行扩展类的指定初始化
  • 执行扩展类的构造函数

d.抽象类和接口的区别

  • 抽象类中可以没有抽象方法;接口中的方法必须是抽象方法;
  • 抽象类中可以有普通的成员变量;接口中的变量必须是 static final 类型的,必须被初始化 , 接口中只有常量,没有变量;
  • 抽象类只能单继承,接口可以继承多个父接口;
  • 继承一个接口必须要实现这个接口的所有方法;而抽象类不需要;
  • Java8 中接口中会有 default 方法,即方法可以被实现;

e.java和c++的区别

共同点:都支持继承封装和多态,都是面向对象编程的语言
不同点:Java 不提供指针来直接访问内存,程序更加安全;Java 的类是单继承的,C++ 支持多重继承; Java 通过一个类实现多个接口来实现 C++ 中的多重继承; Java 中类不可以多继承,但是!!!接口可以多继承; Java 有自动内存管理机制,不需要程序员手动释放无用内存

f.list集合如何去重

利用set的特征
Set set=new HashSet();
List newLibraryIds=new ArrayList();
for(Long libraryId:libraryIds){
    if(set.add(libraryId)){
        newLibraryIds.add(libraryId);
    }
}

g.java中的值传递和引用传递的区别

值传递是指对象被值传递,意味着传递了对象的一个副本,即使副本被改变,也不会影响源对象。引用传递是指对象被引用传递,意味着传递的并不是实际的对象,而是对象的引用。因此,外部对引用对象的改变会反映到所有的对象上。

h.jdk,jre,jvm简单介绍下

JDK 是 java 开发工具包,是 java 开发环境的核心组件,并提供编译、调试和运行一个 java 程序所需要的所有工具,可执行文件和二进制文件,是一个平台特定的软件。
JRE 是 java 运行时环境,是 JVM 的实施实现,提供了运行 java 程序的平台。JRE 包含了 JVM,但是不包含 java 编译器 / 调试器之类的开发工具。
JVM 是 java 虚拟机,当我们运行一个程序时,JVM 负责将字节码转换为特定机器代码,JVM 提供了内存管理 / 垃圾回收和安全机制等。
这种独立于硬件和操作系统,正是 java 程序可以一次编写多处执行的原因。


区别:
JDK 用于开发,JRE 用于运行 java 程序;
JDK 和 JRE 中都包含 JVM;
JVM 是 java 编程语言的核心并且具有平台独立性。

i.java继承的初始化顺序

按定义时的顺序初始化基类的static成员
按定义时的顺序初始化扩展类的static成员
以上两条是对第一次定义扩展类对象时运行)
按定义时顺序执行基类的指定初始化
执行基类的构造函数
按定义时的顺序执行扩展类的指定初始化
行扩展类的构造函数


2.java集合

a.Hashtable和HashMap的区别

  1. HashMap 没有考虑同步,是线程不安全的;Hashtable 使用了 synchronized 关键字,是线程安全的;

  2. 前者允许 null 作为 Key;后者不允许 null 作为 Key。

  3. hashmap详解

java知识点总结_第1张图片

HashMap的概述:
在java编程语言中,最基本的结构就是两种,一个是数组,另外一个是模拟指针(引用),所有的数据结构都可以用这两个基本结构来构造的,
HashMap也不例外。HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。这样的结构结合了链表在增删方面的高效和数组在
寻址上的优势
HashMap的结构:
哈希表是由数组+链表组成的,数组的默认长度为16(可以自动变长。在构造HashMap的时候也可以指定一个长度),数组里每个元素存储的是
一个链表的头结点。而组成链表的结点其实就是hashmap内部定义的一个类:Entity。Entity包含三个元素:key,value和指向下一个Entity

的next

HashMap的存取:
这些元素是按照什么样的规则存储到数组中呢。一般情况是通过hash(key)%(len-1)获得,也就是元素的key的哈希值对数组长度取模得到。
比如上述哈希表中,12%16=12,28%16=12,108%16=12,140%16=12。所以12、28、108以及140都存储在数组下标为12的位置。

HashMap的存储--put:
int hash = key.hashCode(); // 这个hashCode方法这里不详述,只要理解每个key的hash是一个固定的int值
int index = hash %( Entry[].length-1);
table[index] = value;//假定存储链表头结点的数组名为table
用table[index]表示通过hash值计算出来的、元素需要存储在数组中的位置。先判断该位置上有没有存有Entity,没有的话就创建一个Entity对象,在该位置上插入,插入结束;如果有的话,通过链表的遍历方式去逐个遍历,通过equals方法将key和已有的key进行比较,看看有没有已经存在的key,有的话用新的value替换老的value;如果没有,则在table[index]插入该Entity,把原来在table[index]位置上的Entity赋值给新的 Entity的next,也即,新的Entity插入(put)的位置永远是在链表的最前面,这样插入结束。 打个比方, 第一个键值对A进来,通过计算其key的hash得到的index=0,记做:table[0] = A。一会后又进来一个键值对B,通过计算其index也等于0,现在怎么办?HashMap会这样做:B.next = A,table[0] = B,如果又进来C,index也等于0,那么C.next = B,table[0] = C;这样我们发现index=0的地方其实存取了A,B,C三个键值对,他们通过next这个属性链接在一起。
注:null key总是存放在Entry[]数组的第一个元素。
扩展:
按照散列函数的定义,如果两个对象相同,即obj1.equals(obj2)=true,则它们的hashCode必须相同,但如果两个对象不同,则它们的hashCode不一定不同。如果两个不同对象的hashcode相同,就称为冲突(解决hash冲突一般用链地址法)。冲突会导致操作哈希表的时间开销增大,所以尽量定义好的hashCode()方法,能加快哈希表的操作。覆盖了equals方法之后一定要覆盖hashCode方法,原因很简单,比如,String a = new String(“abc”);String b = new String(“abc”);如果不覆盖hashCode的话,那么a和b的hashCode就会不同,把这两个类当做key存到HashMap中的话就 会出现问题,就会和key的唯一性相矛盾。

HashMap的读取--get:
先定位到数组元素,再遍历该元素处的链表
复制代码
public V get(Object key) {
        if (key == null)
            return getForNullKey();
        int hash = hash(key.hashCode());
        //先定位到数组元素,再遍历该元素处的链表
        for (Entry e = table[indexFor(hash, table.length)];
             e != null;
             e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
                return e.value;//显然,在寻找目标元素的时候,除了对比通过key计算出来的hash值,还会用双等或equals方法对key本身来进行比较,两者都为true时才会返回这个元素
        }
        return null;
}

b.ConcurrentHashMap 和 Hashtable 的区别

ConcurrentHashMap 结合了 HashMap 和 HashTable 二者的优势。HashMap 没有考虑同步,hashtable 考虑了同步的问题。但是 hashtable 在每次同步执行时都要锁住整个结构。 ConcurrentHashMap 锁的方式是稍微细粒度的。 ConcurrentHashMap 将 hash 表分为 16 个桶(默认值),诸如 get,put,remove 等常用操作只锁当前需要用到的桶。


ConcurrentHashMap 实现原理:
该类包含两个静态内部类 HashEntry 和 Segment;前者用来封装映射表的键值对,后者用来充当锁的角色;
Segment 是一种可重入的锁 ReentrantLock,每个 Segment 守护一个 HashEntry 数组里得元素,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment 锁。

c.List、Set 和 Map 的初始容量和加载因子

1. List
  ArrayList 的初始容量是 10;加载因子为 0.5; 扩容增量:原容量的 0.5 倍 +1;一次扩容后长度为 16。
  Vector 初始容量为 10,加载因子是 1。扩容增量:原容量的 1 倍,如 Vector 的容量为 10,一次扩容后是容量为 20。
2. Set
HashSet,初始容量为 16,加载因子为 0.75; 扩容增量:原容量的 1 倍; 如 HashSet 的容量为 16,一次扩容后容量为 32
3. Map
HashMap,初始容量 16,加载因子为 0.75; 扩容增量:原容量的 1 倍; 如 HashMap 的容量为 16,一次扩容后容量为 32


d.java.util.ArrayList.trimToSize() 

具有去除空项压缩 体积的作用,方法修整此ArrayList实例的是列表的当前大小的容量。应用程序可以使用此操作,以尽量减少一个ArrayList实例的存储。

3.jvm内存管理

a.多线程和单线程的区别和联系

1.在单核 CPU 中,将 CPU 分为很小的时间片,在每一时刻只能有一个线程在执行,是一种微观上轮流占用 CPU 的机制

2.多线程会存在线程上下文切换,会导致程序执行速度变慢,即采用一个拥有两个线程的进程执行所需要的时间比一个线程的进程执行两次所需要的时间要多一些 也就是说采用多线程并不会提高程序的执行速度,但是对于用户来说,缩短了程序响应的时间

b.线程和进程的区别

1.进程是一个 “执行中的程序”,是系统进行资源分配和调度的一个独立单位;
2.线程是进程的一个实体,一个进程中拥有多个线程,线程之间共享地址空间和其它资源(所以通信和同步等操作线程比进程更加容易);
3.线程上下文的切换比进程上下文切换要快很多。
(1)进程切换时,涉及到当前进程的 CPU 环境的保存和新被调度运行进程的 CPU 环境的设置。
(2)线程切换仅需要保存和设置少量的寄存器内容,不涉及存储管理方面的操作。

c.多线程产生死锁的四个必要条件

1.互斥条件:一个资源每次只能被一个线程使用;
2.请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放;
3.不剥夺条件:进程已经获得的资源,在未使用完之前,不能强行剥夺;
4.循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系。

d.如何避免死锁

 获得锁的顺序是一定的,比如只有获得A锁的线程才有资格获取B锁,按顺序获取锁就可以避免死锁

e.Java 集合的快速失败机制 “fail-fast”

它是 java 集合的一种错误检测机制,当多个线程对集合进行结构上的改变的操作时,有可能会产生 fail-fast 机制。
例如 :假设存在两个线程(线程 1、线程 2),线程 1 通过 Iterator 在遍历集合 A 中的元素,在某个时候线程 2 修改了集合 A 的结构(是结构上面的修改,而不是简单的修改集合元素的内容),那么这个时候程序就会抛出 ConcurrentModificationException 异常,从而产生 fail-fast 机制。
原因: 迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变 modCount 的值。
每当迭代器使用 hashNext()/next() 遍历下一个元素之前,都会检测 modCount 变量是否为 expectedmodCount 值,是的话就返回遍历;否则抛出异常,终止遍历。
解决办法:
1.在遍历过程中,所有涉及到改变 modCount 值得地方全部加上 synchronized;
2.使用 CopyOnWriteArrayList 来替换 ArrayList。

f.如何指定多个线程的执行顺序?

设定一个 orderNum,每个线程执行结束之后,更新 orderNum,指明下一个要执行的线程。并且唤醒所有的等待线程。
在每一个线程的开始,要 while 判断 orderNum 是否等于自己的要求值!!不是,则 wait,是则执行本线程。

g.jvm运行过程

加载    将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区中的运行时数据结构,在堆中生成一个代表这个类的java.lang.Class对象,作为方法区类数据的访问入口,这个过程需要类加载器参与。
链接    将java类的二进制代码合并到JVM的运行状态之中的过程
  验证:确保加载的类信息符合JVM规范,没有安全方面的问题
  准备:正式为类变量(static变量)分配内存并设置类变量初始值的阶段,这些内存都将在方法去中进行分配
  解析:虚拟机常量池的符号引用替换为字节引用过程 
初始化  初始化阶段是执行类构造器()方法的过程。类构造器()方法是由编译器自动收藏类中的所有类变量的赋值动作和静态语句块(static块)中的语句合并产生
当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化
虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步
当范围一个Java类的静态域时,只有真正声名这个域的类才会被初始化
使用
卸载


h.ThreadLocal(线程局部变量)关键字:

答:当使用 ThreadLocal 维护变量时,其为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立的改变自己的副本,而不会影响其他线程对应的副本。
ThreadLocal 内部实现机制:
每个线程内部都会维护一个类似 HashMap 的对象,称为 ThreadLocalMap,里边会包含若干了 Entry(K-V 键值对),相应的线程被称为这些 Entry 的属主线程;
Entry 的 Key 是一个 ThreadLocal 实例,Value 是一个线程特有对象。Entry 的作用即是:为其属主线程建立起一个 ThreadLocal 实例与一个线程特有对象之间的对应关系;
Entry 对 Key 的引用是弱引用;Entry 对 Value 的引用是强引用。


i.垃圾回收算法有哪些?

引用计数 :原理是此对象有一个引用,即增加一个计数,删除一个引用则减少一个计数。垃圾回收时,只用收集计数为 0 的对象。此算法最致命的是无法处理循环引用的问题;
标记-清除 :此算法执行分两阶段。第一阶段从引用根节点开始标记所有被引用的对象,第二阶段遍历整个堆,把未标记的对象清除;
此算法需要暂停整个应用,同时,会产生内存碎片;
复制算法 :此算法把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾回收时,遍历当前使用区域,把正在使用中的对象复制到另外一个区域中;
此算法每次只处理正在使用中的对象,因此复制成本比较小,同时复制过去以后还能进行相应的内存整理,不会出现 “碎片” 问题。当然,此算法的缺点也是很明显的,就是需要两倍内存空间;
标记-整理 :此算法结合了 “标记-清除” 和 “复制” 两个算法的优点。也是分两阶段,第一阶段从根节点开始标记所有被引用对象,第二阶段遍历整个堆,把清除未标记对象并且把存活对象 “压缩” 到堆的其中一块,按顺序排放。
此算法避免了 “标记-清除” 的碎片问题,同时也避免了 “复制” 算法的空间问题。

j.启动一个线程是用run()还是start()? 为什么?(一些初始化的操作)

启动线程肯定要用start()方法。当用start()开始一个线程后,线程就进入就绪状态,使线程所代表的虚拟处理机处于可运行状态,这意味着它可以由JVM调度并执行。这并不意味着线程就会立即运行。当cpu分配给它时间时,才开始执行run()方法(如果有的话)。start()是方法,它调用run()方法.而run()方法是你必须重写的. run()方法中包含的是线程的主体。如果直接执行run方法,那就不是执行一个线程了,而只是一个普通的方法

j.sleep,wait,notify

sleep是Thread类的静态方法,谁调用谁去睡觉。sleep是占用cpu去睡觉,而wait是放弃cpu去睡觉, sleep没有释放锁,而wait释放了锁,sleep不会让出cpu资源,wait是进入线程池等待,一般wait是不会使用时间参数,他必须等待别人notify他才会进入就绪队中。而sleep只要时间到了,就会自动进入就绪队列。如果等不及了,只能通过interrupt来强项打断。

wait,notify以及notifyall只能在同步控制方法或者同步控制块中使用,而sleep可是在任何地方使用。

sleep必须捕获异常,而其他3个则不需要。

在JAVA中的Object类型中,都是带有一个内存锁的,在有线程获取该内存锁后,其它线程无法访问该内存,从而实现JAVA中简单的同步、互斥操作。


k.i++线程安全吗

i++和++i都是i=i+1的意思,但是过程有些许区别:

i++:先赋值再自加。(例如:i=1;a=1+i++;结果为a=1+1=2,语句执行完后i再进行自加为2)

++i:先自加再赋值。(例如:i=1;a=1+++i;结果为a=1+(1+1)=3,i先自加为2再进行运算)

但是在单独使用时没有区别:如for(int i=0;i<10;i++){ }和for(int i=0;i<10;++i) { }没有区别。

i++和++i的线程安全分为两种情况:

1、如果i是局部变量(在方法里定义的),那么是线程安全的。因为局部变量是线程私有的,别的线程访问不到,其实也可以说没有线程安不安全之说,因为别的线程对他造不成影响。

2、如果i是全局变量(类的成员变量),那么是线程不安全的。因为如果是全局变量的话,同一进程中的不同线程都有可能访问到。

l.java的四种引用

强引用:

只要引用存在,垃圾回收器永远不会回收
Object obj = new Object();
//可直接通过obj取得对应的对象 如obj.equels(new Object());
而这样 obj对象对后面new Object的一个强引用,只有当obj这个引用被释放之后,对象才会被释放掉,这也是我们经常所用到的编码形式。

软引用:

非必须引用,内存溢出之前进行回收,可以通过以下代码实现
Object obj = new Object();
SoftReference sf = new SoftReference(obj);
obj = null;
sf.get();//有时候会返回null
这时候sf是对obj的一个软引用,通过sf.get()方法可以取到这个对象,当然,当这个对象被标记为需要回收的对象时,则返回null;
软引用主要用户实现类似缓存的功能,在内存足够的情况下直接通过软引用取值,无需从繁忙的真实来源查询数据,提升速度;当内存不足时,自动删除这部分缓存数据,从真正的来源查询这些数据。

 

弱引用:

第二次垃圾回收时回收,可以通过如下代码实现
Object obj = new Object();
WeakReference wf = new WeakReference(obj);
obj = null;
wf.get();//有时候会返回null
wf.isEnQueued();//返回是否被垃圾回收器标记为即将回收的垃圾
弱引用是在第二次垃圾回收时回收,短时间内通过弱引用取对应的数据,可以取到,当执行过第二次垃圾回收时,将返回null。
弱引用主要用于监控对象是否已经被垃圾回收器标记为即将回收的垃圾,可以通过弱引用的isEnQueued方法返回对象是否被垃圾回收器标记。

 

虚引用:

垃圾回收时回收,无法通过引用取到对象值,可以通过如下代码实现
Object obj = new Object();
PhantomReference pf = new PhantomReference(obj);
obj=null;
pf.get();//永远返回null
pf.isEnQueued();//返回是否从内存中已经删除
虚引用是每次垃圾回收的时候都会被回收,通过虚引用的get方法永远获取到的数据为null,因此也被成为幽灵引用。
虚引用主要用于检测对象是否已经从内存中删除。

 


4.网络协议相关

a.tcp ip协议三次握手描述和四次握手断开连接


首先Client端发送连接请求报文,Server段接受连接后回复ACK报文,并为这次连接分配资源。Client端接收到ACK报文后也向Server段发生ACK报文,并分配资源,这样TCP连接就建立了。


b.为什么连接的时候是三次握手,关闭的时候却是四次握手?
答:因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,"你发的FIN报文我收到了"。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。



6.springmvc web相关

a.ajax状态值和状态码

状态值

 0 - (未初始化)还没有调用send()方法
 1 - (载入)已调用send()方法,正在发送请求
 2 - (载入完成)send()方法执行完成,已经接收到全部响应内容
 3 - (交互)正在解析响应内容
 4 - (完成)响应内容解析完成,可以在客户端调用了

状态码(此处标明常出现的状态码)

200——交易成功
400——错误请求,如语法错误
401——请求授权失败
403——请求不允许
404——没有发现文件、查询或URl
500——服务器产生内部错误
502——服务器暂时不可用,有时是为了防止发生系统过载



7.数据库相关

a.使用索引的注意事项

1.索引不会包含有NULL的列
只要列中包含有NULL值,都将不会被包含在索引中,复合索引中只要有一列含有NULL值,那么这一列对于此符合索引就是无效的。
2.使用短索引
对串列进行索引,如果可以就应该指定一个前缀长度。例如,如果有一个char(255)的列,如果在前10个或20个字符内,多数值是唯一的,那么就不要对整个列进行索引。短索引不仅可以提高查询速度而且可以节省磁盘空间和I/O操作。
3.索引列排序
mysql查询只使用一个索引,因此如果where子句中已经使用了索引的话,那么order by中的列是不会使用索引的。因此数据库默认排序可以符合要求的情况下不要使用排序操作,尽量不要包含多个列的排序,如果需要最好给这些列建复合索引。
4.like语句操作
一般情况下不鼓励使用like操作,如果非使用不可,注意正确的使用方式。like ‘%aaa%’不会使用索引,而like ‘aaa%’可以使用索引。
5.不要在列上进行运算
6.不使用负向查询NOT IN 、<>、!=操作,但<,<=,=,>,>=,BETWEEN,IN是可以用到索引的
7.索引要建立在经常进行select操作的字段上。
这是因为,如果这些列很少用到,那么有无索引并不能明显改变查询速度。相反,由于增加了索引,反而降低了系统的维护速度和增大了空间需求。
8.索引要建立在值比较唯一的字段上。
9.对于那些定义为text、image和bit数据类型的列不应该增加索引。因为这些列的数据量要么相当大,要么取值很少。
10.在where和join中出现的列需要建立索引。
11.where的查询条件里有不等号(where column != …),mysql将无法使用索引。
12.如果where字句的查询条件里使用了函数(如:where DAY(column)=…),mysql将无法使用索引。
在join操作中(需要从多个数据表提取数据时),mysql只有在主键和外键的数据类型相同时才能使用索引,否则及时建立了索引也不会使用。

b.事务的四大特性

原子性(Atomicity):事务作为一个整体被执行 ,要么全部执行,要么全部不执行;
一致性(Consistency):保证数据库状态从一个一致状态转变为另一个一致状态;
隔离性(Isolation):多个事务并发执行时,一个事务的执行不应影响其他事务的执行;
持久性(Durability):一个事务一旦提交,对数据库的修改应该永久保存。

c.事务的隔离级别

读未提交,顾名思义,就是一个事务可以读取另一个未提交事务的数据。

读提交,顾名思义,就是一个事务要等另一个事务提交后才能读取数据。(程序员拿着信用卡去享受生活(卡里当然是只有3.6万),当他埋单时(程序员事务开启),收费系统事先检测到他的卡里有3.6万,就在这个时候!!程序员的妻子要把钱全部转出充当家用,并提交。当收费系统准备扣款时,再检测卡里的金额,发现已经没钱了(第二次检测金额当然要等待妻子转出金额事务提交完)。程序员就会很郁闷,明明卡里是有钱的)

重复读,就是在开始读取数据(事务开启)时,不再允许修改操作(重复读可以解决不可重复读问题。写到这里,应该明白的一点就是,不可重复读对应的是修改,即UPDATE操作。但是可能还会有幻读问题。因为幻读问题对应的是插入INSERT操作,而不是UPDATE操作)

Serializable 是最高的事务隔离级别,在该级别下,事务串行化顺序执行,可以避免脏读、不可重复读与幻读。但是这种事务隔离级别效率低下,比较耗数据库性能,一般不使用。


d.数据库索引的底层实现

底层数据结构是 B+ 树

使用 B+ 树的原因:查找速度快、效率高,在查找的过程中,每次都能抛弃掉一部分节点,减少遍历个数

B+树的数据结构


e.mysql为什么尽量不做连表查询

1,mysql连表查询是比较慢的,相比先查出一个表的记录,然后再查另外一个表。

2,单表查询的数据方便做缓存

3,连表查询是会锁多表,单表查询只锁单表。

 4,如果以后需要分库分表,连表的sql语句就需要改了


f.对比MySQL,你究竟在什么时候更需要MongoDB

你期望一个更高的写负载

默认情况下,对比事务安全,MongoDB更关注高的插入速度。如果你需要加载大量低价值的业务数据,那么MongoDB将很适合你的用例。但是必须避免在要求高事务安全的情景下使用MongoDB,比如一个1000万美元的交易。

不可靠环境保证高可用性

设置副本集(主-从服务器设置)不仅方便而且很快,此外,使用MongoDB还可以快速、安全及自动化的实现节点(或数据中心)故障转移。

未来会有一个很大的规模

数据库扩展是非常有挑战性的,当单表格大小达到5-10GB时,MySQL表格性能会毫无疑问的降低。如果你需要分片并且分割你的数据库,MongoDB因为支持自动分片将很容易实现这一点。

使用基于位置的数据查询

MongoDB支持二维空间索引,因此可以快速及精确的从指定位置获取数据。

非结构化数据的爆发增长

给RDBMS增加列在有些情况下可能锁定整个数据库,或者增加负载从而导致性能下降,这个问题通常发生在表格大于1GB(更是下文提到BillRun系统中的痛点——单表格动辄几GB)的情况下。鉴于MongoDB的弱数据结构模式,添加1个新字段不会对旧表格有任何影响,整个过程会非常快速;因此,在应用程序发生改变时,你不需要专门的1个DBA去修改数据库模式。

缺少专业的数据库管理员

如果你没有专业的DBA,同时你也不需要结构化你的数据及做join查询,MongoDB将会是你的首选。MongoDB非常适合类的持久化,类可以被序列化成JSON并储存在MongoDB。需要注意的是,如果期望获得一个更大的规模,你必须要了解一些最佳实践来避免走入误区。

g.表分区有以下优点:

改善查询性能:对分区对象的查询可以仅搜索自己关心的分区,提高检索速度。
增强可用性:如果表的某个分区出现故障,表在其他分区的数据仍然可用;
维护方便:如果表的某个分区出现故障,需要修复数据,只修复该分区即可;
均衡I/O:可以把不同的分区映射到磁盘以平衡I/O,改善整个系统性能。
缺点:
分区表相关:已经存在的表没有方法可以直接转化为分区表。不过 Oracle 提供了在线重定义表的功能。


8.mvc相关

a.JDK 动态代理实现和 cglib 实现
选择:
如果目标对象实现了接口,默认情况下会采用 JDK 的动态代理实现 AOP,也可以强制使用 cglib 实现 AOP;
如果目标对象没有实现接口,必须采用 cglib 库,Spring 会自动在 JDK 动态代理和 cglib 之间转换

JDK 动态代理,只能对实现了接口的类生成代理,而不是针对类,该目标类型实现的接口都将被代理。原理是通过在运行期间创建一个接口的实现类来完成对目标对象的代理。
spring aop
在是spring上下文中定义类BeanNameAutoProxyCreator的bean
在bean中注入interceptor拦截器的类以及要被代理的目标对象
在拦截器中实现MethodInterceptor接口并实现invoke的方法
在invoke方法中通过invocation.getArguments()获取到被代理的目标对象并通过这个对象去调用目标对象的方法

jdk动态代理

定义一个实现接口 InvocationHandler 的类;
通过构造函数,注入被代理类;
实现 invoke( Object proxy, Method method, Object[] args)方法;
在主函数中获得被代理类的类加载器;
使用 Proxy.newProxyInstance( ) 产生一个代理对象;
通过代理对象调用各种方法

package jiankunking;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
 * 调用处理器实现类
 * 每次生成动态代理类对象时都需要指定一个实现了该接口的调用处理器对象
 */
public class InvocationHandlerImpl implements InvocationHandler
{
    /**
     * 这个就是我们要代理的真实对象
     */
    private Object subject;
    /**
     * 构造方法,给我们要代理的真实对象赋初值
     *
     * @param subject
     */
    public InvocationHandlerImpl(Object subject)
    {
        this.subject = subject;
    }
    /**
     * 该方法负责集中处理动态代理类上的所有方法调用。
     * 调用处理器根据这三个参数进行预处理或分派到委托类实例上反射执行
     *
     * @param proxy  代理类实例
     * @param method 被调用的方法对象
     * @param args   调用参数
     * @return
     * @throws Throwable
     */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
    {
        //在代理真实对象前我们可以添加一些自己的操作
        System.out.println("在调用之前,我要干点啥呢?");
        System.out.println("Method:" + method);
        //当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
        Object returnValue = method.invoke(subject, args);
        //在代理真实对象后我们也可以添加一些自己的操作
        System.out.println("在调用之后,我要干点啥呢?");
        return returnValue;
    }

}



package jiankunking;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
/**
 * 动态代理演示
 */
public class DynamicProxyDemonstration
{
    public static void main(String[] args)
    {
        //代理的真实对象
        Subject realSubject = new RealSubject();
        /**
         * InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
         * 其内部通常包含指向委托类实例的引用,用于真正执行分派转发过来的方法调用.
         * 即:要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法
         */
        InvocationHandler handler = new InvocationHandlerImpl(realSubject);
        ClassLoader loader = realSubject.getClass().getClassLoader();
        Class[] interfaces = realSubject.getClass().getInterfaces();
        /**
         * 该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
         */
        Subject subject = (Subject) Proxy.newProxyInstance(loader, interfaces, handler);
        System.out.println("动态代理对象的类型:"+subject.getClass().getName());
        String hello = subject.SayHello("jiankunking");
        System.out.println(hello);
    }

}

经常用到的是如下的方式,在代理类中完成调用,达到切面编程注入参数的效果

@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
    try {
        //  获取拦截的方法的参数列表
        Object[] args = invocation.getArguments();
        if (args == null || args.length == 0) {
            return null;
        }


        for (Object arg : args) {
            if (!(arg instanceof 基础接口)) {
                continue;
            }
            ((基础接口) arg).setUserId('1234');
        }

    } catch (Exception e) {

    }

    return invocation.proceed();
}

b.Mybatis 知识点
关于 MyBatis 主要考察占位符#和 $ 的区别,区别如下:

符号将传入的数据都当做一个字符串,会对自动传入的数据加一个双引号;
$ 符号将传入的数据直接显示生成 SQL 中;
符号存在预编译的过程,,对问号赋值,防止 SQL 注入;
$ 符号是直译的方式,一般用在 order by ${列名}语句中;

能用#号就不要用 $ 符号。

c.单例

在controller中springmvc框架默认是单例的,当多个线程调用它的时候,它里面的instance变量不是线程安全的,会发生窜数据的情况,因此我们在使用controller的时候
应避免在controller中定义实例变量
有以下几种解决方案:
a.在controller中使用ThreadLocal变量

b.在spring配置文件controller中声明scope="prototype",每次都创建新的controller


d.单点登录

首先,你要写一个单点登录(sso)和一个登陆系统,将那些需要进行单点登陆的系统都归纳到sso的白名单里,然后你在任意一个登陆系统登录之后,请求道sso去,生成信任,然后这个信任和客户端请求的系统(白名单的系统)返回给登陆的客户端,并且sso存储这个信任,客户端收到返回的白名单系统请求(这个请求必须是sso给出,不能由客户端直接可以请求),重定向请求到实际需要请求的地址,该系统验证拿着客户端的信任到sso中验证,验证ok说明在sso中进行过登陆,做放行,数据的获取等等,实现单点登录

e.springmvc执行步骤

DispatcherServlet在web.xml中的部署描述,从而拦截请求到Spring Web MVC
HandlerMapping的配置,从而将请求映射到处理器
HandlerAdapter的配置,从而支持多种类型的处理器
ViewResolver的配置,从而将逻辑视图名解析为具体视图技术
处理器(页面控制器)的配置,从而进行功能处理



9.git

版本回退
git reset --hard 139dcfaa558e3276b30b6b2e5cbbb9c00bbdca96 
git push -f -u origin master 
统计代码git提交的行数

$ git log --author="$(git config --get user.name)" --pretty=tformat: --numstat | gawk '{ add += $1 ; subs += $2 ; loc += $1 - $2 } END { printf "added lines: %s removed lines : %s total lines: %s\n",add,subs,loc }' -

rebase和merge的区别
rebase也可以分支往主干,主干往分支,这只是方向的问题,真正的区别是历史版本的不一致,merge是产生一个新的节点,将两个分支的合并,rebase是丢弃分支的历史版本提交并临时保存为补丁然后依次提交到要合并到的分支,效果是不会有合并的痕迹
所以一般主干往分支合用rebase避免保留痕迹,这属于是默认行为了
分支往主干合是有人为干预,所以要用merge,要保留记录

git和svn的区别
Git是分布式的,SVN不是(自己写的代码放在自己电脑上,一段时间后再提交、合并,也可以不用联网在本地提交)
GIT把内容按元数据方式存储,而SVN是按文件
GIT没有一个全局的版本号,而SVN有
GIT的内容完整性要优于SVN(GIT的内容存储使用的是SHA-1哈希算法。这能确保代码内容的完整性,确保在遇到磁盘故障和网络问题时降低对版本库的破坏)



10.memcached和redis

redis和memecache的不同在于:
1、存储方式:
memecache 把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小
redis有部份存在硬盘上,这样能保证数据的持久性。
2、数据支持类型:
redis在数据支持上要比memecache多的多。
3、使用底层模型不同:
新版本的redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。
4、运行环境不同:
Redis目前官方只支持Linux 上去行,从而省去了对于其它系统的支持,这样的话可以更好的把精力用于本系统 环境上的优化,虽然后来微软有一个小组为其写了补丁。但是没有放到主干上

1、Redis和Memcache都是将数据存放在内存中,都是内存数据库。不过memcache还可用于缓存其他东西,例如图片、视频等等。
2、Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,hash等数据结构的存储。
3、虚拟内存--Redis当物理内存用完时,可以将一些很久没用到的value 交换到磁盘
4、过期策略--memcache在set时就指定,例如set key1 0 0 8,即永不过期。Redis可以通过例如expire 设定,例如expire name 10
5、分布式--设定memcache集群,利用magent做一主多从;redis可以做一主多从。都可以一主一从
6、存储数据安全--memcache挂掉后,数据没了;redis可以定期保存到磁盘(持久化)
7、灾难恢复--memcache挂掉后,数据不可恢复; redis数据丢失后可以通过aof恢复
8、Redis支持数据的备份,即master-slave模式的数据备份。

总结:
Redis使用最佳方式是全部数据in-memory。
Redis更多场景是作为Memcached的替代者来使用。
当需要除key/value之外的更多数据类型支持时,使用Redis更合适。
当存储的数据不能被剔除时,使用Redis更合适。


11.分布式数据集

a.数据存储中间介:Map/Reduce 和RDD

对于RDD:

·        它是不变的数据结构存储

·        它是支持跨集群的分布式数据结构

·        可以根据数据记录的key对结构进行分区

·        提供了粗粒度的操作,且这些操作都支持分区

·        它将数据存储在内存中,从而提供了低延迟性

5个特点

RDD是Spark提供的核心抽象,全称为ResillientDistributed Dataset,即弹性分布式数据集。

RDD在抽象上来说是一种元素集合,包含了数据。它是被分区的,分为多个分区,每个分区分布在集群中的不同节点上,从而让RDD中的数据可以被并行操作。(分布式数据集)

RDD通常通过Hadoop上的文件,即HDFS文件或者Hive表,来进行创建;有时也可以通过应用程序中的集合来创建。

RDD最重要的特性就是,提供了容错性,可以自动从节点失败中恢复过来。即如果某个节点上的RDD partition,因为节点故障,导致数据丢了,那么RDD会自动通过自己的数据来源重新计算该partition。这一切对使用者是透明的。

RDD的数据默认情况下存放在内存中的,但是在内存资源不足时,Spark会自动将RDD数据写入磁盘。(弹性)


12.分布式锁

分布式锁常用方式详细介绍:https://www.cnblogs.com/austinspark-jessylu/p/8043726.html
分布式锁主要用来确保在分布式服务中数据的一致性和准确性
主要通过四种 方式:1.数据库对某方法名称存入,有的话表示已经锁住了没有表示可以获得锁2.数据库行级锁select ...for update
3.memcache,redis,tair缓存 4.zookeper

java知识点总结_第2张图片



你可能感兴趣的:(java)