相关总结

一.多线程概念:

1. 多线程内部通信: 通过共享该进程的内存的方式进行通信。一个线程在适当的时候修改该内存位置的值,另一个线程在后续
操作中通过读取相同内存位置来得到修改后的值。

2. 在构造方法完成之前,要确保正在创建的对象的引用不会被其他线程访问到。否则可能会拿到不完整的对象

3.可重入锁提高效率,在线程安全的散列表中,每个修改的方法都上锁,如果不可重入,每个都要竞争,加锁释放锁太慢。

4. 可见性:可能是cpu的多级缓存机制,多核读主存数据时会先到缓存找。这时可能缓存主存不一致

5. volatile:直接写入主存,对下一次读操作可见。普通变量与volatile变量的区别是,volatile变量保证了新值能够立刻同步到主内存,以及每次使用前从主内存刷新

6. .Java内存模型规定了所有的变量都存储在主内存中,每个线程还有自己的工作内存,线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝(大对象不是全拷贝,只是用到的字段等),线程对变量的所有操作(读取,赋值等)都必须在工作内存中进行,而不能直接读写主内存中的变量。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成。

7.可见性: 当一条线程修改了这个变量的值,新值对于其它线程来讲是可以立刻得知的,因为每次线程使用它之前会刷新(普通变量通过回写主内存才能够实现)

8.CPU重排序: 根据指令的依赖情况以保证处理结果,相互依赖的顺序一致。而lock的作用就是lock之前的可以重排,lock之后的可以重排,但不能跨lock重排


二.Thread本身的线程方法

1.允许当前线程等待另一个线程运行结束join():如果线程A通过调用线程B的join方法等待线程B运行结束,那么在线程B中对共享变量所作的修改对于线程A肯定是可见的。一般做法是,线程A创建并启动线程B后,线程A执行另外的一些操作,接着调用join方法等待线程B完成。线程B和线程A通过修改共享变量的方式来进行交互。

2. Thread.sleep:静态方法,让当前线程进入休眠状态,但不会释放锁。

3.线程中断: 应用场景是实现可取消的任务。

1). Java的线程调度是协作式的,即每个有一个中断标志位,虚拟机底层会轮询这个标志位。中断只是更改这个标志位,并不意味着线程会停止当前的工作。根据中断情况不同而不同处理。这样留给线程处理收尾工作的机会。中断一个线程只是为了引起该线程的注意,被中断线程可以决定如何应对中断。

2).中断的情况分为两种:

a.阻塞时: 当阻塞时,会抛出InterruptedException异常。并将标志位清空(设置为false),因为此时线程已经由阻塞状态转为就绪状态继续进行后续处理
b.非阻塞时:一般代码都会写在一个while的循环体内,循环判断条件是

while(!Thread.currentThread().isInterrupted())

当有中断时退出循环。

3).两种处理中断请求的代码:
a.while循环在内:

public void run() {  
    try {  
        ...  
        /* 
         * 不管循环里是否调用过线程阻塞的方法如sleep、join、wait,这里还是需要加上 
         * !Thread.currentThread().isInterrupted()条件,虽然抛出异常后退出了循环,显 
         * 得用阻塞的情况下是多余的,但如果调用了阻塞方法但没有阻塞时,这样会更安全、更及时。 
         */  
        while (!Thread.currentThread().isInterrupted()&& more work to do) {  
            do more work   
        }  
    } catch (InterruptedException e) {  
        //线程在wait或sleep期间被中断了  
    } finally {  
        //线程结束前做一些清理工作  
    }  
}  
b.while循环在外

public void run() {  
    while (!Thread.currentThread().isInterrupted()&& more work to do) {  
        try {  
            ...  
            sleep(delay);  
        } catch (InterruptedException e) {  
            Thread.currentThread().interrupt();//重新设置中断标示  
        }  
    }  
}  
4).底层中断异常处理方式:不要底层擅自处理,应交给上层

a.直接抛出异常

