work_study_plan

目录

简历自我介绍介绍一下项目,以及难点亮点科研项目Git 、SVN、MAVENGit和 SVN的区别,有哪些优势?Git 下 init 之后生成的.git文件夹是干嘛的?Maven怎么解决冲突?Maven依赖原则:冲突原因和解决JAVA语言Java 集合各种集合的简介及区别联系Hash表解决冲突的方法Hashmap中的hash(key)方法resize()的过程HashMap多线程下出现的问题ConcurrentHashMap 如何扩容?Java NIOBufferChannelSelectorSpringMVC和MyBatisSpringMVC运行流程拦截器@Resource@AutowairedSpringIOCaop事务singleton 和 prototype的作用范围(使用scope指定)MyBatis简介动态代理简介动态代理的原理 ***常用标签动态SQL事务支持和缓存机制具体问题垃圾回收机制垃圾回收算法垃圾回收的时机垃圾收集器新生代收集器老年代收集器G1收集器类加载机制虚拟机的方法调用解析分派JVM 锁优化***JVM编译过程***多线程,并发终止线程的方式***J.U.C 包volatilesychronized 原理ThreadLocalAQS(AbstractQueuedSychronizer)信号量 (Semaphore) ['seməfɔː]CountDownLatchAtomicIntegerFuturefinal 域获取线程dump文件线程的唤醒与阻塞线程池简介四种线程池Executors.newCashedThreadPool()Executors.newFixedThreadPool()Executors.newScheduledThreadPool()Executors.newSingleThreadExecutor()如果你提交任务时,线程池队列已满,这时会发生什么线程池的大小几种锁如何在两个线程之间共享数据写一段 sychronized 能产生死锁的代码异常系统迭代器和比较器Iterator和IterableComparable和ComparatorJava 基础String 相关重载和重写泛型怎么实现sychronized 修饰static方法和普通方法的区别具体问题数据结构和算法基础计算机网络http 和 https长连接和短链接HTTP/1.0、HTTP/1.1、HTTP/2.0HTTP/1.0和HTTP/1.1HTTP/1.1 和 HTTP/2.0状态码相关TCP/IP分层和OSI分层TCP/IP分层OSI 分层TCP和UDP的区别,TCP如何保持可靠性传输TCP相关重点TCP如何保证可靠传输TCP的拥塞控制慢开始和拥塞避免快重传和快恢复UDP协议session 和 cookieHttp中 Header信息操作系统进程进程之间的调度方式处理机调度的层次调度算法进程间的通信方式死锁死锁产生原因死锁产生必要条件死锁预防和避免的算法存储器管理连续行存储管理方式离散型存储管理方式页式管理方式寻址快表TLB段式管理方式寻址段、页式的区别段页式管理方式虚拟存储器置换算法数据库范式第一范式第二范式第三范式mysql为什么用B+树mysql什么情况下会触发表锁乐观锁、悲观锁读锁、写锁、表锁、行级锁数据库事务的四种隔离级别ACID事务ACID事务实现原理InnoDB和MyISAMInnoDBMyISAM比较和讨论索引的实现方式在A、B、C上建立索引,可用与不可用问题***类别连接(join)操作设计模式几种关系继承实现依赖关联聚合组合几个原则开闭原则单一职责原则依赖倒转原则迪米特法则几种单例模式优化技术前端缓存CDN后端缓存RedisNoSql : Not only Sql应用场景数据结构事务持久化底层实现之跳表基于Redis实现分布式锁和分布式任务队列负载均衡MySql优化Sql及索引数据库表结构系统配置硬件分布式和大数据负载均衡的方法一致性Hash算法HDFS文件系统的工作原理组成部分和相关概念写操作读操作Hadoop和SparkMap and ReduceHadoop的数据流(运行简要过程)容错机制SparkHadoop 和 Spark的比较开放式问题海量数据海量数据排序海量日志数据,提取出某日访问百度次数最多的那个IP给40亿个不重复的unsigned int的整数,没排过序的,然后再给一个数,如何快速判断这个数是否在那40亿个数当中?10亿条数据取中位数字符串匹配前缀树

简历

自我介绍

介绍一下项目,以及难点亮点

科研项目

Git 、SVN、MAVEN

Git和 SVN的区别,有哪些优势?

Git 下 init 之后生成的.git文件夹是干嘛的?

Maven怎么解决冲突?

Maven依赖原则:

  • 短路优先:会优先依赖路径短的版本。

  • 先声明先优先。

冲突原因和解决

例如A依赖B,B依赖C,C依赖jar包X1;A依赖D,D依赖jar包X2,假设X1和X2是相同的jar包,只有版本不一样,此时A优先依赖X2,但是,有可能我们需要A依赖Jar包X1,此时我们可以使用denpendency:tree来查看依赖树,并发现冲突版本;在引用D的时候可以使用<exclusion> 来排除X2。

JAVA语言

Java 集合

Java 集合在util([ju til])包下,主要包括Collection和Map两个接口。Collection又包括Set,List,Quene三个接口。List主要包括ArrayList,LinkedList,Vector(Stack 继承了Vector)等实现类,LinkedList同时实现了Queue接口,Queue中包含了一些封装的操作(add offer || remove poll element peek)。Queue还有一个实现类是PriorityQueue,此类底层使用一个堆实现的,每次都返回最小值/最大值(可以实现Comparator重写compare实现)。此外,还有一个Set接口,Set接口底层实现是根据Map的,采用适配器模式。add方法即Map接口中的put(e,null)。以下介绍下Map接口。Map接口包含Hashtable、HashMap、LinkedHashMap、TreeMap、ConcurrentHashMap等,TreeMap同时实现了SortedMap,为有序的Map。

各种集合的简介及区别联系

  • ArrayList:底层用数组实现,插入,添加,获取的的时间复杂度都为。删除的时间复杂度为。有两个标志数据,capacity为容量,size为当前元素的数量。当容量不够扩容时使用Arrays.copyOf进行复制操作,并且容量扩展为原来容量的1.5倍。DEFULT_CAPACITY为10。copyOf 方法调用了System.arraycopy方法,该方法是一个native方法。

  • LinkedList:底层用双向链表实现,插入,获取需要时间复杂度,添加、删除需要的时间复杂度。

  • Vector:与ArrayList的区别在于Vector是线程安全的,(可以用Collections.sychronizedList()包装ArrayList)单线程情况下效率较低,同时在add操作时,ArrayList扩容为1.5倍,Vector扩容为2倍。

  • Hashtable:Hashtable是一个线程安全的类,其方法都是sychronized的。其与HashMap的关系有点像Vector与ArrayList的关系。

  • HashMap:其底层为Hash表,(数组和单链表的集合),容量可以被用户指定,默认为1<<4(16)(为2的幂的原因在于之后的hash操作返回index,使用的是&的位运算,以提高效率),负载因子load_factor默认为0.75,当hash表中的元素数量大于总容量的0.75时,就要对hash扩容,扩展为原容量的两倍,并对原来的元素进行重新hash(rehash)。数组叫做bucket(桶),单链表的元素叫做entry(元素)。

  • LinkedHashMap与HashMap的不同之处在于使用了双向链表维护了元素之间的插入顺序。即是,插入时既要插入对应的桶中,又要插入到双向链表的尾部。以此保证迭代顺序跟插入顺序相同。

  • TreeMap 底层用一个红黑树实现,从而实现了数据的有序。在迭代元素时,按有序的顺序迭代。其插入和获取的时间复杂度均。红黑树的本质是一个2-3查找树,黑链接为2-3查找树中的普通链接,红链接为其中的3节点拆分后的链接,节点的颜色为链接该节点链接的颜色。根节点必须为黑链接,红链接不能连续,大小为的红黑树高度不会超过,接近平衡。在插入或删除操作时,要使用左右旋以保证其满足红黑树性质。

  • ConcurrentHashMap:与HashMap不同的是,ConcurrentHashMap是线程安全的,为分段结构,segment中含有Hashtable,每次对一个segment中的Hashtable加锁进行put、delete等操作,而不是锁整张表,segment的默认大小仍然是16,意味着可以有16个线程同时操作该ConcurrentHashMap。1.8对ConcurrentHashMap进行了改进,抛弃了之前臃肿的segment数组,改用node + CAS + sychronized + 红黑树来实现。采用对数组元素进行加锁,从而实现了更细粒度的锁。

  • CopyOnWriteList (写时复制List)其发布了一个事实上不可变的对象,由于不可变,允许多个线程访问该容器,每当修改容器时,都要创建一个副本进行修改然后再发布。所以最好在迭代访问操作远远多于修改操作的时候才使用该同步容器。

  • BlockingQueue

Hash表解决冲突的方法

  • 开放定址法: ,即在原来地址的基础上加上或减去一个数,再进行定地址的操作。理论上可以使用所有的存储空间,但是容易造成对地址的争夺,产生二次聚集,即在处理同义词的冲突的过程中又增加了非同义词的争夺

  • 再哈希:使用不同的hash函数再次进行hash操作。不易产生聚集,但是增加了计算时间。

  • 链地址法:将冲突的元素进行用一个链表进行存储

  • 公共溢出区

Hashmap中的hash(key)方法

java中的hash操作,首先要对hash的key值进行取模操作,具体实现为,将hash值与hash表的长度减一进行与操作。这样操作的结果就是将高位全部去掉,将低位保存,这也是hash表的大小要取值为2的整数次幂的原因。在得到低位的值之后,由于舍去了高位的信息,所以冲突的概率较大。所以jdk中采取了与高16位(int 为4字节32位)进行异或的操作,以期望引入高位的信息。所以最终的实现为:

static final int hash(Object key){
 int h;
 return (k==null)?0:(h = key.hashCode())^(h >>> 16);
}

模运算则使用return h & (length-1); 完成。

resize()的过程

这篇讲的很好!!!

HashMap多线程下出现的问题

  • put的覆盖问题:存在两个线程添加一个元素,entry相同,由于e.next不同步,在拉链时出现覆盖问题。

  • put之后导致get无限循环:发生在扩容中,当一个线程被挂起时,另一线程重组链表后可能发生循环访问的问题。记不清可见

ConcurrentHashMap 如何扩容?

Java NIO

最主要的三个类是Buffer,Channel和Selector。与传统的IO比较主要有亮点不同。

  • IO是面向流的,而NIO是面向缓冲区的。传统的IO面向流意味着每次从流中读取一个或者多个字节,这些数据没有缓冲在任何一个地方。如果要前后移动流中的数据,要先将其还存在一个缓冲区中。而面向缓冲区的NIO有先天的前后操纵数据的优势。

  • IO是阻塞的,即是,当调用read()或者write()方法时,当没有数据读出或者没有数据写入时,线程将是阻塞的;不同的是NIO是非阻塞的,当一个线程发送读的请求时,其只能读取当前可返回的数据,如果当前没有可用的数据,就什么都不会返回。写请求时,不用等到所写的数据要全部写入时,线程就可以去做其他的事情。线程通常将非阻塞的IO用于在其他的通道上进行读写操作,所以一个IO可以管理多个通道。

Buffer

每种类型都有对应的Buffer,但是常用的Buffer为ByteBufferCharBufferBuffer具体的实例由XXXBuffer.allocate(int capacity)来获取。ByteBuffer还有一个子类MappedByteBuffer,这个子类通常通过Channelmap()方法返回,该缓冲区将磁盘的全部或部分内容映射到内存(虚拟内存)。读写性能较高。

其有几个比较重要的标志,capacity,limit和position。其中capacity是指缓冲区的容量,由allocate初始化的时候指定。limit是指读取和写入时的上界限,position是指读取和写入时下一个位置。读取之前,调用flip()使position回到0 (读取的下一位置),limit回到定位到最后一个元素的下一位置 (读取的上界限),capacity不变。写入时,首先调用clear()方法,此时缓冲区中的元素并没有真正被clear,使用buffer.get(int pos)仍然能获取到缓冲区的内容,只是将position定位到0,将limit定位到capacity的位置,写入时,将覆盖原缓冲区的内容。

Channel

一般需要用流的getChannel()方法来初始化Channel,例如new FileInputStream(f).getChannel()new FileOutputStream(f).getChannel()new RandomAccessFile(f,"rw").getChannel()。读取文件到Buffer里可以用channelread()方法或channelmap(***)方法,写入则使用channelwrite()方法。

使用read()的具体形式为


...;
File f = new File("afile.txt");
FileChannel channel = new FileInputStream(f).getChannel();
ByteBuffer buffer = ByteBuffer.allocate(f.length());
channel.read(buffer);
...;

使用map()的具体形式为


...;
File f = new File("afile.txt");
FileChannel channel = new FileInputStream(f).getChannel();
MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, f.length());
...;

