反射在Java中有运行和编译两种状态。指在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象。
获取class对象的方法来使用反射
通过对象Object的getClass()方法、通过类的静态class属性、通过Class类的静态方法forName(String className)
method.setAccessible(true);//获取私有权限
//可以绕过私有的安全检查,所以我们就可以调用类的私有方法啦。
equals()、toString()、finalize()、getClass()、hashCode()、clone()、wait()、notify()、notifyAll()
当线程执行wait()方法时候,会释放当前的锁,然后让出CPU,进入等待状态。
notify/notifyAll() 的执行只是唤醒沉睡的线程,而不会立即释放锁,锁的释放要看代码块的具体执行情况。所以在编程中,尽量在使用了notify/notifyAll() 后立即退出临界区,以唤醒其他线程让其获得锁。
notify 和wait 的顺序不能错,如果A线程先执行notify方法,B线程在执行wait方法,那么B线程是无法被唤醒的。
区别
notify()是只唤醒一个等待的线程,如果存在多个线程的情况,由操作系统对多线程管理来决定。notifyAll 会唤醒所有等待(对象的)线程,尽管哪一个线程将会第一个处理取决于操作系统的实现。如果当前情况下有多个线程需要被唤醒,推荐使用notifyAll 方法。比如在生产者-消费者里面的使用,每次都需要唤醒所有的消费者或是生产者,以判断程序是否可以继续往下执行。在多线程中如果根据条件决定是否执行,使用while不用if。因为while 具有判断条件成立后在执行。
不可改变的都是一定是线程安全的,因为只有一种状态,被final修饰,对象创建后不可修改。
cookie数据存放在客户的浏览器上,session数据放在服务器上。
cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗,考虑到安全应当使用session。
session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能考虑到减轻服务器性能方面,应当使用cookie。
单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。
所以个人建议:将登陆信息等重要信息存放为session。其他信息如果需要保留,可以放在cookie中
List,Set,Map都是接口,但是List,Set是继承Collection接口,Map为单独的接口
Set下有HashSet,LinkedHashSet,TreeSet
List下有ArrayList,Vector,LinkedList
Map下有Hashtable,LinkedHashMap,HashMap,TreeMap
Collection接口下还有个Queue接口,有PriorityQueue类
注意:
Queue接口与List、Set同一级别,都是继承了Collection接口。
看图你会发现,LinkedList既可以实现Queue接口,也可以实现List接口.只不过呢, LinkedList实现了Queue接口。Queue接口窄化了对LinkedList的方法的访问权限(即在方法中的参数类型如果是Queue时,就完全只能访问Queue接口所定义的方法 了,而不能直接访问 LinkedList的非Queue的方法),以使得只有恰当的方法才可以使用。
SortedSet是个接口,它里面的(只有TreeSet这一个实现可用)中的元素一定是有序的。
— List 有序,可重复
ArrayList
优点: 底层数据结构是数组,查询快,增删慢。
缺点: 线程不安全,效率高
LinkedList
优点: 底层数据结构是链表,查询慢,增删快。
缺点: 线程不安全,效率高
Vector
优点: 底层数据结构是数组,查询快,增删慢。
缺点: 线程安全,效率低
Set 无序,唯一
HashSet
底层数据结构是哈希表。(无序,唯一)
如何来保证元素唯一性?
1.依赖两个方法:hashCode()和equals()
LinkedHashSet
底层数据结构是链表和哈希表。(FIFO插入有序,唯一)
1.由链表保证元素有序
2.由哈希表保证元素唯一
TreeSet
底层数据结构是红黑树。(唯一,有序)
如何选择使用那个集合框架
if(唯一?){
使用Set
if(排序?){
TreeSet或LinkedHashSet
}else{
HashSet(默认)
}
}else{
List
if(线程安全吗?){
Vector
}else{
ArrayList:查询速度快,增删慢
LinkedList: 查询速度慢,增删快
}
}
1. **线程安全**,HashTable 是同步的也是线程安全的,而HashMap是不同步的,也是线程不安全。
2. **效率**。HashTable的执行效率要比HashMap低一些,但是如果对同步性和线程问题没要求的情况下推荐使用HashMap
3. **Null值**。在HashTable 中是不允许使用Null值,但是在Hash Map中Key和Value 都可以为Null。
4. **父类不同**。HashTable 的父类是Dictionary而HashMap的父类是AbstractMap。
ConcurrentHashMap 的加锁粒度要比HashTable更细一点。将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。(分段锁)
Segment继承了ReentrantLock,所以它就是一种可重入锁(ReentrantLock)。在ConcurrentHashMap,一个Segment就是一个子哈希表,Segment里维护了一个HashEntry数组,并发环境下,对于不同Segment的数据进行操作是不用考虑锁竞争的。(就按默认的ConcurrentLeve为16来讲,理论上就允许16个线程并发执行)
具体解释和总结,很棒!
https://www.baidu.com/link?url=f3UWlcJcGkMogsmnsGc_Hrgbnh1Qfkdl8ojdzZM73lZIpjvOAXK0n8fCRHY-nKAWyhbe0wrA0TdJA1qEkmBgaa&wd=&eqid=bfc65ebf00036d59000000065b9a2f25
CopyOnWriteArraySet是基于CopyOnWriteArrayList实现的,只有add的方法稍微有些不同,因为CopyOnWriteArraySet是Set也就是不能有重复的元素,故在CopyOnWriteArraySet中用了addIfAbsent(e)这样的方法。
写入时复制一个数组,对复制后产生的新数组进行操作,而旧的数组不会有影响,所以旧的数组可以依旧就行读取(可以看出来,读的时候如果有新的数据正在写是无法实时的读取到的,有延时,得等新数据写完以后,然后才可以读到新的数据)
多个线程同时去写,多线程写的时候会Copy出N个副本出来,那么可能内存花销很大,所以用一个重入显式锁ReetrantLock锁住,一次只能一个线程去添加。
读取时,不用进行线程同步。
可重入就意味着:线程可以进入任何一个它已经拥有的锁所同步着的代码块
java中常用的可重入锁
synchronized
java.util.concurrent.locks.ReentrantLock
解释:ThreadLocal 是一个线程的内部存储类,主要方法有set和get,可以用于介于对现在多线程的情况下出现线程安全的问题。通过get和set方法就可以得到当前线程对应的值。
实际上是ThreadLocal的静态内部类ThreadLocalMap为每个Thread都维护了一个数组table,ThreadLocal确定了一个数组下标,而这个下标就是value存储的对应位置。。
public void set(T value) {
//获取线程t,
Thread t = Thread.currentThread();
//在ThreadLocalMap中 用线程t作为key 要存储的对象作为vlaue
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
public T get() {
//获取线程t,
Thread t = Thread.currentThread();
//获取线程t的map
ThreadLocalMap map = getMap(t);
if (map != null) {
//判断map是否存在
ThreadLocalMap.Entry e = map.getEntry(this); //判断
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
作用:
Java只支持单继承,所以使用Thread 有局限性,Runnable接口可以实现多个。
在实现Runable接口的时候调用Thread的Thread(Runnable run)或者Thread(Runnable run, String name)构造方法创建进程时,使用同一个Runnable实例,所以建立的多线程的实例变量是可以共享的。
public class TraditionalThread {
public static void main(String[] args) {
Runnable runnable = new Runnable() {
public void run() {
System.out.println("runnable :" + Thread.currentThread().getName());
}
};
Thread thread = new Thread(runnable) {
public void run() {
System.out.println("thread :" + Thread.currentThread().getName());
}
};
thread.start();
}
}
————————————————
结果
thread:Thread-0
当我们调用thread.start()方法时,虚拟机会自动去调用其run()方法。而当run()方法被覆盖时会调用我们重写的方法,便调用不到runnable .run()方法。这点我们可以从Thread类的源代码中看到。
新建,就绪,运行,阻塞(等待阻塞,同步阻塞,其他阻塞),死亡
public ThreadPoolExecutor(int corePoolSize, //核心线程数 ,规定可运行的线程数
int maximumPoolSize, //最大线程数
long keepAliveTime, //存活时间
TimeUnit unit, //存活时间的时间单位
BlockingQueue<Runnable> workQueue, //阻塞队列(用来保存等待被执行的任务) 存放任务的队列
ThreadFactory threadFactory, //线程工厂,主要用来创建线程
RejectedExecutionHandler handler //表示当拒绝处理任务时的策略,有以下四种取值
)
注: 当线程池的饱和策略,当阻塞队列满了,且没有空闲的工作线程,如果继续提交任务,必须采取一种策略处理该任务,线程池提供了4种策略:
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
当然也可以根据应用场景实现RejectedExecutionHandler接口,自定义饱和策略,如记录日志或持久化存储不能处理的任务。
Executors类有一些静态方法可以创建线程池Executor。
newFixedThreadPool:创建固定长度的线程池
newCachedThreadPool:创建一个可缓存的线程池,自动回收空闲线程,自动扩展新线程
newSingleThreadExecutor:创建一个单线程来执行任务
newScheduledThreadPool:创建一个固定长度的线程池,可演示或定时执行任务
死锁是指两个或两个以上的进程,因为资源的抢夺,而造成相互等待的情况,如果在无外力的情况下,他们无法推进下去,需要满足足四个条件:互斥条件,不剥夺条件,请求和保持条件,循环等待条件。
程序计数器:当前线程所执行的字节码的行号指示器,通过改变计数器的值来选取下一条需要执行的字节码指令
java本地栈:本地栈为虚拟机使用到的Native方法服务,即被Native修饰的方法。
java虚拟机栈:虚拟机栈为java方法服务,存储局部变量表,操作数栈,动态链接,方法出口。局部变量表存放各种基本数据类型,对象引用。
java堆区: 存放几乎所有的对象实例,被所有线程所共享的内存区域,也是Java虚拟机所管理的最大一块内存。垃圾回收机制主要是在着,也被称做“GC堆”。所以 Java 堆中还可以细分为:新生代和老年代;再细致一点的有 Eden 空间、From Survivor 空间、To Survivor 空间等。从内存分配的角度来看,线程共享的 Java 堆中可能划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB)。
元数据区(方法区):同样是被所有线程共享的区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
JDK8的时候,永久代退出历史舞台,把JDK 7中永久代还剩余的内容(主要是类型信息)全部移到元空间中,方法区由本地内存的元空间实现。
元数据区代替永久代,以前永久代的字符串常量转移到堆内存中其他内容移至元空间,元空间直接在本地内存分配。
引用计数:每当有需要引用对象的地方就对对象的计数+1,引用失效则-1,
优点:实现简单、判断效率高。 缺点:难以解决对象之间的循环引用问题。
可达性分析算法:从一系列“GC Roots”对象作为起始点,从这些节点向下搜索,搜索走过的路径叫引用链。从GC Roots到该对象不可达,则该对象不可用。
Java中GC Roots包括以下几种对象:
a.虚拟机栈(帧栈中的本地变量表)中引用的对象
b.方法区中静态属性引用的对象
c.方法区中常量引用的对象
d.本地方法栈中JNI引用的对象
Serial(单线程)
Serial GC,它是最古老的垃圾收集器,“Serial”体现在其收集工作是单线程的,并且在进行垃圾收集过程中,会进入臭名昭著的“Stop-The-World”状态。当然,其单线程设计也意味着精简的 GC 实现,无需维护复杂的数据结构,初始化也简单,所以一直是 Client 模式下 JVM 的默认选项。
从年代的角度,通常将其老年代实现单独称作 Serial Old,它采用了标记 - 整理(Mark-Compact)算法,区别于新生代的复制算法。
Serial GC 的对应 JVM 参数是:
-XX:+UseSerialGC
ParNew(多线程)
很明显是个新生代 GC 实现,它实际是 Serial GC 的多线程版本,最常见的应用场景是配合老年代的 CMS GC 工作,下面是对应参数:
-XX:+UseConcMarkSweepGC -XX:+UseParNewGC
CMS
基于标记 - 清除(Mark-Sweep)算法,设计目标是尽量减少停顿时间,这一点对于 Web 等反应时间敏感的应用非常重要,一直到今天,仍然有很多系统使用 CMS GC。但是,CMS 采用的标记 - 清除算法,存在着内存碎片化问题,所以难以避免在长时间运行等情况下发生 full GC,导致恶劣的停顿。另外,既然强调了并发(Concurrent),CMS 会占用更多 CPU 资源,并和用户线程争抢。
Parrallel GC(并行),
在早期 JDK 8 等版本中,它是 **server** 模式 JVM 的默认 GC 选择,也被称作是吞吐量优先的 GC。它的算法和 Serial GC 比较相似,尽管实现要复杂的多,其特点是新生代和老年代 GC 都是并行进行的,在常见的服务器环境中更加高效。开启选项是:
-XX:+UseParallelGC
G1 GC
这是一种兼顾吞吐量和停顿时间的 GC 实现,是 Oracle JDK 9 以后的默认 GC 选项。G1 可以直观的设定停顿时间的目标,相比于 CMS GC,G1 未必能做到 CMS 在最好情况下的延时停顿,但是最差情况要好很多。
G1 GC 仍然存在着年代的概念,但是其内存结构并不是简单的条带式划分,而是类似棋盘的一个个 region。Region 之间是复制算法,但整体上实际可看作是标记 - 整理(Mark-Compact)算法,可以有效地避免内存碎片,尤其是当 Java 堆非常大的时候,G1 的优势更加明显。
G1 吞吐量和停顿表现都非常不错,并且仍然在不断地完善,与此同时 CMS 已经在 JDK 9 中被标记为废弃(deprecated),所以 G1 GC 值得你深入掌握。
性能调优建议:
针对JVM堆的设置,一般可以通过-Xms -Xmx限定其最小、最大值,为了防止垃圾收集器在最小、最大之间收缩堆而产生额外的时间,通常把最大、最小设置为相同的值;
年轻代和年老代将根据默认的比例(1:2)分配堆内存, 可以通过调整二者之间的比率NewRadio来调整二者之间的大小,也可以针对回收代。比如年轻代,通过 -XX:newSize -XX:MaxNewSize来设置其绝对大小。同样,为了防止年轻代的堆收缩,我们通常会把-XX:newSize -XX:MaxNewSize设置为同样大小。
年轻代和年老代设置多大才算合理
1)更大的年轻代必然导致更小的年老代,大的年轻代会延长普通GC的周期,但会增加每次GC的时间;小的年老代会导致更频繁的Full GC
2)更小的年轻代必然导致更大年老代,小的年轻代会导致普通GC很频繁,但每次的GC时间会更短;大的年老代会减少Full GC的频率
如何选择应该依赖应用程序对象生命周期的分布情况: 如果应用存在大量的临时对象,应该选择更大的年轻代;如果存在相对较多的持久对象,年老代应该适当增大。但很多应用都没有这样明显的特性。
在抉择时应该根 据以下两点:
(1)本着Full GC尽量少的原则,让年老代尽量缓存常用对象,JVM的默认比例1:2也是这个道理 。
(2)通过观察应用一段时间,看其他在峰值时年老代会占多少内存,在不影响Full GC的前提下,根据实际情况加大年轻代,比如可以把比例控制在1:1。但应该给年老代至少预留1/3的增长空间。
在配置较好的机器上(比如多核、大内存),可以为年老代选择并行收集算法: -XX:+UseParallelOldGC 。
线程堆栈的设置:每个线程默认会开启1M的堆栈,用于存放栈帧、调用参数、局部变量等,对大多数应用而言这个默认值太了,一般256K就足用。
理论上,在内存不变的情况下,减少每个线程的堆栈,可以产生更多的线程,但这实际上还受限于操作系统。
Java中的所有类,必须被装载到jvm中才能运行,这个装载工作是由jvm中的类装载器完成的,类装载器所做的工作实质是把类文件从硬盘读取到内存中,JVM在加载类的时候,都是通过ClassLoader的loadClass()方法来加载class的,loadClass使用双亲委派模式。
1、防止重复加载同一个.class
。通过委托去向上面问一问,加载过了,就不用再加载一遍。保证数据安全。
2、保证核心.class
不能被篡改。通过委托方式,不会去篡改核心.clas
,即使篡改也不会去加载,即使加载也不会是同一个.class
对象了。不同的加载器加载同一个.class
也不是同一个Class
对象。这样保证了Class
执行安全
volatile是一种稍弱的同步机制,把变量声明为volatile类型后,JVM会注意到这个变量是共享的,不会进行重排序,也不会缓存到寄存器等不可见的地方,所以读取volatile类型的变量会返回最新写入的值
它在多处理器开发中保证了共享变量的“可见性”。可见性的意思是当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。
synchronized可以保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区,同时它还可以保证共享变量的内存可见性
synchronized
方法时,由于对象锁的存在,所有加synchronized
的方法都不能被访问(前提是在多个线程调用的是同一个对象实例中的方法)synchronized
,此类的所有的实例化对象在调用该方法时,共用同一把锁,称之为类锁。所谓的显示和隐式就是在使用的时候,使用者要不要手动写代码去获取锁和释放锁的操作。
读写锁:允许多个读操作同时进行,但每次只允许一个写操作。
读写锁的机制:
* "读-读"不互斥
* "读-写"互斥
* "写-写"互斥
即在任何时候必须保证:
* 只有一个线程在写入;
* 线程正在读取的时候,写入操作等待;
* 线程正在写入的时候,其他线程的写入操作和读取操作都要等待
互斥锁:一个线程获得资源的使用权后就会将该资源加锁,使用完后会将其解锁。所以不能读/读,不能写/写,更不能读/写
饿汉模式
/*
*饿汉式
*类加载到内存后,就实例化一个单例,Jvm保证线程安全
*缺点:不管是否使用到了,类装载时就完成实例化
*(话说 你不用你装载它干啥)
*/
public class Mar01 {
private static final Mar01 INSTANCE = new Mar01();
private Mar01() {
}
public static Mar01 getInstance() {
return INSTANCE; }
public void m(){
System.out.println("m"); }
public static void main(String[] args) {
Mar01 mar01=Mar01.getInstance();
Mar01 mar02=Mar01.getInstance();
System.out.println(mar01==mar02);
}
}
懒汉模式
/*
*lazy loading 懒汉模式
* 虽然达到了按需初始化 但是出现了线程不安全的问题
*
*/
class Mar01 {
private static Mar01 INSTANCE;
private Mar01() { };
public static Mar01 getInstance() {b
if (INSTANCE == null) {// 如果线程A判断INSTANCE==null 然后 线程B执行了下面代码 创建了Mar01 然后A判断通过后 会创建2个Mar01 造成线程不安全问题
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new Mar01();
}
return INSTANCE;
}
public void m() { System.out.println("m"); }
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(() -> {
System.out.println(Mar01.getInstance().hashCode());
}).start();
}
}
}
完美解决方法 静态内部方法
/*
*静态内部类
* jvm保证单例
* 加载外部类,不会加载内部类,这样可以实现懒加载
*/
class Mar01 {
private Mar01() {
};
private static class Mar01Holder {
private final static Mar01 INSTANCE=new Mar01();
}
public static Mar01 getInstance(){
return Mar01Holder.INSTANCE;
}
public void m() {
System.out.println("m"); }
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(() -> {
System.out.println(Mar01.getInstance().hashCode());
}).start();
}
}
}
1. IO 是面向流的,NIO 是面向缓冲区的。
阻塞性I/O是用户线程出现IO请求时,判断数据是否就绪,未就绪等待,用户线程阻塞,交出CPU,就绪后在执行,结束block状态。data = socket.read();
非阻塞性I/O是用户发送IO请求时,判断数据是否就绪并直接返回结果error,进行while循环 一直请求,直到准备好了,并且接收到用户请求才执行,也就是非阻塞I/O不交出CPU 从获得CPU一直到数据准备好了 完成I/O操作才释放
while(true){
data = socket.read();
if(data!= error){
处理数据
break;
}
}
多路复用I/O当用户发起I/O请求时,会有一个线程不断去轮询socket的状态,当socket真正有读写时间的时候,才会调用资源进行I/O读写操作在 Java NIO 中,是通过 selector.select()去查询每个通道是否有到达事件。
异步I/O
当用户线程发起请求,内核接收到了 asynchronous read 之后立即返回结果,如果数据准备好了就返回状态 用户线程继续执行,不阻塞用户线程,直接使用数据(当完成后将结果拷贝给用户线程)
buffer和channel
buffer是是缓冲区,channel 是通道,
一般数据都是成channel 通道开始,可以读取buffer内的数据 也可以写入数据到buffer
channel和I/O中的流比较相似,但是不同在于流是单向的 channel是双向。
直接缓冲区,通过allocateDirect() 方法分配缓冲区,将缓冲区建立在物理内存中
选择区,用来监听多个注册的信道的连接打开和数据到达,实现单线程监控多个信道。
2.
通道可以异步读写。
- 通道总是基于缓冲区Buffer来读写。
FileChannel 用于文件数据读写
DatagramChannel 主要用于DCP的读写
SocketChannel 用于TCP数据读写,主要是客户端
ServerSocketChannel 允许我们监听TCP请求,每个请求会创建会一个SocketChannel主要是服务器端
如果要从写入缓冲区 读取数据需要调用
buffer.filp()方法 将Buffer从写模式变为可读模式
package filechannel;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class FileChannelTxt {
public static void main(String args[]) throws IOException {
//1.创建一个RandomAccessFile(随机访问文件)对象,
RandomAccessFile raf=new RandomAccessFile("D:\\niodata.txt", "rw");
//通过RandomAccessFile对象的getChannel()方法。FileChannel是抽象类。
FileChannel inChannel=raf.getChannel();
//2.创建一个读数据缓冲区对象
ByteBuffer buf=ByteBuffer.allocate(48);
//3.从通道中读取数据
int bytesRead = inChannel.read(buf);
//创建一个写数据缓冲区对象
ByteBuffer buf2=ByteBuffer.allocate(48);
//写入数据
buf2.put("filechannel test".getBytes());
buf2.flip();
inChannel.write(buf);
while (bytesRead != -1) {
System.out.println("Read " + bytesRead);
//Buffer有两种模式,写模式和读模式。在写模式下调用flip()之后,Buffer从写模式变成读模式。
buf.flip();
//如果还有未读内容
while (buf.hasRemaining()) {
System.out.print((char) buf.get());
}
//清空缓存区
buf.clear();
bytesRead = inChannel.read(buf);
}
//关闭RandomAccessFile(随机访问文件)对象
raf.close();
}
}
public abstract class FileChannel
extends AbstractInterruptibleChannel
implements SeekableByteChannel, GatheringByteChannel, ScatteringByteChannel
1. 开启FileChannel
因为fileChannel 是虚拟类 无法直接实现,需要通过InputStream,OutStream及RandoAccessFile 开启。
2.从FileChannel读取数据/写入数据
创建读取缓冲区将数据读取
ByteBuffer buf=ByteBuffer.allocate(48);
inChannel.read(buf1);
创建写入缓冲区将数据写入
ByteBuffer buf2=ByteBuffer.allocate(48);
buf2.put(“filechannel test”.getBytes());//将这句话写入到文本中
buf2.flip();
inChannel.write(buf2);
3.关闭通道
channel.close();
5. SocketChannel和ServerSocketChannel
```java
客户端 开启SocketChannel
1.通过SocketChannel连接到远程服务器
2.创建读数据/写数据缓冲区对象来读取服务端数据或向服务端发送数据
3.关闭SocketChannel
//1.通过SocketChannel的open()方法创建一个SocketChannel对象
SocketChannel socketChannel = SocketChannel.open();
//2.连接到远程服务器(连接此通道的socket)
socketChannel.connect(new InetSocketAddress("127.0.0.1", 3333));
//3.创建缓冲区 读取写入 关闭
服务端
```
```
1.通过ServerSocketChannel 绑定ip地址和端口号
2.通过ServerSocketChannelImpl的accept()方法创建一个SocketChannel对象用户从客户端读/写数据
3.创建读数据/写数据缓冲区对象来读取客户端数据或向客户端发送数据
4. 关闭SocketChannel和ServerSocketChannel
.通过ServerSocketChannel 的open()方法创建一个ServerSocketChannel对象,open方法的作用:打开套接字通道
ServerSocketChannel ssc = ServerSocketChannel.open();
//2.通过ServerSocketChannel绑定ip地址和port(端口号)
ssc.socket().bind(new InetSocketAddress("127.0.0.1", 3333));
//通过ServerSocketChannelImpl的accept()方法创建一个SocketChannel对象用户从客户端读/写数据
SocketChannel socketChannel = ssc.accept();
DatagramChannel的使用
//1.通过DatagramChannel的open()方法创建一个DatagramChannel对象
DatagramChannel datagramChannel = DatagramChannel.open();
//绑定一个port(端口)
datagramChannel.bind(new InetSocketAddress(1234));
---------------------------------------
因为是无连接的 无需建立连接 只需要知道地址就可以
2.接收消息
//先创建一个缓存区对象,
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
//然后通过receive方法接收消息,这个方法返回一个SocketAddress对象,表示发送消息方的地址
channel.receive(buf);
3.发送消息:
//创建缓冲区
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
//输入要发送的内容
buf.put("datagramchannel".getBytes());
//改为读模式
buf.flip();
发送的地址 返回发送成功的字节数
int send = channel.send(buffer, new InetSocketAddress("localhost",1234));
通道之间的数据传输
Scattering Reads
Gathering Writes
“gathering write”把多个buffer的数据写入到同一个channel中,
事务的原子性,一致性,隔离性,持久性。
并发事务产生的原因
频繁作为查询条件的字段应该创建索引
经常增删改的表不创建索引
单键/组合索引的选择问题,who?(在高并发下倾向创建组合索引)
关联查询优化:小表驱动大表,大表join字段已经被索引
order by关键字优化:尽量Index方式,避免FileSort排序;尽可能在索引列上完成排序操作,遵照索引建的最佳左前缀
GROUP BY关键字优化:group by实质是先排序后进行分组,遵照索引建的最佳左前缀;where高于having,能写在where限定的条件就不要去having限定了。
普通索引:仅加速查询
唯一索引:加速查询 + 列值唯一(可以有null)
主键索引:加速查询 + 列值唯一(不可以有null)+ 表中只有一个
组合索引:多列值组成一个索引,专门用于组合搜索,其效率大于索引合并
全文索引:对文本的内容进行分词,进行搜索
注意:聚簇索引并不是一种单独的索引类型,而是一种数据存储方式,表示数据行和相邻的键值的存储在一起。数据行在磁盘的排列和索引排序保持一致。在查询的时候因为数据紧密相连的,不用从多个数据块提取,节约时间
![image-20200710153516144](https://imgconvert.csdnimg.cn/aHR0cDovL2RhZXIueGlhc2hlbmcud29yay9pbWFnZS0yMDIwMDcxMDE1MzUxNjE0NC5wbmc?x-oss-process=image/format,png)
2. BTREE类型的索引,在存储的时候是存储的Kay-value的形式在链表上有顺序的存储。
3. 为什么说B+树比B-树更适合实际应用中操作系统的文件索引和数据库索引?
1. B+树的磁盘读写代价更低
2. B+树的查询效率更加稳定
4. 在B_TREE上查找x,现将x的关键字与根结点的n个关键字di逐个比较,然后做如下处理:
- 若x.key==28,则查找成功返回;
- 若x.key<28,则沿着指针p1所指的子树继续查找;
- 若28<链表最大值,则沿着指针后面所指的子树继续查找。
from–where–group by–having–select–order by,
from:需要从哪个数据表检索数据
where:过滤表中数据的条件
group by:如何将上面过滤出的数据分组
having:对上面已经分组的数据进行过滤的条件
select:查看结果集中的哪个列,或列的计算结果
order by :按照什么样的顺序来查看返回的数据
SQL Select语句完整的执行顺序【从DBMS使用者角度】:
1、from子句组装来自不同数据源的数据;
2、where子句基于指定的条件对记录行进行筛选;
3、group by子句将数据划分为多个分组;
4、使用聚集函数进行计算;
5、使用having子句筛选分组;
6、计算所有的表达式;
7、使用order by对结果集进行排序。
Spring 如何创建对象?
利用构造函数创建对象
<bean class="com.mc.base.learn.spring.bean.Person" id="person">
<constructor-arg name="id" value="123">constructor-arg>
<constructor-arg name="name" value="LiuChunfu">constructor-arg>
bean>
通过静态方法创建对象
<bean id="person" class="com.mc.base.learn.spring.factory.PersonStaticFactory" factory-method="createPerson">bean>
通过工厂方法创建对象
<bean id="personFactory" class="com.mc.base.learn.spring.factory.PersonFactory">bean>
<bean id="person2" factory-bean="personFactory" factory-method="createInstance">bean>
三级缓存
分析getSingleton()的整个过程,Spring首先从一级缓存singletonObjects中获取。如果获取不到,并且对象正在创建中,就再从二级缓存earlySingletonObjects中获取。如果还是获取不到且允许singletonFactories通过getObject()获取,就从三级缓存singletonFactory.getObject()(三级缓存)获取,
Spring解决循环依赖的诀窍就在于singletonFactories这个三级cache。这个cache的类型是ObjectFactory
alue=“LiuChunfu”>
通过静态方法创建对象
<bean id="person" class="com.mc.base.learn.spring.factory.PersonStaticFactory" factory-method="createPerson">bean>
通过工厂方法创建对象
<bean id="personFactory" class="com.mc.base.learn.spring.factory.PersonFactory">bean>
<bean id="person2" factory-bean="personFactory" factory-method="createInstance">bean>
三级缓存
分析getSingleton()的整个过程,Spring首先从一级缓存singletonObjects中获取。如果获取不到,并且对象正在创建中,就再从二级缓存earlySingletonObjects中获取。如果还是获取不到且允许singletonFactories通过getObject()获取,就从三级缓存singletonFactory.getObject()(三级缓存)获取,
Spring解决循环依赖的诀窍就在于singletonFactories这个三级cache。这个cache的类型是ObjectFactory