void mySubTask() throws InterruptedException {  
    ...  
    sleep(delay);  
    ...  
} 
5). interrupt()方法是不能中断死锁线程的,因为锁定的位置根本无法抛出异常



三.Object的线程方法:当获得该对象锁之后,可以调用

1.wait(): 释放其持有的监视器对象上的锁。使当前线程进入等待状态。

1) 当前线程持有监视器对象上的锁: synchronized(object)    object.wait();

2. notify()/notifyAll():当线程被唤醒时,线程需要重新竞争锁来获得继续运行wait方法调用完成之后的代码的机会。但是很有可能由底层操作系统意外唤醒,所以必须要把wait()写在while循环中,做判断


四.锁:

1.synchronized:对象锁

1)如果JAVA程序中synchronized修饰明确指定了对象的参数,那么锁定与解锁的对象就是此对象;如果没有明确指定,那就看修饰的是实例方法还是类方法,去取对应对象的实例或Class对象来作为锁对象。最后锁的都是对象

2)当释放锁时,对共享变量的修改会从CPU缓存中直接写回到主存中。当锁被获取时,CPU缓存中的内容被置为无效的状态,从主存中重新读取共享变量的值。

2.Lock:

1) tryLock:看是否能获取锁:

2)读写锁: 当读操作比写操作多时

a. ReadWriteLock:其实是两个锁,一个是读取操作的共享锁,一个是写入操作的排他锁。多个线程可以同时读。但是有一个线程写时,别的线程不能进行其它操作

b.代码例子:

ReadWriteLock lock = new ReentrantReadWriteLock();  
Lock read = lock.readLock();       //用于get方法
Lock write = lock.writeLock();     //用于set方法
c.一般将unlock放在finally中
write.lock();  
try {  
	...				
} catch (Exception e) {  

} finally {  
	write.unlock();  
}  
3)避免线程饥饿:一直等待获得不到锁。ReentrantLock 公平锁按等待时间长短获取锁。

a.锁代码块,一个ReentrantLock对象一次只能被一个线程占有

b.代码

Lock lock = new ReentrantLock();
public void getInWhenLock(){
   lock.lock();
   try {
      ...
   }finally {
       lock.unlock();
   }
}


五.锁的数据结构:

1. ConcurrentHashMap: 

1)这些线程可以分别在不同的部分同时进行更新操作而不会相互影响

2) 提升性能的因素: ConcurrentHashMap实现类会根据这个估计的线程数把内部的空间划分为对应数量的部分。

a.map对象中可能包含的集合数目,可以节约调整大小的操作

b.同时进行更新的线程数。

3)通过迭代器可以访问在迭代器创建时集合中包含的元素,但是不一定可以反映出迭代器创建之后散列表所发生的变化

2. BlockingQueue: 线程安全的阻塞式队列,当队列已满时,向队列中添加数据的防范会阻塞当前线程。当队列为空时,从队列中获取数据的方法会阻塞当前线程(put/take),非阻塞式(offer/poll)

3.CopyOnWriteArrayList: 所有对列表的更新操作都会重新创建一个底层数组的副本,并使用这个副本来存储数据。对列表的更新操作是加锁的,而读取操作是不加锁的。通过复制方式避免了可能差生的竞争。

1)所有对列表的更新操作都会重新创建一个底层数组的副本,并使用这个副本来存储数据。所有对列表的更新操作都会重新创建一个底层数组的副本,并使用这个副本来存储数据。

2)iterator方法的迭代器只反映迭代器创建时的列表状态。


六.线程池:

1.当线程较多时,一般做法是创建一个线程池来进行统一管理,同时降低重复创建线程的开销。

2.如果需要获取一个线程异步执行任务的结果,让线程实现Callable接口的call方法,该方法会抛出异常以及有一个Future对象的返回值

3.Future接口简化了任务的异步执行。调用Future接口的get方法可以获取异步任务的结果,如果任务没有执行完,那么调用get方法的线程会处于等待状态,直到任务完成或被取消。如果希望取消一个任务的执行,那么可以调用cancel方法。