Selector

selector使用单线程处理多个channel,当应用打开了多个channel且每个channel的吞吐量不是很大时,使用channel就比较方便。使用selector分如下的几步:

  • 创建:使用Selector selector = Selector.open()

  • 将channel注册到selector:channel.register(selector, **),该方法可返回一个SelectorKey,代表该channel在selector中的"代号"。与selector一起使用时,channel一定处于非阻塞模式下。所以需要提前设置channel.configureBlocking(false)

  • 使用selector几个select的重载方法测试通道有多少已经准备好了:

    • int select()阻塞到至少有一个对象在通道上准备好了。

    • int select(long timeout)最长会阻塞timeout毫秒

    • int selectNow()不会阻塞,不管什么情况会直接返回,如果自上次调用选择操作以来,没有就绪的通道则直接返回0。

  • 调用select方法之后,如果有返回值,则说明已经有selector就绪了。此时可以通过selectedKey来选择已选择键集中就绪的通道。使用Set<SelectionKey> selectedKey = selector.selectedKeys();,之后在得到的SelectionKey的Set中可以通过SelectionKey提供的方法来操作channel。得到Channel的基本方法为selectionKey.channel()

SpringMVC和MyBatis

SpringMVC

运行流程

用户向服务端发送请求,被DispatcherServlet (调度员Servlet)截获,然后交由HandlerMapping处理,获得Handler对象及其对应的拦截器,以HandlerExecutionChain返回给DispatcherServlet,DispatcherServlet根据返回的Handler选择合适的HandlerAdapter处理,该适配器调用对应Handler的实际处理请求的方法(例如login)。接下来执行相应的Handler方法,并返回一个ModelAndView对象,这个对象中包含视图名称和数据对象,选择一个合适的ViewResolver对象返回给DispatcherServlet,渲染View并返回给客户端。

拦截器

实现 HandlerInterceptorAdapter接口,其中preHandler在拦截之前执行,postHandle在拦截之后执行;在springmvc-servlet.xml中需要定义<mvc:interceptors> , <mvc:mapping> 指定需要过滤的路径,bean 指定拦截器类。

@Resource@Autowaired

@Resource 是属于javax的,默认按名称进行装配,当找不到名称匹配的类型才使用类型进行装配;@Autowaired 是属于spring的,默认按照类型进行装配。

Spring

IOC

  • 注入方式:设值注入(setter)和构造注入;设值注入 需要一个setter方法和属性,在配置文件中用标签<property> 指定要注入的类。构造注入指在类的构造器中注入属性,需要在配置文件中使用<constructor-arg ref="XXXClass"> 来进行注入。

  • Spring核心组件:BeanFactory ,其有一个子接口ApplicationContext 因此也称为Spring上下文。通过getBean来获取对象实例。

  • Bean的作用域:通过scope="XXX" 来指定Bean的作用域

    • singleton:单例模式,使用singleton定义的Bean在整个IoC容器中将只有一个实例

    • prototype:原型模式,每次通过容器的getBean获取prototype定义的Bean都将产生一个新的实例

    • request:每次HTTP请求产生一个新的实例

    • session: 每次Http session产生一个新的实例

    • global session:每次全局的HTTP session对应一个Bean实例

    默认情况下,Spring IoC容器的Bean作用域为singleton。

aop

  • 在不改动原有方法时对原有类进行增强。源于设计模式的代理模式,使用JDK提供的动态代理技术或CGLIB的动态代理技术。默认使用JDK动态代理技术,如果要改为CGlib,则要指定<aop:aspectj-autoproxy proxy-target-class="true" />

    • JDK动态代理:实现InvocationHandler 接口,重写invoke() 方法,需要对接口实现代理。

    • CGLIB:通过CGlib原理是为被代理的类生成一个子类,重写被代理的方法;实现MethodInterceptor 接口,重写intercept() 方法。

    • 比较:JDK动态代理必须要实现接口,对接口进行动态代理增强;而CGLIB利用继承关系,如果被代理类或方法被final修饰,则不能被代理。

  • 使用@Aspect 定义一个切面,切面中定义对某一包下的方法进行各种类型的增强,在切面中使用@Before() , @AfterReturning()@AfterThrowing()@After() 进行对原有方法的增强 。

  • @Aspect 中使用@Pointcut 定义一个切点(要增强的方法),在增强方法中用@Before(pointcut="XXX.myPointcut()") 来为增强方法指定该切点。

  • 使用JointPoint类型来访问要增强方法的参数等信息

事务

  • 声明式事务: @Transactional

  • 编程式事务:使用TransactionManager 或 TransactionTemplete 两个类

  • 事务底层实现原理???????????????

  • 事务传播机制???????????

singleton 和 prototype的作用范围(使用scope指定)

  • singleton 单例模式,在整个 SringIoC 容器中,使用singleton的bean将只拥有一个实例

  • prototype原型模式,每次通过getBean方式获得Bean的时候,都将产生一个新的实例。

MyBatis

简介

主要是把sql语句和代码分开,以增强项目的可维护性。一般将sql写在xml文件中,被叫做mapper文件,一个BEAN对应一个mapper文件,其中有对该BEAN的增删改查。涉及了两个比较重要的类,SqlSessionFactory和SqlSession。其中增删改查的方法均源于SqlSession。也可以使用Mapper接口的动态代理对象来访问数据库,invoke方法中只根据package+Mapper+method全限定名确定执行的方法,即一个mapper文件对应一个Mapper接口,接口的一个方法对应了一个sql语句。方法实际上底层仍然调用的是SqlSession的方法。

动态代理

简介

为了增强target,代理类需要继承一个InvocationHandler接口,实现invoke方法。以Proxy.newProxyInstance(ClassLoader, Class(Interface), InovationHandler)获取代理对象。只能为接口对象获取代理。MyBatis的Mapper接口因为没有实现类,因此直接运行代理方法而没有对所谓的target增强。

动态代理的原理 ***

 

常用标签

  • 首先是config文件中的标签:根标签为<configuration>,其中较为重要的标签为<settings>:设置mybatis的行为,制指定日志实现、缓存、懒加载等等。<environments>配置mybatis的环境变量,即为数据源配置。需要配置dataSource和事务管理器等。<typeAliases>指定类型的别名,为其设置一个方便的名字。<mappers>标签指定了每张数据表所属于的mapper映射器。

  • Mapper XML文件中的标签:增(add),删(delete), 改(update), 查(select)。<resultMap>是mybatis中比较强大的标签,可以将数据库中选择的column(column属性)指定成需要的对象属性(property),在关联映射中这个属性用的尤其多。关联其他表的一行记录时,在resultMap 中使用<association> 标签,用column 指定对应的外键,select指定定义好的查询,javaType 指定返回的类型。关联其他表的多行记录时,用<collection> 标签,除上面的属性可以指定外,还可以使用fetchType="lazy" 指定查询模式为懒加载。

