zoukankan      html  css  js  c++  java
  • JAVA基础知识复习小结

    集合

    Set集合

    • Set集合的基本特征是元素不允许重复。HashSet不保存元素顺序,LinkedHashSet用链表保持元素的插入顺序,TreeSet可定制排序规则。
    • HashSet的底层是用HashMap实现的,即HashMap<key, value>中把所有value置为null,key就组成了一个Set
    • HashSet把元素的hashCode值作为地址索引来存储元素,可以实现类似根据数组下标索引查找元素的效果,这是HashSet访问速度快的原因
    • HashSet中如果两个元素通过equals比较结果为true,但是两个元素的hashCode不相等,即在两个地方存放了值相等的两个元素,HashSet将会出现各种奇怪问题,不能正常工作。
    • 基于上面第4点,HashSet元素判断两个元素相等的标准是,不仅需要equals比较的结果为true,而且需要元素hashCode值相等,即不允许equals比较为true但是hashCode不想等的两个元素存放在HashSet中。
    • HashSet中允许equals比较为false但是hashCode相等的两个元素同时存在,这两个元素将会被存放在同一个位置,并用链表维持两个元素顺序,但是这将严重影响HashSet的性能。
    • 基于上面4.5.6点,如果需要重写一个HashSet的equals方法,一定也要重写hashCode方法,原则是如果equals方法和equals方法使用共同的变量(成员变量)做计算,使得如果equals为true,hashCode结果值要相等。
    • LinkedHashSet底层多了一个链表结构用来保存元素的插入顺序(插入位置还是由hashCode决定的),遍历LinkedHashSet时会自动根据链表来遍历

    List集合

    • List集合的基本特征是元素有序,可重复,每个元素都有顺序索引。因此List集合可以像数组一样使用。
    • List集合有一个专用迭代器ListIterator,可以实现反向迭代。
    • ArrayList和Vector底层使用一个智能数组实现,可动态扩展(ensureCapacity(int i)重新分类空间),
    • 在java.util.Arrays 的内部也定义了一个ArrayList (通过asList()方法返回),但这是一个不可变定长数组,不能增加,删除数组元素,否则会抛出异常。
    • Vector是一个古老的集合实现类,Vector所拥有的功能ArrayList基本都有。但是Vector是一个线程安全类,性能上会比ArrayList稍低。另外Vector还有一个子类Stack,实现了栈结构。
    • LinkedList 是List的实现类,同时又是Deque的实现类,因此LinkedList同时具有ArrayList(随机存取)和ArrayDeque(双端队列, 栈)的功能。但是LinkedList的内部实现完全不同,LinkedList内部使用链表实现,虽然也LinkedList表面上使用的是index 索引数组方式的随机访问,但是内部实现的时候使用index关联了链表的顺序,依然使用的是迭代访问,所以性能上比ArrayList差。不过插入和删除 性能更好。

    Map集合

    • Map 的基本特征是key不重复。key和set存在单向一对一关系。
    • Map接口中有两个重要的数据结构,一个是keySetp,用Set集合保存了所有key;一个是内部类Entry,用来封装key-value对,每个元素对应一个Entry对象,保存在Map的全局数组transient Entry table[];中。
    • Map的entrySet()方法可以返回一个Entry对象组成的的Set集合的视图。注意entrySet中并没有使用一个Set对象来保存所有Entry集合,而是定义一个EntrySet内部类,其中有个iterator()方法可以迭代访问map的所有Entry对象,调用EntrySet的iterator()方法就相当于得到了Entry集合的视图。
    • HashMap中,每次put进一个新元素时,都会在底层new一个Entry类来关联key-value,并将Entry对象保存在Map的全局数组transient Entry table[];中
    • 由上面可知遍历HashMap至少有四种方法。1)集合通用方法value = iterator.next。 2)map常规方法 value=map.get(key).  3)遍历entrySet集合。  value=map.entrySet.getValue(). 4)遍历values集合 value=map.values...
    • HashMap和Hashtable的关系,可以类比HashSet跟Vector.  Hashtable是一个古老的,线程安全的集合。
    • HashMap跟HashSet判断元素是否相等的标准一样,都是需要同时满足equals为true,且元素hashCode值相等才认为是相同元素。 equals为true但hashCode不相等的两个元素可以存入map中,但是不能正常工作;equals为false但hashCode相等会认为是不同元素,存放在同一个地方,用链表关联value,性能低。
    • LinkedHashMap跟linkedHashSet一样,也用(双向)链表维护元素的插入顺序(key顺序)。

    JAVA 网络通信模型

    通信步骤(系统调用recvfrom():1)等待数据,2)拷贝数据

    五种通信模型

    • 1、阻塞式IO: 等待整个通信步骤完成(等待数据和拷贝数据)并返回到应用程序,期间应用程序无法进行其他操作,就是阻塞式IO。
    • 2、非阻塞IO: 在等待数据期间,程序轮询操作系统,直到数据就绪,停止轮询,开始拷贝
    • 3、IO复用(多路复用):通过select()函数同时监听(轮询)多个IO,只要有一个可用(数据就绪),就执行IO操作。
    • 4、信号驱动模式:发起IO请求时注册一个信号,操作系统数据就绪之后发起信号,应用程序就可以进行IO操作。与IO复用的区别是只能对一个IO进行监控。
    • 5、异步IO: 与阻塞式IO完全相反,就是整个通信步骤都交给操作系统,操作系统完成所有操作(等待和拷贝)再通知应用程序。与信号驱动的区别是信号驱动在数据就绪后就通知应用程序,而异步IO是在数据拷贝完成后才通知应用程序。

     

    JAVA IO

    传统IO

    按Unix网络通信标注,主要分成基于字节的IO和基于字符的IO,常用传统的IO操作类及特性如下,

    • 文件IO:FileInputStream/FileOutputStream, FileReader/FileWriter,这四个类都直接操作文件流,与OS底层直接交互,只是操作数据的单位不同。
    • 包装流:包装其他类型的数据流,简化编程。PrintStream用来包装字节流OutputStream。PrintWriter即可以包装字节流OutputStream又可以保证字符流Writer。Scanner,用来包装键盘输入。
    • 转换流:InputStreamReader/OutputStreamReader, 是字节流与字符流转换的桥梁,经常在处理键盘输入或者网络通信的时候用这两个类。
    • 缓冲流:BufferReader/BufferWriter, BufferInputStream/BufferOutputStream, 减少系统调用的次数,提升性能。 另外BufferReader还是常用的行读取器,因为它能将Reader包装后,用readline()方法按行读取。
    • 字符串流:StringReader/StringWriter,可以将字符在String与char之间转换,其中StringWriter是将字符写入StreamBuffer(因为String不可写)

     不同类别的传统IO的应用场景:

    • FileInputStream/FileOutputStream, 当需要逐个处理二进制流的时候使用
    • FileReader/FileWriter, 当需要逐个处理字符的时候使用
    • StringReader/StringWriter, 需要将String转换成char数组的时候使用
    • PringStream/PrintWriter, 需要包装FileOutputStream,直接将String写入文件的时候
    • Scanner,需要处理键盘输入(System.in)的时候
    • InputStreamReader/OutputStreamReader,  需要处理键盘输入或者网络通信的时候,对字节流进行转换
    • BufferReader/BufferWriter, BufferInputStream/BufferOutputStream, 包装字节流后者字符流,提升性能。

    NIO

    JDK1.4开始引入了NIO,NIO即New IO, NIO主要有两方面的变化,

    • 一是引入Channel和Buffer概念,实现了与传统IO完全不同的IO方式。传统IO在OS底层都是按字节操作,但是NIO可以通过Channel将一块或全部数据直接映射成Buffer,实现按块操作。

    Buffer类似一个数组,不过在IO处理上比数组方便点,通过position和limit两个指针,可以防止越界存取。

    Channel必须结合Buffer一起使用,主要用来做内存映射。

    • 二是实现了非阻塞通信(Non Blocking)——Unix中的多路复用通信模型

    主要是在网络通信中,引入了selector概念,通过selector的select()方法可以监听IO请求(channel),当监听到有请求时就返回对应的channel建立通信。这里所说的非阻塞通信对应到Unix通信模型中的多路复用模型。

    NIO的多路复用通信模型解决了传统多线程网络通信时候需要在服务器端也创建多线程的问题, 多路复用可以同时监听多个请求, 有可用的请求则依然用当前线程(或者仅仅创建一个线程)去处理。

    目前流行的NIO框架有Apache的Mina和JBoss的Netty, 是基于线程池(简单的负载均衡,而不是为每个请求创建线程)的多路复用通信模型。

    NIO.2

    JDK7,是在NIO的基础上做了两方面改进。

    一个是基于异步channel的IO(即AIO),在java.nio.channels包下新增了多个以Aysnchronous开头的接口和类,

    另一个是增加了一个接口两个工具(Path接口,Paths工具,Files工具),在兼容性和操作性上面得到提升。

     socket通信

    三次握手:1)客户端向服务器发送建立连接的请求。2)服务器回应客户端。3)客户端再次回应服务器。

     

    多线程

    线程声明周期

    线程的五个状态:新建,就绪,运行,阻塞,死亡。 其中就绪和运行两个状态客户互相转换,但运行到阻塞,阻塞到就绪,只能单向转换。

    刚new出的线程就是【新建】状态,调用start之后就是就【绪状】态,获取CUP资源并执行后就是【运行】状态,CUP用完/sleep()/调用阻塞IO/被suspend()挂起/正在获取其他线程同步监视器/等待通知等这些行为都有可能让线程从【运行】进入【阻塞】状态。

    创建线程三种方式及对比

    • 继承Thread类创建线程
      需要写Thread的子类,并重写其中的线程执行体run()方法,通过start()方法来启动线程。
    • 实现Runable接口创建线程类
      需要在实现类中重写run()方法,实现类的实例将做为target对象,通过new Thread(target, "thread name").start()启动线程。
    • 使用Callable接口结合Future接口
      Callable中的call()方法作为线程执行体,Future的实现类FutureTask(同时实现了Runable)的实例作为target,初始化FutureTask时传入Callable实例,通过new Thread(target,"thread name").start()启动线程。

    优劣对比

    • 采用继承Thread类的方法编程相对简单,但无法再继承别的类,且多个线程之间无法共享变量。
    • 采用Runable接口方法可以同时继承其他类,但是无法获取线程的返回值,也不能抛出异常。
    • 采用Callable结合Future的方法虽然编程比较复杂,但是即不受继承限制,又可以获取多线程的返回值,还能抛出异常,因此是最常用的实现多线程的方法。

    控制线程

    join()等待别的线程:在某个线程中调用其他线程的join()方法,就会让当前线程进入阻塞,直到被join线程执行完毕。通常会在主线程中调用join()方法,这样可以保证所有子线程都结束了,主线程才结束。

    setDaemon():在start()之前调用线程自身的setDatemon()可以设置成后台线程:如果所有前台线程都死亡,后台线程会自动死亡。

    sleep(): 通过Thread.sleep() 可以让当前线程进入【睡眠】状态,在睡眠时间到达之前,即使有可用CUP,线程也无法执行,醒来的线程也只能进入就【绪状】态。

    yield():通过Thread.yield()可以设置线程的优先级(用数字或者常量),使线程进入【就绪】状态,相当于暂停线程。只有优先级等于或高于当前线程的线程,才有可能获取CUP资源执行,否则当前线程将继续执行。

    线程同步

    是为了解决经典的生产者消费者问题(保证事务处理的原子性,例如多个线程向同一个账户存钱取钱,存钱和取钱各自都需要是一个原子操作)。

    线程同步的方法,

    • 同步代码块。即将需要同步的代码放在 synchronized(obj){}的大括号中,使得线程要执行同步代码块之前,需要先获取同步监视器(的锁定),执行完之后,需要释放同步监视器。
    • 同步方法。使用synchronized修饰整个方法(不能修饰static方法),同步监视器就是this,当在线程执行体run()或call()中调用这个方法的时候,需要获取到同步监视器的锁定(即同步方法所在类的实例)的线程才能执行同步方法。
      注意sleep(), yield(), suspend()方法并不会释放同步监视器。
    • 同步锁。最常用的ReentrantLock锁,是一种更灵活的同步方式,线程需要先加锁,之后加锁成功的线程才能执行代码,之后需要解锁。使用try ... finally 可以保证锁释放。

    死锁

    两个线程互相等待对方(释放同步监视器),就会进入死锁。

    例如线程1线锁定A对象,接着睡眠,线程2锁定B对象,也睡眠,然后线程A醒来,如果此时去请求B对象监视器,A将被阻塞,如果B醒来也去请求A监视器,B也将阻塞,并且A和B此时各自持有一个监视器又在互相请求对方监视器,将进入死锁。

     线程通信

    线程通信可以保证线程执行的先后顺序。

    通常有三种通信方式,

    • 传统的线程通信,使用wait(), nofity(),nitifyAll(),适用于使用同步代码块和同步方法的同步线程中。
    • 使用lock对象返回的condition对象控制线程通信。condition也包含三个方法,await(),signal(),signalAll()。 condition方法使用与使用lock进行线程同步的情况中。
    • 使用阻塞队列(BlockingQueue)控制线程通信。其特征是,当队列满的时候,生产者线程阻塞,此时只有消费者线程能执行代码;当队列空的时候,消费者线程将阻塞,此时只有生产者线程能执行代码。在线程非空且未满的时候,生产者和消费者都可以执行。这种方式更灵活,能通过队列容量控制线程切换。

    数据库线程池

    传统的connection需要通过DriverManager获取,每一次操作都需要打开一个连接,使用完毕又需要关闭,下次使用需要重新打开,这样性能开销大。

    而数据库连接池为了解决这个问题,一次性创建一批连接,每次使用时从连接池获取一个连接,使用完关闭(放回连接池)即可。

    weblogic, websphere等商用服务器提供了连接池, Apache等开源组织也提供了连接池,常用链接池有DBCP(Apache提供,tomchat使用),CP30(Hibernate使用)。

    使用连接池的一般步奏是,1)创建连接池对象,2)配置连接池,3)通过连接池对象获取一个连接并使用,4)关闭连接(将连接放回连接池)

    动态代理(AOP)

    代理模式及应用场景

    当需要访问类A的某个方法时,不是直接通过A的实例去访问,而是通过类B的实例去访问A的方法,这就是代理模式,代理分为静态代理和动态代理。

    应用场景:例如创建A需要很大开销,我们用代理类(B)类来代替A类,只有真正需要使用到A中的方法时,才会通过B创建A类。 又或者A在远程Server上,调用者没有权限访问Server,而代理类B却有,于是通过B调用A。 又或者我们需要增强A中方法的功能,但是又不想修改A或者根本没有权限修改,于是在代理类B中添加功能再调用A,

    AOP在J2EE中的常见应用场景:打印日志,性能监控,事务控制,拦截器,权限控制等。

    静态代理和动态代理

    -静态代理

    代理类B和委托类A实现相同的接口,在B中可以添加额外的逻辑,之后再调用A的方法。 代理类是程序员实现已经编写好,在编译期间就已经知道需要调用委托类A的具体方法,因此这叫静态代理。

    静态代理实现了调用者与委托类的解耦,可以隐藏委托类的实现,在某些场合可以提高性能。

    缺点

    • 一是增加了冗余代码,因为委托类和代理类都需要实现相同接口,当接口改变时,委托类和代理类都需要改变。
    • 二是静态代理类只能代理一种委托类,假如有第二种委托类需要代理,就需要实现第二种代理类,当系统规模大到一定程度的时候,就会产生许多代理类。

    -动态代理

    动态代理是在程序运行的时候,才根据传入的委托类参数,通过反射获取接口/委托类的信息并动态地生成代理对象,然后invoke()方法可以调用委托类的方法。

    目前主要有两种方式实现动态代理,一种是JDK自己实现的基于接口的动态代理,另一种是开源项目cglib实现的扩展委托类(继承)来实现的,不需要实现接口。

    -JDK实现的基于接口的动态代理主要涉及反射类Proxy和InvocationHandler,我们希望通过Proxy来访问委托类方法,而Proxy则将访问请求交给InvocationHandler,我们需要实现InvocationHandler 接口,重写其中的invoke()并在invoke中通过method.invoke()就可以访问委托类的方法(反射),然后通过Proxy调用invoke()。

    -而cglib的实现方式则是通过反射方式动态生成委托类的子类,覆盖委托类中的方法并进行功能扩展。通过这种方式,代理类和委托类都不需要实现统一接口,但是代理类因为是通过生成委托类子类方式实现,如果委托类是一个finnal类就不行了。

     JAVA反射

    Java 程序运行机制

    1.JVM

    某个Java程序被调用时,java命令会先启动一个JVM,所以程序都在JVM中进行。

    2.类加载

    当程序需要使用某个类时,会先使用类加载器将类的.class文件加载进JVM, 生成一个java.lang.Class实例。通常可以从本地,或者JAR(例如JDBC),或者网络加载class文件。

    java.lang.Class实例

    当类被加载之后,JVM中就会生成一个Class实例,通过这个实例就可以访问JVM中的这个类。有三种方式可以获取Class对象(Class.forName(包名) , 类名.class , 对象.getClass() )

    从Class实例中,可以获取类的所有信息,包括属性,方法。

    3.类连接

    负责将类的二进制数据合并到JRE

    4.类初始化

    反射:程序依靠运行时信息来发现该对象和类的真是信息,需要使用反射。即通过java.lang.Class对象获取类的信息调用类的属性或者方法。

  • 相关阅读:
    MyBatis 核心配置综述之 ResultSetHandler
    MyBatis 核心配置综述之 ParameterHandler
    MyBatis 核心配置综述之StatementHandler
    高等数学——手撕牛顿莱布尼茨公式
    用二分法优化动态规划——实现快速决策
    召回、精确、准确,这些让人头大的概念一文全讲清楚
    Python——详解__slots__,property和私有方法
    LeetCode50——一题学会快速幂算法
    Golang——详解Go语言代码规范
    spark——详解rdd常用的转化和行动操作
  • 原文地址:https://www.cnblogs.com/fysola/p/6281687.html
Copyright © 2011-2022 走看看