4.有些任务不在提交之后就立即执行,而是需要等待一段时间。Delayed接口用来生命任务在调度方式上的这种需求。getDelay方法用来返回当前剩余的延迟时间。

5. ExecutorService:

1)对任务的管理,使用ExecutorService接口的submit方法可以把Callable接口和Runnable接口的实现对象作为任务来提交,得到一个Future接口的实现对象作为返回值。通过该Future接口的实现对象可以获取任务的执行结果或者取消任务。

2) 批量集合处理: 通过invokeAll和invokeAny方法可以同时提交多个Callable接口的实现对象。调用invokeAll方法后,会等待所有的任务都执行完成,返回值是一个包含每个任务对应的Future接口实现对象的列表,从中可以获取每个任务的运行结果。

3)线程池关闭:

a.shutdown方法只是不再允许新任务被提交,在shutdown方法被调用前提交的任务仍然可以继续运行

6.任务时间相关的调度:ScheduledExecutorService

7.如果你向Executor提交了一个批处理任务,并且希望在它们完成后获得结果。为此你可以保存与每个任务相关联的Future,然后不断地调用timeout为零的get,来检验Future是否完成。这样做固然可以,但却相当乏味。幸运的是,还有一个更好的方法:完成服务(Completion service)。CompletionService整合了Executor和BlockingQueue的功能。你可以将Callable任务提交给它去执行,然后使用类似于队列中的take和poll方法

8.源码:

1).静态常量表示线程池状态: 运行/关闭/停止
2).private final BlockingQueue<Runnable> workQueue;  等待处理队列
3).private final HashSet<Worker> workers = new HashSet<Worker>();   存储正在运行的线程
4).private volatile ThreadFactory threadFactory; 创造新线程
5). 四种情况:  public void execute(Runnable command) 
当前线程有空闲:thread直接开始运行
无空闲: 加入等待队列
等待队列满且没超过线程最大数,创建新线程
队列满且线程数量到达最大数:抛出异常
6).有过期时间


七.序列化和反序列化:

1.对象的序列化主要有两种用途:

1) 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;
2) 在网络上传送对象的字节序列。

2.  serialVersionUID: 添加了一个字段后,由于没有显指定 serialVersionUID,那么会根据类中各种元素的特征计算出一个散列值,当然和前面保存在文件中的那个不会一样了,于是就出现了2个序列化版本号不一致的错误。因此,只要我们自己指定了serialVersionUID,就可以在序列化后,去添加一个字段,或者方法,而不会影响到后期的还原,还原后的对象照样可以使用,而且还多了方法或者属性可以用。

3.transient关键字修饰的属性不会被实例化

八.equals/hashcode:

1.equals()最准,hashcode很快,先用hashcode比较,排除不准的

2. hashcode一般用于集合元素比较

3. 在重写equals方法的同时,必须重写hashCode方法

4. 在设计hashCode方法和equals方法的时候,如果对象中的数据易变,则最好在equals方法和hashCode方法中不要依赖于该字段。否则很有可能存在map里,但是取不出来,hashcode已经变了


九. I/O
1. inputStream流无法复用,它本身不是数据,只是一次性的。所以当一个流到末尾时,其实再也读不了数据了
2.流复用的方法:
1) 用BufferedInputStream: 在流开始的地方进行标记,当一个接收者读完流所有的内容以后,再进行重置。

2)  直接读到一个字节数组中,然后传递这个字节数组

3.处理文本类型:字符流Reader/Writer:  一个典型的应用场景是把InputStream对象中的内容转化成一个String类型的字符串。用InputStreamReader的readLine方法


十.对象生命周期:
1.初始化顺序: 静态代码块和静态域(按出现顺序)之后是父类,之后是子类,先实例域的值,后构造方法
2.不要在父类中调用子类重写的方法


十一. Java虚拟机:

A.jvm内存模型:

1. Java栈: 逻辑单位,存储对象引用,变量值,基本数据类型(长度固定)

2. Java堆: 年轻代(伊甸区/幸存区1/幸存区2),老年代(可变对象长度),8的整数倍byte。

1) full gc整个堆扫

2) minor gc只是年轻代

3) 因为大部分对象都是从Eden区开始的,同时Eden区不会分配的很大,所以Eden区的GC会频繁进行。因而,一般在这里需要使用速度快、效率高的算法,使Eden去能尽快空闲出来。

4)年老代有一个"card table",记录年老代指向新生代的对象引用。新生代回收时只需要查询年老代该table就可以。加快速度

5) 大对象,长期存活的对象进入老年代

3. 方法区: 静态变量,常量(字符串)

B.基本回收算法:

按策略:

1.引用计数: 有引用加一,删除引用减一。回收引用为0的对象。无法回收循环引用对象

2.复制: 需要两倍空间,一个满了将正在使用的对象复制到另一个空间。不会出现碎片问题

3.标记清除: 首先由根节点开始,标记被引用对象,然后回收未被引用的。会暂停以及产生内存碎片

4.标记清理: 首先由根节点标记所有被引用对象。然后清除未标记对象,并压缩在一起


按线程:

1.串行: 单CPU

2.并行:  多线程收集,需要暂停运行环境。堆空间大的时候,回收的时间也变长。适用于对吞吐量有要求的。比如后台计算程序

-XX:+UseParallelGC.打开。

-XX:ParallelGCThreads=<N>设置并行垃圾回收的线程数。此值可以设置与机器处理器数量相等。

-XX:MaxGCPauseMillis=<N>指定。<N>为毫秒.如果指定了此值的话,堆大小和垃圾回收相关参数会进行调整以达到指定值。设定此值可能会减少应用的吞吐量。

吞吐量:吞吐量为垃圾回收时间与非垃圾回收时间的比值,通过 -XX:GCTimeRatio=<N>来设定,公式为1/(1+N)。

3.并发:  不停顿。可以保证大部分工作都并发进行(应用不停止)。会产生浮动垃圾,必须要清理完之前保证老年代没有满。对响应时间有要求的。Web服务器

XX:+UseConcMarkSweepGC打开。

-XX:CMSInitiatingOccupancyFraction=<N>指定还有多少剩余堆时开始执行并发收集。并发收集器一般需要20%的预留空间用于这些浮动垃圾。


C. 内存泄漏几种情况:

1. Java heap space: 年老代满了。找到回收后残余对象,然后分析

2. PermGen space: 持久代满了。 Java大量反射导致class文件太多

D.常用调优参数:

1) -xx:NewRatio: 指定新生代和老年代大小比例.基本上是新:老=1:2=3:8


E. 查询命令:

1.  dump内存快照 /usr/java/jdk1.6.0_29/bin/./jmap -dump:format=b,file=/dev/shm/dump.bin -F 18188

2. 查看堆内存对象数量,大小: jmap -histo pid

3. 查看堆使用情况: jmap -heap pid

4.查看死锁: jstack pid


十二.网站调优:

1.减少HTTP请求:
   a. 用CSS Sprites产生一张图片,而非分开的图片,这样只用请求1次而非多次。一个图片关联多个url。通过background-position属性指定CSS偏移量。 且合并图片比分离图片的总和要少(减少颜色表,格式信息),降低下载量
   b.合并脚本和样式表(JS/CSS):合并成一个文件,但不要产生大量的组合

2.使用内容发布网络(CDN):

  a.使web服务器离用户更近,可以减少http请求时间:用于发布静态内容,图片,脚本,样式表和Flash。跟地理位置有关

3.压缩组件:
 a.减少HTTP响应的大小来减少响应时间:Accept - Encoding: gzip
 b.如果有代理服务器,需要Vary: Accept - Encoding,这样代理服务器就可以根据Accept - Encoding返回不同的内容(否则会出错),因为存在支持/不支持gzip的页面