动态SQL

为适应实际开发中经常碰到的拼接sql的情况,常使用MyBatis提供了一些标签提供对动态sql的支持。if用来判断条件,choose配合when、otherwise用来判断多个条件,where可以拼接后面的where条件而不需要加where 1=1 这种语句,set可用在更新语句中,foreach可用于where xx in 的查询语句;bind可以从OGNL表达式中创建一个变量并将其绑定到上下文。

事务支持和缓存机制

MyBatis通过TransactionFactory来返回事务管理对象。其中JdbcTransaction直接使用JDBC的提交和回滚事务管理机制。(java.sql.Connection中的commit()和rollback()来实现。)ManagedTransaction的事务管理交给容器管理。

MyBatis有两级缓存机制。

  • SqlSession级别,构造SqlSession的时候使用HashMap用于存储缓存数据,不同的SqlSession存储的缓存数据之间是不影响的。同一个SqlSession对象执行两次相同的查询操作,第二次可以直接从内存中拿数据而不需要再次查表。如果SqlSession执行了DML操作并提交到了数据库,MyBatis将会清空当前的缓存,避免脏读。MySql默认开启。

  • mapper级别,多个SqlSession使用同一个Mapper的查询操作,得到的数据会缓存在二级缓存区域,同样使用HashMap进行数据存储。但是缓存是多个SqlSession共享的,作用域是mapper的同一个namespace。不同的SqlSession执行两次相同的namespace下的sql语句,第二次执行的可以直接额从内存中拿数据。默认没有开启,需要在setting参数中配置开启二级缓存。使用如下配置:


    <settings>
     <setting name="cacheEnable" value="true"/>
    </settings>

    在mapper.xml中可以配置二级缓存的属性。eviction收回策略LRU、FIFO、SOFT、WEAK;size:缓存大小;flushInterval刷新间隔,默认没有;readOnly:读写属性,默认false。

具体问题

  • MyBatis中${}和#{}的区别? MyBatis基于JDBC封装,#{}是预编译处理范畴的(PreparedStatement);而${}是字符串替换。MyBatis在运行的时候在执行#{}的sql语句时,会首先创建一个prepareStatement, 然后再将参数的值使用setInt设置。

垃圾回收机制

垃圾回收算法

主要分新生代和老年代的垃圾回收。新生代采用复制算法 ,即一块较大的Eden(['i:dən])区域和两块较小的survivor区域,对象优先在Eden区域中分配,当Eden区域满了,进行一次复制回收,因为java对象大部分的对象都具有朝生夕灭的特点,所以survivor区域很小(Minor GC);老年代采用标记整理算法 (因为老年代的对象存活率很高),即将还在使用的对象移到一边,以在回收的同时产生连续的空间。(Full GC) 还有一种回收算法叫做标记清除算法 ,效率不高 什么对象可以被回收?

  • 引用计数法:每有一个对象被引用,就将该对象的引用计数加一,引用失效时,就将引用计数减一,当引用计数为0的时候,表明该对象可以被回收。但是难以解决对象循环引用的问题。

  • 可达性分析算法:通过一系列“GC Root”的对象作为起始点,从这个节点向下搜索引用该节点的对象,一条路径称为一条引用链,如果一个对象到GC Root没有任何可达的引用链,则该对象就是可以被GC的。可作为GC Root的对象包括(1)虚拟机栈中引用的对象(2)方法区中类静态属性引用的对象(3)方法区中引用的常量(4)Native方法引用的对象。 几条原则:

  • 对象优先在Eden区分配

  • 大对象直接进入老年代:大对象指需要连续大块内存的java对象,例如很长的字符串和很大的数组(写程序应该避免使用朝生夕灭的短命大对象);由于申请不到连续的足够空间,即使内存区域还含有很多空闲区域,仍然需要进行垃圾回收以获取足够的连续空间来安置他们。所以大对象直接进入老年代可以避免较为频繁的GC。

  • 长期存活的对象进入老年代:对象的年龄计数器Age,每熬过一次Minor GC,则该对象的年龄就增加一岁。默认大于15岁的对象进入老年代。动态对象年龄判断:相同年龄的对象大小的总和大于Survivor空间的一半,年龄大于等于该年龄的对象可以直接进入老年代。

垃圾回收的时机

一般在Eden区不够分配的时候,JVM将出发Minor GC的过程;接着会发生空间分配担保的过程。

空间分配担保(Minor GC 和 Full GC):在Minor GC之前,会先检查老年代最大可用连续空间是否大于新生代所有对象空间,Minor GC就是安全的。如果不成立,会检查是否允许担保失败,如果允许,就检查连续空间是否大于历次晋升到老年代的对象的大小的平均值 ,如果是,就进行一次Minor GC,同时风险是老年代已经存不下了,这时就要再进行一次Full GC,否则进行一次Full GC。如果不允许担保失败,就要在Minor GC前先进行Full GC。

垃圾收集器

新生代收集器

  1. Serial收集器:如其名字,在垃圾回收的时候会暂停所有的线程(Stop the world)

  2. ParNew收集器:其实就是serial收集器的多线程版本,使用多个线程进行垃圾回收,使用复制算法

  3. Paralell Scavenge收集器:并行收集器且使用复制算法进行垃圾回收,与ParNew关注点不一样,关注点在于吞吐量,可以设置一个可控的吞吐量。吞吐量 = 运行用户代码的时间 / (运行用户代码的时间 + 垃圾收集时间),可以为垃圾收集器设置自适应值,让垃圾回收器动态调整参数以达到最大的吞吐量。不可以与CMS收集器配合使用,只能与Parallel Old配合使用

老年代收集器

  1. Serial Old 收集器 Serial 收集器的老年代版本

  2. Parallel Old 收集器:是Parallel Scavenge的老年代版本,使用标记整理算法

  3. CMS收集器:是一种以最短回收停顿时间为目标的收集器,其基于的是标记清除算法(注意不是标记--整理算法)。分为以下几个步骤:初始标记-->并发标记-->重新标记-->并发清除。其中初始标记和重新标记都是需要Stop the world的。初始标记记录一下GC Roots直接关联的对象,并发标记阶段是进行GC Root Tracing的过程,重新标记算法是为了修正在并发标记阶段用户程序继续进行造成的标记变动。缺点:对CPU敏感;无法处理浮动垃圾,所谓浮动垃圾,是指垃圾收集阶段运行用户程序所产生的垃圾,这些垃圾需要下一次GC才能清理;基于“标记--清除”算法,由于碎片过多可能会过早的产生Full GC。

G1收集器

G1收集器将Java堆划分为多个大小相等的独立区域(Region),虽然还有新生代老年代的概念,但是不再是物理隔离的了,他是一部分Region的集合。其步骤分为初始标记->并发标记->最终标记->筛选回收,前两个步骤与CMS是相似的,初始标记阶段标记GCRoot,并且记录正确可用的Region信息,下一段运行程序能在可控的Region中创建对象;在筛选回收阶段对各个Region进行回收价值和成本的排序,回收特定的Region区域,以此达到时间可控 。除了时间可控,G1采用“标记--整理”算法,避免了过多碎片的产生。

类加载机制

  • 双亲委派模型及其意义:启动类加载器(Bootstrap ClassLoader)在最顶层,接着是扩展类加载器(Extension ClassLoader)和应用程序类加载器(Application ClassLoader),最底层是用户定义的类加载器。双亲委派模型要求在有类加载任务的时候首先检查父类加载器是否可以加载该类,每一个类的加载都要首先传送到顶层的启动类加载器。当父类加载器不能完成加载时,才进行子类加载器的加载。这样做的意义在于类加载器具有了优先级关系。例如Object类在启动类加载器中加载,无论哪个类加载器加载,最终都交由启动类加载器加载,因此Object类在各种类加载器情况下都是同一个类。如果不采取这种模型,将产生多个同名但不同的类,java的基础运行得不到保障。在当前JDK下,用户自定义与rt.jar中类库重名的类是无法被加载运行的。

  • 类加载过程:只有在加载阶段用户可以通过自定义类加载程序参与之外,其余动作完全由虚拟机主导和控制

    • 加载

      • 通过类的全限定名来获取此类的二进制字节流

      • 将字节流的静态存储结构转换为方法区中的运行时数据结构

      • 在内存中生成一个代表该类的Class对象,作为方法区这个类的各种数据的访问入口。

    • 链接

      • 验证:为了确保Class文件字节流中包含的信息符合当前虚拟机的需要,且不会危害虚拟机的安全

        • 文本格式验证:字节流是否符合Class文件格式的规范(例如魔数开头等)

        • 元数据验证:进行语义的分析,以保证符合Java语言的语法规范

        • 字节码验证:判断语义是否合法,符合逻辑

        • 符号引用验证:能否通过符号引用找到相应的类、方法等

      • 准备:是为类变量 分配内存并设置类变量初始值,这些变量所使用的内存都将在方法区 进行分配。此时分配初值的变量仅包含类变量,即是用static修饰的变量;此时分配初值只是分配一个默认值,比如int类型的赋值为0,真正的初值(程序员定义的初值)将在类初始化的阶段才会进行赋值;但是如果使用了final修饰类变量,则类变量将在准备阶段被附上初值。

      • 解析:解析阶段是将常量池的符号引用转换为直接引用的过程;

    • 初始化:在初始化阶段,会根据程序员通过程序制定的主观计划去初始化类变量和其他资源。执行<clinit>

虚拟机的方法调用

解析

所有的方法调用在Class文件里都是一个常量池中的符号引用。在类加载的解析阶段,会将其中一部分符号引用转化为直接引用,这种解析能成立的前提是:方法在程序真正运行之前就有一个可确定的调用版本,并且这个版本在运行期间是不可改变的。这类方法调用称为解析。

Java语言中“编译期可知,运行期不可变”的方法主要包含静态方法和私有方法两大类,前者和类型直接相连,后者外部不可访问,这两类方法决定了他们不可能通过继承或者别的方式重写其他版本,因此都适合在类加载阶段进行解析。与之相对应的,java虚拟机里提供了5条方法调用字节码指令,分别如下。

  • invokestatic :调用静态方法。

  • invokespecial :调用实例构造方法,私有方法和父类方法。

  • invokevirtual:调用所有的虚方法。

  • invokeinterface :调用接口方法,会在运行期确定实现此接口的对象。

  • invokedynamic: 现在运行期动态解析出调用点限定符所引用的方法,再执行该方法。

只要能被invokestaticinvokespecial 指令调用的方法,都可以在解析阶段确定唯一的调用版本,所以符合这个条件的有静态方法、私有方法、实例构造器、父类方法4类,在类加载过程中就会把符号引用转换为直接引用。这些方法可以称为非虚方法 ,其他方法成为虚方法 (除去final方法)。虽然final方法是通过invokevirtual 来调用的,但是由于其无法被覆盖,无需对其进行多态选择,所以是一种非虚方法。

分派

  • 静态分派:与重载过程紧密相关,使用那个重载版本,取决于传入参数的数量和数据类型,虚拟机在重载时通过参数的静态类型(外观类型)而不是实际类型作为判定依据。静态类型是在编译期可知的,因此在编译阶段,javac编译器会根据参数的静态类型来决定使用哪个版本。静态分派发生在编译阶段。

  • 动态分派:与重写过程紧密相关,invokevirtual 指令第一步就在运行期会确定元素所指向对象的实际类型。这种在运行期间根据实际类型确定方法执行版本的分派过程成为动态分派。

  • 单分派和多分派。方法的接受者和方法的参数统称为方法的宗量。有多个宗量决定的分派叫多分派,静态分派属于多分派,因为由方法的参数和静态类型共同决定。而动态分派属于单分派,因为只有接受者的实际类型决定了选择哪个方法。

JVM 锁优化***

JVM编译过程***

 

多线程,并发

终止线程的方式***

J.U.C 包

volatile

只保证可见性,保证在更新之后将变量写到其他的线程可见的地方,我的理解是写到主内存而不是每个线程的工作内存。在读取的时候也是从主内存而不是自己的私有拷贝中读取值。但是不保证原子性,不保证线程安全,自增操作就是不安全的,因为自增操作依赖于当前的值,具有取值-改值-写入的特性,改值和写入是原子的,但取值不是原子的,在取值的过程中可能被其他线程改动。(可见性)

有序性:防止指令重排。例如在线程A中有一个初始化的操作,设置了一个flag;在没初始化完成之前flag为false,完成之后为true;在另一线程B中有个while循环,仅当flag设置为true时才进行相关操作。如果给flag加了volatile,则flag不会进行重排操作,只有在进行完初始化操作才进行flag赋值为true的操作。

sychronized 原理

每个对象都有一个monitor锁,加锁的时候执行monitorenter ,释放锁的时候使用monitorexit 。尝试获取锁的时候,过程如下:

  1. 如果monitor进入数为0,则进入monitor并将进入数加1。

  2. 如果线程已经占有了monitor,只是重新进入,则将monitor的进入数加1。

  3. 如果有其他的线程占有了monitor,则该线程进入阻塞状态,直到monitor进入数为0,重新尝试获取monitor。

方法的同步JVM底层常量池多了ACC_SYNCHRONIZED,底层仍然是调用monitor实现的。

ThreadLocal

维持线程封闭的一种方法,每个线程拥有一个属于自己的副本,其get方法返回的值总是当前线程中调用其set方法设置的值。这些用于特定线程的值保存在Thread对象中 。使用的典型例子如多线程数据库的Connection连接。

AQS(AbstractQueuedSychronizer)

Java并发详解只AQS

AQS维护了一个先进先出的双向队列,队列元素Node节点为其内部类;还维护了一个共享资源的状态量,volatile int state,用来记录共享资源的状态。state有三个相关的访问方法:

  1. getState()

  2. setState()

  3. compareAndSetState()

AQS 定义了两种资源访问的方法Exclusive(独占方式如ReetrantLock)和 Share(共享方式,如Semaphere和CountDownLatch)。

如果某个同步器支持独占的获取操作,则需要实现一些保护方法,tryAcquire、tryRelease 和 isHeldExclusively;而对于支持共享获取的同步器,则应该实现tryAcquireShared、tryReleaseShared等方法。在AQS中的acquire、acquireShared、release、releaseShared方法均需要调用对应方法的try版本来判断某个操作是否能够执行。

信号量 (Semaphore) ['seməfɔː]

控制多线程情况下某个资源能被访问的次数。通过acquire()获取一个许可,如果没有就等待,通过release()释放一个信号。如果设置信号量的个数为1,则可以当作互斥锁使用

CountDownLatch

与Semaphore不同的是,CountDownLatch 的release操作(countDown)使得计数值减少,当计数值减少到0的时候,await操作才能获取许可。相当于有多把锁,这些锁都被打开(countDown),才能将门打开(await)。latch.await()的对象都存储在队列中。

AtomicInteger

提供了一些原子的操作,例如CAS,incrementAndGet;CAS是指比较当前的变量值与期望的变量值,如果相等才进行赋值新值的操作。例如在incrementAndGet中就是在一个无限循环中不停的与期望值比较,相等了才Set新的值;有Bug,ABA问题,智能保证得到的值与期望值相等,不能知道其在检查的过程中有没有改变。J.U.C 提供了一个AtomicStampedReference来解决这个问题,如名称,邮戳,通过控制变量版本来保证CAS的正确性。

Future

配合Callable使用获取多线程call方法返回的结果,当其调用get方法时,get将是阻塞的,直到callable线程返回了结果;在get方法之前可以进行其它的动作。FutureTask实现了Runnable和Future两个接口,可以提交给ExecutorService,也可以提交给Thread。

final 域

用于构造不可变对象,但在多线程的语义中,final能确保初始化过程的安全性,内存的可见性,从而可以不受限制的访问不可变对象,共享这些对象时无需同步。

获取线程dump文件

Thread类提供了一个getStackTrace()方法也可以用于获取线程堆栈。

线程的唤醒与阻塞

  • yield:Thread的方法,让步,放弃CPU时间到等待运行状态(而不是阻塞状态),如果有优先级较高的线程则运行优先级高的线程,否则仍然运行被让步的线程。

  • sleep:Thread方法,睡眠,放弃CPU时间到阻塞状态,阻塞指定的时间,但是不放弃对象锁,允许较低优先级的线程获取机会。

  • join:Thread方法,加入,主线程要等待该线程执行完毕才能进行join()方法之后的调用

  • wait/notify/notifyAll:Object方法,等待/唤醒/唤醒所有;假如一个obj调用了这组方法,必须配合sychronized(obj)使用,在同步块中。调用了这组方法,将释放同步块拿到的obj的锁。调用wait()不加参数的方法将一直等待,直到收到了notify的信号才继续执行。

线程池

简介

将任务的执行和任务的提交解耦开来。避免为一个任务开启和销毁线程所做的开销。Executors工厂类新建ExecutorService对象,有两种方式向该对象提交线程。

  1. 使用ExecutorService 接口中定义的方法submit ;该方法有几种重载形式。

    1. Future<T> submit(Callable<T> task) : 该对象以得到Future 类型的的返回值,使用future.get() 方法获取。

    2. Future<?> submit(Runnable task): 此时的返回对象仍然是Future 但是由于Runnable 并没有返回值,所以future.get() 方法获取的返回值为null

    3. Future<T> submit(Runnable task, T result): 此时由于Runnable 并没有返回值,可以使用一个对象result 将返回结果带出来。

  2. 使用Executor 中定义的方法void execute(Runnable command) ,此方法无法返回返回值。

ExecutorService 的默认实现类为 ThreadPoolExecutor ,其构造方法为:


public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime
TimeUnit unit, BlockingQueue<Runnable> workQueue,
                        RejectedExecutionHandler handler)

各个参数的说明如下:

  • corePoolSize : 线程池中工作线程的数量,当任务数超过这个数量,新的任务将被放到阻塞队列中。

  • maximumPoolSize:线程池中最大线程的数量,当任务队列满了,而且运行任务的数量小于maximumPoolSize,仍然创建新的线程处理任务。如果队列已满且线程数量也达到了maximumPoolSize,如果使用的阻塞队列是ArrayBlockQueue<Runnable> ,则抛出异常RejectExecutionException ;如果使用的阻塞队列是LinkedBlockQueue<Runnable> ,因为对大小没有限制,所以不存在上述问题。

  • keepAliveTime :指超过corePoolSize 的线程在不工作情况下的生存时间,如果一个线程空闲的时间超过keepAliveTime ,则会终止,直到线程池中线程的数量减少到corePoolSize

  • unit :参数keepAliveTime 的单位。

  • workQueue :阻塞队列

  • handler :表示当拒绝处理任务时的策略。有丢弃任务抛出异常/不抛出异常/丢弃队列的任务尝试执行任务/由调用线程处理该任务。

  • 还有一个构造函数其中有个参数threadFactory 用来创建线程。

四种线程池

Executors.newCashedThreadPool()

创建一个可缓存线程池,可以灵活的创建和回收线程,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

Executors.newFixedThreadPool()

创建固定长度的线程池;

Executors.newScheduledThreadPool()

创建一个定长线程池且支持定时或周期性的执行任务;

Executors.newSingleThreadExecutor()

创建一个单线程的线程池。

如果你提交任务时,线程池队列已满,这时会发生什么

  • 如果队列满了,而且此时运行的数量小于maximumPoolSize,则仍然创建新的线程运行任务。(一般情况下运行的线程数为corePoolSize)。如果队列已满而且线程数也到达maximumPoolSize,则抛出异常(RejectedExecutionException)。

  • 以上情况针对于ArrayBlockingQueue<Runnable>,如果是LinkedBlockingQueue<Runnable>,因为该队列无大小限制,所以不存在上述问题。

线程池的大小

  • 对于密集型计算任务,在拥有 个处理器的系统上,当线程池的大小为 时,通常能达到最优的利用率。

  • 可以根据任务等待时间与服务时间的比值来计算线程池的大小。可以定义如下的公式:

    其中,代表cpu的数量, 指cpu的平均利用率, 指等待时间与服务时间的比值。从公式中可以看出,cpu密集型任务(较小)所需设置的CPU数量较小,I/O密集型任务(较大)所需要的CPU数量较大。

几种锁

  • ReentrantLock:可重入锁,功能与sychronized相当(sychronized)也是可重入的,必须要在finally块中释放锁,因此sychronized较为安全,而且sychronized是基于JVM的,lock是基于JDK的。相比于sychronized,实现了几个高级功能

    1. 等待可中断:当持有线程请求的锁的线程长时间不释放锁时,可以选择放弃等待锁。tryLock(long timeout, Unit unit)

    2. 公平锁:sychronized和ReentrantLock在默认情况下实现的都是非公平锁,即:当一个线程请求锁时,这个锁正好是可用的,不必理会其它请求该锁的线程而直接占用该锁,使用new RenentrantLock(boolean fair)来创建公平锁。虽然说公平,但是公平锁的吞吐量较低,因为每次锁释放时,将队列头的线程唤醒到就绪状态是比较消耗资源的。非公平锁的插队抢占方式带来了吞吐量的提升

  • ReentrantReadWriteLock:读写锁,在基于AQS实现的ReetrantReadWriteLock中,维持了两个状态量,一个写入锁的状态量,一个读取锁的状态量。在读取锁上的操作将使用共享的方法获取锁,在写入锁上的操作将使用独占的获取方法与释放方法。AQS在内部维护一个等待线程队列,其中记录了某个线程请求的是共享访问还是独占访问。在ReentrantReadWriteLock中,当锁可用时,如果位于队列头的是写线程,则独占锁进行写入。当位于队列头的是读线程,则在队列中第一个写入线程之前的多有读线程都将获得这个锁。

如何在两个线程之间共享数据

利用共享对象共享数据,wait/notify/notifyAllawait/signal/signalAll进行唤起和等待。如果两个线程的动作相同,可以在线程类内部创建该共享数据,如果两个线程动作不同,可以将共享数据另外封装成一个对象,并且对该对象进行共享操作。

写一段 sychronized 能产生死锁的代码

主要要考虑到锁住一个对象之后请求另一个对象的锁,而这个对象被另一个线程锁了。


public class DeadLock{
 private boolean switch ;
 private Object obj1 = new Object();
 private Object obj2 = new Object();
 public DeadLock(boolean switch){
   this.switch = switch ;
}
 @Override
 public void run(){
   if(switch){
     sychronized(obj1){
       try{
         sleep(500) ;
      }catch(Exception e){
         e.printStackTrace();
      }
       sychronized(obj2){
         System.out.println("lock");
      }
    }
  }else{
     sychronized(obj2){
       try{
         Tread.sleep(500);
      }catch(Exception e){
         e.printStackTrace();
      }
       sychronized(obj1){
         System.out.println("lock");
      }
    }
  }
}
}

死锁测试方法如下


public static void main(String[] args){
 DeadLock lock1 = new DeadLock(true) ;
 DeadLock lock2 = new DeadLock(false) ;
}

 

异常系统

异常(Throwable)分为Error和Exception;Error分为VirtulMachineError和AWTError;VirtulMachineError包含常见的栈溢出(StackOverFlow)异常和内存溢出(OutOfMemoryError)异常;Exception包含IO异常(IOException)和运行时异常(RunTimeException)。运行时异常不捕捉也可以,Error不需要被捕捉。 也就是说,必须要被捕捉的异常只有IO异常?

迭代器和比较器

Iterator和Iterable

Iterable是一个接口,其中有一个方法Iterator<T> iterator();返回一个Iterator对象。Iterator对象包含相关的迭代器方法,next() ,hasNext(), remove() 方法。List等集合函数实现了Iterable接口;在具体的实现类中,iterator() 返回了一个继承Iterator的内部类ItrIterator 在java.util包下面;而Iterable 在java.lang包下

Comparable和Comparator

Comparable在java.lang包下;Comparator在java.util包下;Comparable接口只有一个实现方法,compareTo(T o),其中o是要比较的元素;而Comparator作为比较器,jdk1.8实现了很多默认方法(default method),继承它的实现类需要要实现compare(T o1, T o2)方法。Comparator是一个外部比较器,即单独定义一个比较器来实现对象的比较,而Comparable是一个内部比较器,通过对象调用自身的compareTo方法来实现与其他类的比较。如果希望实现比较器进行在有序集合中的排序,实现comparable较为简单,但是需要改动原有类;comparator作为外部比较器不需要改动原有类,但需要同时将比较器作为参数传入相关的方法或通知相关的类。例如Collections.sort(arrayList, myComparator)

Java 基础

String 相关

  1. String 可以被继承码?不可以,String 被声明为final类。

重载和重写

  • 重载的函数参数类型一定不同:1)数量不同;2)类型(顺序)不同。