4.精简JS:
a.减少空格等

十三.HTTP

1.本地浏览器输入域名回车,去DNS服务器找到对应的IP,浏览器向这个IP地址发送请求,路由器根据IP决定下一步数据流向

2.Domain
1)IP的数字唯一确定服务器或互联网上的电脑
2)路由器有简单查找表,根据IP决定数据流动方向
3)我们的电脑上没有能穷举所有IP的数据库来找到相应的域名,所以请求DNS服务器转换
4)DNS服务器是一个层级结构,如果你最近的DNS不知道这个域名,他会递归询问上一层,直到知道。某些服务器知道谁知道,然后去找那个服务器,之后最近的DNS服务器缓存,下次就不用这样了

5)找注册商买域名,一般是按年卖的,买完之后告诉注册商你的DNS服务器是什么(让大家可以访问到你),去网络主机公司买网络主机使用权(他们会告诉你DNS服务器是哪个),
然后注册商会告诉DNS服务器你在用谁的DNS服务器,这样就可以通过那个服务器找到你的网站了。
6)一旦知道谁是你域名的DNS服务器,就开始创建DNS记录(NS), 你的域名是那个
  MX代表邮件交换:说明你邮件服务器IP地址是什么,无需使用同一台物理服务器作为电邮服务器,无需使用你网站同样的IP地址。编号n0,所以一旦有人试图给你域名上的人发邮件,他们的电脑会询问你邮件服务器的IP,然后根据编号发邮件,直到有响应。或者直接连上Google的免费,请求你的DNS,然后根据MX转到Google
  A:将域名映射到IP
  C:将一个域名映射到另一个域名,地址的别名,不用关心ip变化,但这个会带来延迟(多找ip一次)
7)先告诉世界你的域名存在何处,先建立A记录(需要告诉网站所在服务器的ip),之后如果想把邮件服务器外包给google,需要建立一个C记录(记录域名而不是ip,因为如果
google的ip变了,就不work了,但域名不会变),所以这时返回的是google的域名,浏览器根据这个域名再去查ip
8)当多个网站都在一个server上时(ip唯一),server根据浏览器http头的host行得到请求的url,然后区分到底是server上哪个网站,这样就不会串
9)SSL会将http加密,这样服务器就不知道了host,所以需要单独唯一的IP地址
3.HTTP: 客户端browser和服务器交流的方式
1)无状态:单个请求就结束了,不会维持服务器与客户端的连接,断网了还是可以显示内容
4.将路径硬编码的话,只能在自己机器运行,移到别的服务器上就不行了
5.VPS:虚拟专用服务器。商家可以用虚拟机软件创造一台计算机,多个服务器运行的假象,8核可以模拟8台虚拟单核电脑,每台占1/8内存。这个虚拟的就是你的了,可以改
变配置等
6.大文件下载传输需要考虑带宽
7.与远程服务器交互:Linux --> SSH/ WINDOWS --->PUTTY
8.没有加密JS一说,因为只有解析才能运行


十四:服务器相关:

1.一台服务器可以监听多个ip地址,所以一个服务器崩溃了,用另一个服务器监听它的ip,接收请求
2.一般url,80端口是默认值
3.告诉服务器监听哪个IP+ DNS(A/C record)
4.HTTPd.conf 配置网络服务器相关信息,例如根目录等(不公开的类库文件不放在那个底下,这样让用户访问不了,只开放可以公开的文件).还可以重定向(google.com-->www.google.com)RewriteCond 例如移动端新闻与固定端新闻
5.DNS有TTL这个概念,生存时间,多长时间内A/C record是有效的 管理员决定30/60/1day
6.更改服务器提供商,需要改变IP时,有一个副本,然后暂时定向302到副本,等大家的DNS都存好后,再改原的DNS
7.共享网络主机:网络服务器不能让用户以根来运行,恶意,复制删除。也不能让他们用一个账户,这样他们可以互相复制。suPHP-网络服务器--规定某个文件夹下只能以某
用户执行(自己),对于大网站,这样切换用户会有效率问题