泛型怎么实现

在编译阶段进行了泛型擦除,虚拟机是看不到泛型的,擦除的类型将被单独记录在Class文件中的Attributes域内,而在使用泛型处做类型转换和类型检查。擦除规则叫做保留上界。

<T> 擦除后变为Object;

<? extends A> 擦除后变为A;

<? super A> 擦除后变为Object;

sychronized 修饰static方法和普通方法的区别

static方法是类方法,所以sychronized修饰类方法拿到的锁是Class的锁;而普通方法拿到的锁是当前对象的锁;两个锁并不会产生冲突;如果要在类方法和普通方法里实现同步,可以在方法体内使用sychronized同步块对一个锁对象加锁。

具体问题

  1. 怎么样实现一个不可变类?

    • 将类声明为 final。

    • 将属性声明为 private且不提供 setter 方法。

    • 所有成员变量声明为final,在构造方法中赋值。

    • 实现深拷贝,getter 方法返回对象的深拷贝。

  2. 深拷贝和浅拷贝

    • Object类的clone方法是一个浅拷贝。等于号是一个地址拷贝,指向的是同一个对象;clone()方法生成了一个新的对象,对象中的基本类型是直接拷贝过来的,但是对象中的引用类型指向的仍然是原对象的数据。Collections.copy()方法是一个深拷贝。

数据结构和算法基础

二叉树前中后遍历,知道某两个推出第三个,哪些能推出,哪些推不出?

前序和后续,没有中序的推不出,因为只能确定父子关系,不能确定左右树的关系。

 

计算机网络

http 和 https

  • http:建立连接(DNS)--> 发送请求 --> 响应请求 --> 关闭连接

  • https:请求建立连接 --> 服务端发送证书(包括公钥)给客户端 --> 协商加密等级 --> 客户端根据加密等级将会话密钥利用公钥进行加密--> 服务端利用私钥解密会话密钥并利用会话密钥加密通信的信息。

长连接和短链接

在HTTP/1.0中,默认使用短链接。每进行一次HTTP传输,就建立一个HTTP连接,当任务结束,该连接就断开。从HTTP/1.1起,改用默认长连接,用以保持连接特性。使用长连接的协议,会在响应头有Connection:keep-alive 代码。在使用长连接时,客户端访问一个网页完成后,客户端和服务器之间用于传输的TCP连接不会关闭,当客户端再次访问这个网站上的网页时,会复用这条已经建立的连接。Keep-Alive 不会永久保持,可以在不同的服务器软件设定这个时间。

对比 :短链接的优点是控制起来比较简单,保存的连接都是有效的连接;但是由于每次建立连接销毁连接需要消耗资源,效率较低。

HTTP/1.0、HTTP/1.1、HTTP/2.0

HTTP/1.0和HTTP/1.1

  • Http/1.1默认支持长连接

  • 节约带宽:HTTP/1.1 支持只发送Header信息(不带body信息),如果服务器认为客户端有权限访问则返回100,此时再发送body信息;如果服务器返回401,则无权限,不必再发送Body信息,节约了带宽。

HTTP/1.1 和 HTTP/2.0

  • 多路复用:HTTP2.0使用了多路复用技术,做到统一个连接处理多个数据请求,而且并发的数据量比HTTP1.1大了好几个数量级。

  • 数据压缩:HTTP2.0支持HPACK算法对header的数据进行压缩。

  • 服务器推送:对支持HTTP/2.0的server进行请求数据的时候,服务器会把一些客户端需要的资源一起推送到客户端,以免客户端再次请求建立连接。非常适合加载静态资源。推送的这些资源实际上是存放在客户端的=某处,并不需要占用带宽。

状态码相关

  • 301和302的区别:301是永久重定向HTTP,搜索引擎会抓取新的网址内容同时将旧的网址替换为新的网址;302是暂时重定向,搜索引擎会抓取新的内容而保留旧的网址,可能会发生网址劫持问题。例如好的URL重定向为坏的URL,但网址的内容是存在于坏的URL中的,搜索引擎所显示的url结果却是好的URL,这时发生了对坏URL网址的内容劫持。

TCP/IP分层和OSI分层

TCP/IP分层

  1. 链路层:典型协议ARP(地址解析协议 IP->MAC)和RARP (逆向地址解析协议)

  2. 网络层:IP协议,ICMP(Internet 控制消息协议,允许目标主机或者路由器给予数据发送方提供反馈信息,ICMP是IP协议的一部分,任何实现了IP协议的设备同时也要使用ICMP协议),和IGMP协议(Internet工作组管理协议),用于广播流量的管理,只广播给一部分主机,组播。

  3. 传输层:TCP和UDP协议

  4. 应用层:电子邮件,超文本传输等等协议

OSI 分层

1.物理层;2.数据链路层;3.网络层;4.传输层;5.会话层;6.表示层;7.应用层

TCP和UDP的区别,TCP如何保持可靠性传输

TCP相关重点

  1. 三次握手:请求链接的客户端向主机发送syn信号和sequence number = x 并进入SYN_SENT状态;服务器收到syn信号必须确认syn信号,将ack信号设置为x+1,并且自己也将发送一个syn信号和自身的seq number = y,服务端进入SYN_RECV状态;客户机收到服务端发送的SYN+ACK信号,向服务器发送确认信号 ACK = y+1,发送完毕后,服务机和客户机都进入ESTABLISHED状态,完成三次握手。使用三次握手是为了防止发生服务器忙等状态。当客户机发送SYN信号后,由于各种原因在网络中延迟了,此时客户机超时重发SYN信号并且成功建立了链接并发送了数据;而在数据发送完成后,之前没有到达的SYN到达服务机,服务机产生应答并等待客户机发送信息,而此时客户机并没有要发送信息,造成服务机的空等待。

  2. 四次挥手:主动关闭方发送FIN信号,进入FIN_WAIT_1状态;被动关闭方接收FIN信号,并发送ACK确认信号,进入CLOSE_WAIT状态,此时被动关闭方仍然可以向主动方发送数据;主动方收到FIN信号后进入FIN_WAIT_2状态,当被动方数据发送完毕,向主动方发送FIN信号,被动方进入LAST_ACK状态;主动方接收到FIN信号,并向被动方发送一个ACK确认信号,进入TIME_WAIT状态,如果等待2MSL服务器没有想应信息,则说明关闭成功,进入最终的CLOSE状态。至此,四次挥手执行完毕。

  3. 滑动窗口:由于TCP连接在传输信息时需要收到返回确认信息才能进行接下来的传输,这种协议浪费了大量的宝贵带宽。TCP协议通过滑动窗口技术来提高网络的吞吐量。具体做法是在要发送的数据上放置一个滑动窗口,发送时将窗口中的所有分组都进行发送,当收到第一个分组的确认信息后,可以将窗口向后移动一个分组。随着确认的送达,窗口持续向后移动。

  4. 典型基于此的应用层协议HTTP, SMTP, POP3,FTP

TCP如何保证可靠传输

  1. 将数据截断为合理的长度

  2. 超时重发

  3. 对于收到的请求,给予确认响应

  4. 校验出错将丢弃不予以响应

  5. 对失序数据将进行重排序,然后才交给应用层

TCP的拥塞控制

慢开始和拥塞避免

发送方维持一个拥塞窗口(cwnd) ,自己的发送窗口等于或小于拥塞窗口。

主机开始工作时,如果立即把大量数据发送到网络,有可能引起网络拥塞。较好的方法是先试探一下,由小到大的增加窗口。先将窗口设置为一个初始大小(一般设置为一个最大报文段的大小)。每当收到一个对新的报文段的确认之后,就将拥塞窗口加倍,这个过程叫做慢开始

为了防止拥塞窗口一直增大引起网络拥塞,需要设置一个慢开始门限,当拥塞窗口的大小大于这个门限的时候,进行拥塞避免 的操作,拥塞避免的思路是让拥塞窗口缓慢的增大,将之前的指数型增加改为线性增加。

无论是在慢开始阶段还是在拥塞避免阶段,每当发送方判断到网络出现了拥塞(根据是没有按时收到确认),就把慢开始门限减小到原来的一半,然后吧慢开始的拥塞窗口重新设置为1并开始慢开始算法。

快重传和快恢复

快重传 算法要求接收方每收到一个失序的报文段就对发送发发送重复确认,而不要等待自己发送数据时才进行捎带确认。例如,接收方接收到了M1、M2和M4,M4接收到之后因为是失序的,所以再次进行对M2的确认,之后如果一直没收到M3,在接收到M5,M6的时候仍然需要对M2进行确认。当发送方接受到3个连续的重复确认之后,就应当立即重传对方尚未接收到的报文M3,而不必等到为M3设置的重传计时器到期。尽早重传报文段可以使网络吞吐量增加。与快重传同时进行的还有快恢复 算法,同样把慢开始门限减半,但是拥塞窗口不是从1开始,而是设置为慢开始门限减半后的值,同时执行拥塞避免算法(加法增大)。

UDP协议

尽最大能力交付,典型的基于此的应用层协议例如DNS协议和TFTP

session 和 cookie

cookie和session

  • session:Session是另一种记录客户端状态的机制,不同的是,session是保存在服务端的。

  • cookie:是存储在客户端的一小段信息。客户端请求服务器,如果服务器需要记录用户信息,就使用response向客户端浏览器颁发一个cookie。客户端浏览器会把cookie保存起来。当浏览器再次请求网站时,浏览器把请求连同cookie信息一起发送给服务器。服务器检查cookie信息,以此来判断用户状态。Cookie并不提供修改、删除操作。如果要修改某个Cookie,只需要新建一个同名的Cookie,添加到response中覆盖原来的Cookie。删除cookie时通过setMaxAge来删除。

Http中 Header信息

 

操作系统

进程

进程之间的调度方式

处理机调度的层次

分为高级调度(作业调度)、低级调度(进程调度)、中级调度(中程调度)。高级调度就是指对外存上作业的调度,低级调度是指内存中对排队线程的调度,中程调度是指为了提高内存利用率把暂时不能运行的进程不再占用宝贵的内存资源而将他们调到外存上等待,这种状态叫做挂起状态。

调度算法

  • 先来先服务算法(FCFS):有利于长作业而不利于短作业。

  • 短作业优先算法(SJF):每次都优先选择所用时间较短的作业进行服务。

  • 高优先权优先调度算法

    • 分类:非抢占式和抢占式

    • 优先权类型:静态优先权和动态优先权

    • 高响应比优先调度算法:优先权 = (等待时间+要求服务时间) / 要求服务时间

  • 基于时间片的轮转调度算法

进程间的通信方式

  1. 基于存储器系统共享

    • 共享数据结构,例如生产者-消费者问题中的共享缓冲区,这种模式下,公共缓冲区的设置以及进程间的同步设置都是程序员的职责。只适用于传递叫少量的数据。

    • 共享存储区,在存储区开辟公共空间,适合传递大量的数据。

  2. 管道通信

    • 管道是指链接一个读进程和一个写进程之间的共享文件,pipe文件。适合大量数据的传输。

  3. 信号量(Semaphore):是一个计数器,用来控制多个进程对共享资源的访问。不是用来交换大批数据,而是常用于多线程之间的同步。

  4. 消息队列:消息队列是消息的链表,存放在内核中并由消息队列标识符标识。克服了信号量传递信息少,管道只能承载无格式字节流以及缓冲区大小受限等问题。

  5. 信号机制(signal):信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。

  6. 套接字(socket):可用于不同机器间的进程通信。

死锁

死锁产生原因

  • 竞争资源

  • 进程推进顺序非法

死锁产生必要条件

  • 互斥条件

  • 请求和保持条件

  • 不剥夺条件

  • 环路等待条件

死锁预防和避免的算法

  • 预防是指破坏死锁必要条件的几个条件,以此来预防死锁的发生。(预:之前防止),主要的方法有:

    1. 破坏请求和保持条件:这种方法规定在进程分配资源时一次性分配给进程

    2. 摒弃不剥夺条件:当进程的资源不能立即得到满足时,必须释放自己请求的资源

    3. 摒弃环路等待条件:所有进程对资源的请求必须按照对进程编号的递增顺序提出

  • 死锁避免算法:银行家算法,在检测到系统不会进入不安全状态才会对进程进行资源的分配

存储器管理

连续行存储管理方式

将用户进程分配在连续的连续的内存空降,这样会产生较多的碎片。

离散型存储管理方式

页式管理方式

将存储空间划分为大小一定的页面。

寻址
  • 页表:存储了页号和物理块号的关系。

  • 寻址时(逻辑地址转换为物理地址)时,有一个页表寄存器记录了页表起始地址和页表长度,逻辑地址包含页号和业内地址,首先将页表始址与页号和页表长度的乘积相加,得到该表项在页表中的位置,由此可以得到该页的物理块号,将至装入物理地址寄存器中,与此同时将有效地址寄存器中的页内地址送入块内地址字段中,计算得到物理地址。

快表TLB

有一个记录了当前访问的页表项的高速寄存器,从寄存器寻找页号对应的物理地址并与页内地址拼接生成物理地址。

段式管理方式