8.session:http无状态,session用来保持状态,维持客户端与服务器的联系。是个关联数组或哈希表, get/post是表单内容,session是自己放的
9.cookie: 登录的象征,别人拿到了就可以以你的帐号登录(移动上网,ip会变,所以无法用ip确定),里面是一个字符串。 解决办法,用SSL加密,但是就有开销。Facebook
不用,而且需要唯一ip(hostname会被加密)。
10.每次用户请求,先看它有没有cookie,有,看里面有没有seesionID,有,去找到对应的值,作为session告诉开发者(服务器把session存在哪?)
11.办法证书证明该网站是合法的,但是因为要多去访问一遍验证机构,所以很少人会这么做
12.系统判断该用户是否已经登录,每个页面都要判断


十五.安全
1.共享服务器; 用自己的用户名运行,否则被别人看见。suPHP
2.cookie:cookie里会存一个大的随机数,与之对应的是访问的服务器里也有,每次请求传输这个随机数,服务器进行验证。如果被别人知道这个随机数,那么别人也可以看到你的内容。
3.session劫持:用户在无线网找到这个数字,然后去服务器端冒充你。数据包嗅探
4.咖啡厅,用NAT,整个咖啡厅一个IP
5.S:WPA2:保证你与无线接入点之间的报文是加密的,但是无线接入点之后的英特网, 还是明文,解决一部分问题
6.S:Force-TLS: 火狐插件,每次请求时只允许https的url
7.S:VPN:在客户端与服务器之间建立安全连接。 不过会将自身作为默认网关或者路由器,所有的通信每次都得先经过它再去别的地方。而且那个网站与外界网站的连接
依旧不安全。 SSL只有客户端与服务器知道密钥,所以除非物理接入服务器,否则不行。所以是防范附近的攻击
8.NAT:网络地址转换,让多台内部电脑共享一个IP地址。对外只有一个公有IP,对内,不同的私有IP。如果端口号相同,NAT储存的是自己这边随机的端口号
9.SSL:需要SSL证书。首先先有一个公用的密钥和私有的密钥。当别人请求你的网站时,客户端与服务器会告诉他公有密钥
10.DLF:因为没见过,所以只能用公有密钥。A与B各选一个随机数,然后再共选一个质数g,只要不知道是A与B,这些数被知道没关系。将运算结果传给对方,因为两个随机数
没动,所以保证自己的随机数与对方传过来的运算运算结果一样。这个作为共享密钥
11.SQL注入:特殊字符用转义符号表示
12.浏览器的同源策略:来自A的url无法向另一个域名B发送Ajax请求
13.跨站请求伪造攻击:同时登录网站A与B,B中正好也有A的LINK.你点了一下,他会模仿你去A中操作一些你不知道的操作。解决办法:做一个确认页面/用POST。GET容易被模仿
14.XSS:点击请求,然后那个get请求有一个script,网站发回来,这样浏览器以为是网站的,所以可以执行那段JS。需要转义就能避免
打印机不在哈弗,连VPN连不上打印机



十六.B树/B+树: 多叉搜索树

1.B树: 

1)关键字集合分布在整颗树中

2)其搜索性能等价于在关键字全集内做一次二分查找

3)从根结点开始,对结点内的关键字(有序)序列进行二分查找,如果命中则结束,否则进入查询关键字所属范围的儿子结点;重复,直到所对应的儿子指针为空,或已经是叶子结点

4)所有叶子结点位于同一层

5)由于M/2的限制,在插入结点时,如果结点已满,需要将结点分裂为两个各占M/2的结点;删除结点时,需将两个不足M/2的兄弟结点合并

6)B树中的每个结点根据实际情况可以包含大量的关键字信息和分支(当然是不能超过磁盘块的大小,根据磁盘驱动(disk drives)的不同,一般块的大小在1k~4k左右);这样树的深度降低了,这就意味着查找一个元素只要很少结点从外存磁盘中读入内存,很快访问到要查找的数据。