页式存储为了提高内存的利用率,段式存储为了满足程序员在编程和使用上多方面的需求。例如,方便编程(按逻辑划分为若干段),信息共享,信息保护,动态连接(运行时需要该段才调入内存)等。

寻址
  • 段表:每个段在表中占有了一个表项,其中记录了该段在物理地址中的起始地址(基址)和段的长度。

  • 寻址:根据段表始址和该段的短号计算出该段对应的段表项的位置,从中读出该段在内存中的起始地址,将该段的起始地址与段内地址相加得到物理地址。

段、页式的区别

页式物理单位,分页减少内存碎片;段式信息的逻辑单位,其含有一组意义相对完整的信息;为了能更好的满足用户的需要。

段式系统有一个突出优点,是易于实现信息的共享(将共享的部分划分为一个段即可),允许若干个进程共享一个或多个分段,且对段的保护也简单易行。

段页式管理方式

既能够便于实现、分段可共享、易于保护、可动态链接,有能够减少内存碎片。

虚拟存储器

置换算法

  • 最佳置换算法:每次置换出来的都是其后不再使用或者最长时间不再使用的块。(不可实现)

  • 先进先出置换算法。

  • 最近最久未使用(LRU)。(特殊的栈支持)

  • Clock置换算法:仅需要为每一页设置一个访问位,再将内存中的所有页面都通过链指针连接成为一个循环队列,某一页被访问时,将标志位置为1。置换算法在选择某一页进行置换的时候,只需检查访问位,如果是0,则选择该位进行换出,若为1,则将1置为0且不换出,再按先进先出算法检查下一个页面。

数据库

范式

第一范式

强调原子性,即是,列是不可再分的。考虑数据表列中含有一个列是用户电话,而电话包含工作电话和私人电话,这样的数据表就是不符合第一范式的。

第二范式

首先是第一范式,然后满足两个条件,其一必须有主键,其二表中的非主键列必须完全依赖于主键,而不能只依赖于主键的一部分。例如考虑到一个订单明细表含有【订单号商品号 ,商品名称,商品价格】,其中订单号和商品号是复合主键,但是商品名称和商品价格只依赖于商品号,因此部分依赖于主键,这样也是容易产生冗余,例如不同的订单里含有相同的商品,商品名称和商品价格会产生重复(冗余),这样部分依赖于主键的表设计不符合第二范式。

第三范式

首先是第二范式,其次不能出现传值依赖。传值依赖是指,例如一张订单表【订单号,订单客户ID,订单时间,客户姓名】,其中订单表是主键,订单时间,订单客户ID完全依赖于订单号,但是客户姓名却依赖于订单客户ID(非主键),所以这个表不符合第三范式。存在着冗余。

mysql为什么用B+树

B+树为了存储索引,索引一般数据量比较大,存储在磁盘上。一次读取磁盘I/O的时间比较大,所以要求I/O次数要小。B-树由于中间节点存储了数据,每个节点数据量就比较大,而每次I/O读出数据是一定的,所以B-树的读取次数就比较多。而B+树中间节点是不存储数据的,因此I/O次数较少。同时由于只在叶子节点存储数据,树高不会有太大差别,所以每次查找效率相当,查找比较稳定。

mysql什么情况下会触发表锁

乐观锁、悲观锁

  • 悲观锁:悲观的认为业务操作是可能失败的,要先获取锁,再进行业务操作。“一锁二查三更新”即指使用悲观锁。通常来说,数据库通常使用select ... for update 来实现悲观锁,使用该语句时,会获取该语句查询到的行的行锁,其他并发执行的select for update将会等待其释放锁。需要注意的是,mysql行级锁都是基于索引的,如果一条sql语句查询的条件不是索引,mysql会锁整张表,造成效率问题 。适用于事务操作频繁易于产生数据冲突的情况。

  • 乐观锁:在提交数据更新之前检查是否发生了冲突,发生了则进行事务的回滚。使用版本还进行实现。适用于不易产生数据冲突的情况。

读锁、写锁、表锁、行级锁

数据库事务的四种隔离级别

  • 未提交读(Read Uncommitted):在事务没有提交时就允许读取数据。允许其他事务读取没有提交的数据。脏读:一个事务读取到了另外一个事务没有提交的数据。重点在于事务过程中读取

  • 提交读(Read Committed):只能读取已经提交的数据。解决了脏读问题。Oracle等多数数据库默认都是该级别 。但有一个问题:不可重复读 。在一个事务里,有多次读取操作,但是在两次读取之间有其他的事务改变了数据,此时导致两次读取的数据不同,读取数据的时候其他的事务不能对数据进行修改 。重点在于事务过程中修改

  • 可重复读(Repeatable Read):可重复读,在同一事务内,查询的数据始终与事务开始时是一致的。InnoDB的默认级别,消除了不可重复读,但是存在幻读现象。幻读是指一个事务对表进行操作,改动涉及了表中的所有数据。同时第二个事务向表中插入一行新的数据,此时操作第一个数据的用户就会发现数据表中还有没有修改的数据,好像产生了幻觉一样。重点在于事务过程中增加或删除

  • 可串行化(Serializable):完全串行化的读,每次读都要获得表级别的锁,读写都会相互阻塞。

ACID事务

ACID

  • A(Atomicity 原子性):一个事务要么执行,要么不执行。

  • C(Consistency 一致性):事务的运行不改变数据的一致性,例如,完整性约束了a+b=10,事务改变了a,b也随之改变。

  • I(Isolation独立性):事务之间不会交替执行。

  • D(Durability持久性):改变的数据持久的留在数据库中。

事务实现原理

mvcc原理

InnoDB的 MVCC主要是为Repeatable-Read(可重复读)事务隔离级别所做的。在InnoDB的MVCC中,在每一行后面隐藏着两个列,一个保存了行的创建时间(版本号),一个保存行的过期时间(或删除时间)。这里的时间并不是指的实际的时间,而是系统的版本号。没开始一个新的事务,系统的版本号都会自动递增。在Repeatable read隔离级别下,MVCC具体的操作实现如下:

  • SELECT: 会根据下面的两个条件查询每行记录:

    • InnoDB只查找创建时间小于等于当前版事务本号的的数据行,可以确保当前的事务读取的行是事务开始之前就存在或者由该事物创建。

    • InnoDB只会查询删除版本不存在或者版本号小于当前事务版本号的数据,可以确保查询的数据在当前事务开始之前没有被删除。

  • INSERT: InnoDB为新插入的每一行作为当前的版本号作为行版本号。

  • DELETE: InnoDB为删除的每一行保存当前的系统版本号作为行删除标志版本号。

  • UPDATE: InnoDB将为更新的行保存当前的版本号作为创建版本号,复制出该行进行修改;同时将更新之前的行的删除版本号设置为当前版本号;复制的行修改后的版本号等于之前的版本号才进行事务的提交,否则进行回滚。

InnoDB和MyISAM

InnoDB

支持了ACID事务,提供了数据库的四种完整性约束,提供了行级锁和完整性约束,MySQL在运行InnoDB的引擎时,会在内存中建立缓冲池,用于缓冲数据和索引,其设计目标是处理较大数量的数据。当要使用数据库事务时,该引擎时首选,因为支持更小粒度的锁,写操作不会锁整张表,适合并发较高时提升效率。但是该引擎没有保存行数,select count(*)from table 需要扫面整张表。

其索引结构为B+树,索引文件本身就是数据文件,即B+树数据域存储的本身就是data数据,索引的key就是数据表的主键,因此InnoDB数据表本身就是主索引 。一个主索引的例子如图所示:

对于辅助索引 来说,B+树数据域存储的是主键的值,如下图所示

这种基于主键的聚集索引使得按主键查询效率较高,但是对于辅助索引来说,需要查询两次,首先需要查询到主键,然后通过主键查询数据。这种在索引中直接存储数据的索引方式成为聚簇索引

MyISAM

没有提供对数据库事务的支持,也不支持行级锁和外键。每次对表进行事务操作时需要锁整张表,效率较低。但存储了数据记录的行数。如果数据库的读操作远远多于写操作且不需要数据库事务的支持,那么MyISAM也是一种好的选择。

 

其索引结构同样是B+树,但不同于InnoDB的是,B+树中存储的是实际数据的地址,地址指向了实际数据。这种索引的形式叫做非聚簇索引 以下两张图分别给出了主索引辅助索引 的例子。

比较和讨论

大尺寸的数据趋向于选择InnoDB,因为其支持事务处理和故障恢复。数据库的大小决定了恢复的长短,InnoDB可以利用事务日志进行数据恢复,这会比较快。主键查询在InnoDB也会相当快。大批的INSERT语句(在每个INSERT语句中写入多行,批量插入)在MyISAM下会快一些,但是UPDATE语句在InnoDB下则会更快一些,尤其是在并发量大的时候。

了解数据库底层原理对我们正确使用和优化数据库都有好处,比如:

  • 由于InnoDB直接存储主键数据作为索引,所以不宜用过长的主键来作为主键,因为这会增加索引的数据大小,过长的主索引大小会令辅助索引变得过大。

  • 使用非单调的字段作为主键不是一个好主意,这可能会使索引为了维持B+树的特性而不停的分裂调整,十分低效。推荐使用递增的数据列作为主键。

索引的实现方式

  1. B+树

  2. 散列索引:Hash的形式,将散列值相同的数据放到同一个桶中。

  3. 位图索引:

在A、B、C上建立索引,可用与不可用问题***

类别

普通索引、唯一索引、主键索引、组合索引。

连接(join)操作

Inner join、left join、right join、outer join、cross join

设计模式

几种关系

几种关系

继承