2.B+树:

1)叶子结点增加链表指针,所有关键字都在叶子结点中出现,非叶子结点作为叶子结点的索引;B+树总是到叶子结点才命中
3.为什么说B+-tree比B 树更适合实际应用中操作系统的文件索引和数据库索引

1)B+-tree的磁盘读写代价更低

B+-tree的内部结点并没有指向关键字具体信息的指针。因此其内部结点相对B 树更小。如果把所有同一内部结点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。一次性读入内存中的需要查找的关键字也就越多。相对来说IO读写次数也就降低了。
2) B+-tree的查询效率更加稳定
由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。

4.B树操作: m阶意味着每个节点的个数为 m/2 -- m-1

1) 插入操作: 

a.检索到适当位置

b.插入节点,若结点个数大于m,分裂成两个节点,中间结点到上一层,如果上一层节点接收中间结点后超了,继续分裂

2)删除操作:

a.检索到适当位置

b.如删除非叶子节点,用右子树最小的节点(即右子树最左边的叶子节点)代替删除的节点

  如删除后的节点的关键字个数不小于m/2,删完不用动

  如删除后的节点的关键字个数为m/2-1个,而左边/右边的兄弟节点大于m/2-1,最大的关键字顺时针/最小的关键字逆时针旋转

                                                                             而左右兄弟节点关键字个数均为m/2-1的情况下,父节点下来一个关键字到该节点

5.二叉查找树:左边都小于父节点,右边都大于父节点。插入只能插入到叶子节点

6.平衡条件的二叉查找树(AVL): 每个节点的左子树和右子树最多相差一的二叉查找树

  1)很显然,平衡二叉树的优势在于不会出现普通二叉查找树的最差情况。其查找的时间复杂度为O(logN)。

  2) 当在二叉排序树中插入一个节点时,首先检查是否因插入而破坏了平衡,若 破坏,则找出其中的最小不平衡二叉树,在保持二叉排序树特性的情况下,调整最小不平衡子树中节点之间的关系,以达 到新的平衡。所谓最小不平衡子树 指离插入节点最近且以平衡因子的绝对值大于1的节点作为根的子树。 子树4层

  3)如果不平衡出现在子树的外侧节点,连着根左旋或者右旋,不平衡节点挂在下来的根节点下

  4))如果不平衡出现在子树内侧节点,两层节点旋转,先第二层左旋或者右旋,不平衡节点挂在下来的第二层节点下,然后根节点右旋或者左旋

   5)局限:在大数据量查找环境下(比如说系统磁盘里的文件目录,数据库中的记录查询 等),所有的二叉查找树结构(BST、AVL、RBT)都不合适。如此大规模的数据量(几G数据),全部组织成平衡二叉树放在内存中是不可能做到的。那么把这棵树放在磁盘中吧。问题就来了:假如构造的平衡二叉树深度有1W层。那么从根节点出发到叶子节点很可能就需要1W次的硬盘IO读写。大家都知道,硬盘的机械部件读写数据的速度远远赶不上纯电子媒体的内存。 查找效率在IO读写过程中将会付出巨大的代价。在大规模数据查询这样一个实际应用背景下,平衡二叉树的效率就很成问题了。

  6) 插入时-->递归查找(因为每层查找规则都一样,所以递归)-->往左往右-->插入了不平衡了三种情况,一种要旋转-->新方法:左旋的话是单左旋还是需要回旋(插在左子树还是右子树)--->新方法:左旋

7.红黑树:

1)优点: AVL是严格平衡树,因此在增加或者删除节点的时候,根据不同情况,旋转的次数比红黑树要多;
红黑是用非严格的平衡来换取增删节点时候旋转次数的降低;
所以简单说,如果你的应用中,搜索的次数远远大于插入和删除,那么选择AVL,如果搜索,插入删除次数几乎差不多,应该选择RB

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值