实现

依赖

类A使用到了类B,但这种关系具有偶然性、临时性,非必然。具体表现为A中的某个方法使用到了B。

关联

是指两个类或者类与接口之间比较强的依赖关系,关系不是偶然的,是长期,稳定的,双方的关系也是平等的。表现在代码里,就是被关联的B以属性的方式出现在A中。

聚合

是关联关系的一种特例,其表现的是一种整体与部分的关系、用有的关系,此时整体和部分是可以分离的,他们有各自的生命周期。比如公司与员工,电脑与CPU的关系。表现在代码层面,与关联关系是一样的,只能通过语义来判断。

组合

组合也是关联关系的一种特例,这种关系比聚合强,也称为强聚合关系;也体现的是整体与部分的关系,此时整体与部分是不可分的,比如人和大脑的关系。代码层面上,表现与关联关系也是一致的,只能通过语义判断。

关系的强弱关系一般来说组合>聚合>关联>依赖。

几个原则

开闭原则

单一职责原则

依赖倒转原则

迪米特法则

 

几种单例模式

  • 懒汉模式


    public class SingletonInstance{
     private SingletonInstance instance ;
     private SingletonInstance(){};
     public static SingletonInstance getInstance(){
       if(instance == null)
      instance = new SingletonInstance();
       return instance ;
    }
    }

    这种情况下使用了lazy loading,但是是线程不安全的,可以对getInstance方法进行sychronized关键字包装实现同步,但是这样就损失了效率

  • 饿汉模式


    public class SingletonInstance{
     private static SingletonInatance instance = new SingletonInstance();
     private SingletonInstance();
     public static SingletonInstance getInstance(){
       return instance;
    }
    }

    这种情况下基于classLoader方式规避了线程安全问题,但是类的装载有很多种可能,不能保证一定是调用getInstance导致了类的装载,所以没有达到lazy loading的效果,可能浪费资源。

  • 内部类方式


    public class SingletonInstance{
     private static class SingletonHolder{
       private static final SingletonInstance INSTANCE = new SingletonInstance();
    }
     private SingletonInstance(){};
     public static getInstance(){
       return SingletonHolder.INSTANCE ;
    }
    }

    这种方式只在调用getInstance的时候才进行对SingletonInstance的实例化,比饿汉模式更加合理。

优化技术

前端缓存CDN

用户在访问网页时,CDN选择一个离用户最近的CDN边缘节点来相应用户请求。

好处:

  • 用户:距离较近的节点相应请求,加快了对请求的响应速度

  • 服务器:由于处理请求在边缘节点,起到了分流作用,减轻了源服务器的负载

后端缓存Redis

NoSql : Not only Sql

为什么要NoSql?

  • 高并发读写问题

  • 海量数据问题,查询效率

  • 高可用性和高扩展性,不能通过添加服务器和节点来解决问题。

应用场景

  • 缓存

  • 网站访问统计

  • 任务队列

  • 分布式构架中的session分离 :在分布式构架中,在一台服务器上保存了session,但是有可能该用户的另一请求被负载均衡到另一个服务器,此时就需要对session进行分离,将session写在Redis等非关系型数据库中,并且有相关的备份。读取都从Redis中读取,写入也都写入到Redis服务器。可容错且实时性较强。

数据结构

  • 字符串:get、set、incr(增加操作)、decr(减小操作)、incrby num 3 (给num加3)、decrby num 3append num 5

  • Hash:hset myhash username jack ; hmset myhash usename rose age 2 ;hget myhash user name ; hmget myhash usename age ; hgetall myhash ; hexists myhash username ; hkeys myhash ; hvals myhash ;

  • List: 按照插入顺序排序的字符串链表。可以在头部、尾部插入元素。

    lpush mylist a b c : 左侧插入

    rpush mylist a b c : 右侧插入

    lrange mylist 0 5 : 显示0到5位置的元素

    lpoprpop : 弹出元素

    lrem mylist 2 3 : 从前往后删除2个3

    lrem mylist -2 1 : 从后往前删除2个1

    lrem mylist 0 2 : 删除所有的2

    lset mylist 3 mmm : 设置index为3的元素为mmm

    linsert mylist before/after b 11: 在b元素之前/之后插入11

    rpoplpush mylist1 mylist2 : 从mylist1右侧弹出数据插入到mylist2的左侧

  • Set: 不允许重复元素,允许集合操作

    sadd/srem...

    smembers myset : 展示元素

    sismember myset a : 判断a是否在myset中

    sdiff mylist1 mylist2 : mylist1 和 mylist2 的差集 ;

    sinter mylist1 mylist2 : 并集

    sunion mylist1 mylist2 : 交集

    scard : 返回成员数量

    srandmember myset : 随机返回成员

    sdiffstore myset1 myset2 myset : 将myset1和myset2的差集存到myset中,其他集合操作相似。

  • Sorted-Set : 每个成员都有一个分数,Redis根据其分数进行排序。

    zadd mysort 70 zhansan 80 lisi 90 wangwu : 添加元素和其对应的分数

    zscore mysort zhangsan : 获取分数

    。。。

事务

支持多数据库,16个数据库,编号从0到1;


multi //开启事务
exec //提交事务
discard //回滚事务

持久化

  • RDB:默认支持,指的是在制定的时间间隔内将数据写入到磁盘。

    所有的数据库都只包含一个数据文件,方便文件的恢复。效率较高。

    不能最大限度的保持数据的可用性,没来得及往硬盘上写可能发生宕机发生数据的丢失。数据集较大可能会使服务器停止实现备份。

    save 300 10 : 每300秒有10个key发生变化会向硬盘上写一次

  • AOF:将以日志的形式记录Redis服务器执行的每一个操作。

    可以带来更高的数据安全性。以append方式持久化,不会丢失之前的数据文件。有日志文件记录修改操作,可以通过此文件完成数据重建。

    持久化文件大,运行效率低于RDB。

    appendonly yes;

    appendfsync always : 发生操作就持久化;

    appendfsync everysec : 每秒同步;

    appendfsync no : 不同步。

底层实现之跳表

跳表是一种随机的数据结构,将数据分层,每次提取一些数据节点,有以下的特点:

  • 有很多层组成

  • 每一层都是一个有序的节点

  • 最底层包含所有的元素

  • 如果一个元素出现在某一层,在其下方层中也将出现

  • 每个节点包含两个指针,一个指向当前层的下一元素,一个指向下一层的相同元素。

搜索比较容易,从上往下开始搜索。

跳表的插入,是先随机一个K,K代表要将元素插入的层数,然后再level1 到 level K都插入合适的位置。

基于Redis实现分布式锁和分布式任务队列

  • 分布式锁:主要使用到了Redis中的setNX 命令,意思是set if not exist,即在不存在此key值的时候才能设置该key的value。客户端可以使用setNX foo.lock <current unix time> 来尝试获取锁,当锁已经被别人获取时,setNX 命令将返回0,即不能为该key设置value,反之可以获取该锁。操作完成后使用Del foo.lock 释放锁。有可能一个任务获取了锁但是因为遇到了异常没有释放,此时可能发生死锁问题。解决的方法是为锁加上一个过期时间,当一个线程尝试获取锁失败的时候用当前的时间减去锁之前的value值(value值设为加锁时的时间);使用GET方法过去锁的时间戳,与当前时间对比, 如果超过了过期时间,可以尝试使用GETSET 命令获取锁,同时返回原锁的时间戳,如果此时获取的时间戳与之前GET 获取的时间戳不同,说明在此过程中有其他的任务获取了锁,此任务仍然不能获取锁。

  • 分布式任务队列:使用Sorted-set存储任务,任务ID作为value,当前时间作为score,在入队出队时需要调用分布式锁。

负载均衡

见下一章

MySql优化

Sql及索引

  • 慢查询日志,寻找哪些查询是有效率问题的,MySql提供了一个日志工具,慢查询日志来记录哪些数据库查询是有效率问题的。可以设置查询语句的时间超过某一特定值就记录到慢查询日志中去,以此来分析性能瓶颈。

  • 慢查询日志的的工具

    • mysqldumpslow 工具

    • pt-query-digest 工具

    • explain 查看数据库的索引信息。explain select * from tb_***显示的字段有

      • id:select 查询的序列号

      • select_type:select查询的类型,一般包含SIMPLE、联合查询、子查询等

      • table:输出结果所引用的表

      • type:联合查询所使用的类型

      • possible key:指出MySQL使用哪个索引在该表中找到行。如果是空,代表没有相应的索引

      • key:mysql决定使用的索引

      • ....

  • Sql 语句优化

  • 哪些列建立索引,索引的规则

    • 在where从句,group by从句,order by 从句, on从句出现的列,连接的列。

    • max 函数所使用的列

    • 索引字段越小越好,由于数据库分页存储,减少I/O次数,太大的字段不宜过多的建立索引。

    • 离散程度大的列放到联合索引前面,离散度是指列的不同的程度。

  • 索引优化

    • 索引会增加数据库的写入速度;而且过多的索引会使得数据库在查询时要找到需要使用的索引,这个过程也会降低查询的效率。

    • 删除重复的索引:例如主键和在主键上建立的唯一索引就是重复的,因为InnoDB中主键本身就是一个索引了。

    • 删除冗余的索引:例如在建立联合索引的时候,人为的加上了主键这一列,在InnoDB中,所有的索引都是有主键这一列的。

    • 查找重复及冗余索引的工具:pt-duplicate-key-checker


      pt-duplicate-key-checker -uroot -p '' -h 127.0.0.1
posted @ 2018-02-27 22:33  lzycw  阅读(252)  评论(0编辑  收藏  举报