Java基础-高级

一、线程

1.程序(programm)、进程(process)、线程(thread)

程序:是为完成特定任务,用某种语言编写的一组的指令的集合。指一段静态的代码

进程:程序的一次执行过程,或是正在运行的一个程序

线程:进程可以进一步细化成线程,是一个程序内部的一条执行路径。(例如360可以同时进行木马查杀和电脑清理,这两个是360程序运行的两个线程。 线程作为调度和执行的单位,每个线程拥独立的运行栈和程序计数器(pc),一个进程共享方法区和堆

2.并行与并发

并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事。

并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事

3.创建多线程的两种方式

方式一:继承Thread类的方式

* 1. 创建一个继承于Thread类的子类myThread
* 2. 重写Thread类的run() --> 将此线程执行的操作声明在run()中
* 3. 创建Thread类的子类的对象new myThread
* 4. 通过此对象调用start():①启动当前线程 ② 调用当前线程的run()

方式二:实现Runnable接口的方式

* 1. 创建一个实现了Runnable接口的类
* 2. 实现类去实现Runnable中的抽象方法:run()
* 3. 创建实现类的对象
* 4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
* 5. 通过Thread类的对象调用start()

对比:

* 开发中:优先选择:实现Runnable接口的方式
* 原因:1. 实现的方式没类的单继承性的局限性
*      2. 实现的方式更适合来处理多个线程共享数据的情况。
*
* 联系:public class Thread implements Runnable
* 相同点:两种方式都需要重写run(),将线程要执行的逻辑声明在run()中。
          目前两种方式,要想启动线程,都是调用的Thread类中的start()。

4.线程的同步-synchronized

当过个线程同时操作相同的资源时(买票出现重票,错票),此时应该只允许一个线程操作同步资源加锁,其他线程等待该线程资源锁的释放。

1)同步解决办法:

方式一:同步代码块
*
*   synchronized(同步监视器){
*      //需要被同步的代码
*
*   }
*  说明:1.操作共享数据的代码,即为需要被同步的代码。  -->不能包含代码多了,也不能包含代码少了。
*       2.共享数据:多个线程共同操作的变量。比如:ticket就是共享数据。
*       3.同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。
*          要求:多个线程必须要共用同一把锁。
*
* 补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。
       在继承Thread类创建多线程的方式中,慎用this充当同步监视器,考虑使用当前类充当同步监视器。

*  方式二:同步方法(不一定在线程类中定义,可以在普通类中定义同步方法)
*     如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步的。

*  关于同步方法的总结:
*  1. 同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。
*  2. 非静态的同步方法,同步监视器是:this
*     静态的同步方法,同步监视器是:当前类本身

   方式三:Lock锁  --- JDK5.0新增

Lock需要手动的启动同步(lock(),同时结束同步也需要手动的实现(unlock())

2)死锁的理解:

不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁.

二、常用类String\StringBuffer\StingBuilder

主要用于其方法,用于算法中的字符串

1.String实例化的不同方式

方式一:通过字面量定义的方式,数据储存在方法区的字符常量池中

方式二:通过new + 构造器的方式,此时遍历是指的堆空间的地址值

常量与常量的拼接结果在常量池。且常量池中不会存在相同内容的常量。

只要其中一个是变量,结果就在堆中。

如果拼接的结果调用intern()方法,返回值就在常量池中

2.StringBuffer\StingBuilder是可变长的字符串

StringBuffer、StringBuilder中的常用方法:
增:append(xxx)
删:delete(int start,int end)
改:setCharAt(int n ,char ch) / replace(int start, int end, String str)
查:charAt(int n )
插:insert(int offset, xxx)
长度:length();
*遍历:for() + charAt() / toString()

三、枚举类和注解

1. 枚举类的说明

创建类时,在类中直接实例化出有限个类对象,是以常量的类形式存在
* 1.枚举类的理解:类的对象只有有限个,确定的。我们称此类为枚举类
* 2.当需要定义一组常量时,强烈建议使用枚举类
* 3.如果枚举类中只一个对象,则可以作为单例模式的实现方式。

2.注解的使用

注解作用:说明程序的(给计算机看)

注解可以理解成一个标签,是给类、方法、变量、属性等加标签;

这个标签还会有一些限制因素,java设计出元注解来产生这些限制因素,元注解也是标签,只是这些标签是来描述注解的原始标签。

四、集合-Collection接口(list\set)Map接口

1.单列集合框架结构Collection

 常用实现类:

|----Collection接口:单列集合,用来存储一个一个的对象
*  |----List接口:存储序的、可重复的数据。  -->“动态”数组,替换原的数组
*      |----ArrayList:作为List接口的主要实现类;线程不安全的,效率高;底层使用Object[] elementData存储
*      |----LinkedList:对于频繁的插入、删除操作,使用此类效率比ArrayList高;底层使用双向链表存储(模拟队列和栈时使用,底层是双向链表
*      |----Vector:作为List接口的古老实现类;线程安全的,效率低;底层使用Object[] elementData存储

|----Collection接口:单列集合,用来存储一个一个的对象
*          |----Set接口:存储无序的、不可重复的数据   -->高中讲的“集合”
*              |----HashSet:作为Set接口的主要实现类;线程不安全的;可以存储null值
*                  |----LinkedHashSet:作为HashSet的子类;遍历其内部数据时,可以按照添加的顺序遍历
*                 在添加数据的同时,每个数据还维护了两个引用,记录此数据前一个数据和后一个数据。                   对于频繁的遍历操作,LinkedHashSet效率高于HashSet.
*              |----TreeSet:可以照添加对象的指定属性,进行排序。

2.list接口常用方法-增删改查

增:add(Object obj)
删:remove(int index) / remove(Object obj)
改:set(int index, Object ele)
查:get(int index)
插:add(int index, Object ele)
长度:size()
遍历:① Iterator迭代器方式
     ② 增强for循环
     ③ 普通的循环

3.HashSet元素添加过程(不可重复的值)

我们向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值
此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置(即为:索引位置,判断
数组此位置上是否已经元素:

  •     如果此位置上没其他元素,则元素a添加成功。 --->情况1
  •     如果此位置上其他元素b(或以链表形式存在的多个元素,则比较元素a与元素b的hash值:

        如果hash值不相同,则元素a添加成功。--->情况2
        如果hash值相同,进而需要调用元素a所在类的equals()方法
               equals()返回true,元素a添加失败
               equals()返回false,则元素a添加成功。--->情况3

对于添加成功的情况2和情况3而言:元素a 与已经存在指定索引位置上数据以链表的方式存储。
jdk 7 :元素a放到数组中,指向原来的元素。
jdk 8 :原来的元素在数组中,指向元素a
总结:七上八下

HashSet底层:数组+链表的结构。(前提:jdk7)

4.双列集合框架Map实现类结构

|----Map:双列数据,存储key-value对的数据   ---类似于高中的函数:y = f(x)
*       |----HashMap:作为Map的主要实现类;线程不安全的,效率高;存储null的key和value
*              |----LinkedHashMap:保证在遍历map元素时,可以照添加的顺序实现遍历。
*                    原因:在原的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素。
*                    对于频繁的遍历操作,此类执行效率高于HashMap。
*       |----TreeMap:保证照添加的key-value对进行排序,实现排序遍历。此时考虑key的自然排序或定制排序
*                      底层使用红黑树
*       |----Hashtable:作为古老的实现类;线程安全的,效率低;不能存储null的key和value
*              |----Properties:常用来处理配置文件。key和value都是String类型
*
*
*      HashMap的底层:数组+链表  (jdk7及之前)
*                    数组+链表+红黑树 (jdk 8)

储存结构

>Map中的key:无序的、不可重复的,使用Set存储所的key  ---> key所在的类要重写equals()和hashCode() (以HashMap为例)
>Map中的value:无序的、可重复的,使用Collection存储所的value --->value所在的类要重写equals()
> 一个键值对:key-value构成了一个Entry对象。
>Map中的entry:无序的、不可重复的,使用Set存储所的entry

5.Map常用方法

* 添加:put(Object key,Object value)
* 删除:remove(Object key)
* 修改:put(Object key,Object value)
* 查询:get(Object key)
* 长度:size()
* 遍历:keySet() / values() / entrySet()

6.HashMap内存储存

HashMap在jdk7中实现原理:
HashMap map = new HashMap():
*      在实例化以后,底层创建了长度是16的一维数组Entry[] table。(此时table是一个类有两个属性key-value)
*      ...可能已经执行过多次put...
*      map.put(key1,value1):
*      首先,调用key1所在类的hashCode()计算key1哈希值,此哈希值经过某种算法计算以后,得到在Entry数组中的存放位置。
*      如果此位置上的数据为空,此时的key1-value1添加成功。 ----情况1
*      如果此位置上的数据不为空,(意味着此位置上存在一个或多个数据(以链表形式存在)),比较key1和已经存在的一个或多个数据的哈希值:
*              如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1添加成功。----情况2
*              如果key1的哈希值和已经存在的某一个数据(key2-value2)的哈希值相同,继续比较:调用key1所在类的equals(key2)方法,比较:
*                      如果equals()返回false:此时key1-value1添加成功。----情况3
*                      如果equals()返回true:使用value1替换value2。
*
*      补充:关于情况2和情况3:此时key1-value1和原来的数据以链表的方式存储。
*
*     在不断的添加过程中,会涉及到扩容问题,当超出临界值(且要存放的位置非空)时,扩容。默认的扩容方式:扩容为原来容量的2倍,并将原的数据复制过来。

HashMap在jdk8中相较于jdk7在底层实现方面的不同:

1. new HashMap():底层没创建一个长度为16的数组
2. jdk 8底层的数组是:Node[],而非Entry[]
3. 首次调用put()方法时,底层创建长度为16的数组
4. jdk7底层结构只:数组+链表。jdk8中底层结构:数组+链表+红黑树
4.1 形成链表时,七上八下(jdk7:新的元素指向旧的元素。jdk8:旧的元素指向新的元素)
4.2 当数组的某一个索引位置上的元素以链表形式存在的数据个数 > 8 且当前数组的长度 > 64时,此时此索引位置上的所数据改为使用红黑树存储。

红黑树是具有下列着色性质:

  • 每个节点着色成红色,或者黑色
  • 根是黑色的
  • 如果一个节点是红色的,那它的子节点必须是黑色的
  • 从一个节点到一个null引用的每一条路径必须包含相同数目的黑色节点

五、反射

Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。

1.类的加载过程

程序经过javac.exe命令以后,会生成一个或多个字节码文件(.class结尾)。接着我们使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件加载到内存中。此过程就称为类的加载。加载到内存中的类,我们就称为运行时类,此运行时类,就作为Class的一个实例。

2.获取Class实例的几种方式:(前三种方式需要掌握)

Class实例也是对象,因为java万事万物皆对象,反射是获取的运行时类的对象

  • 方式一:调用运行时类的属性:.class

        Class clazz1 = Person.class;

  •  方式二:通过运行时类的对象,调用getClass()

        Person p1 = new Person();
        Class clazz2 = p1.getClass();

  •   方式三:调用Class的静态方法:forName(String classPath)

     Class clazz3 = Class.forName("com.atguigu.java.Person");
      clazz3 = Class.forName("java.lang.String"); 

  •   方式四:使用类的加载器:ClassLoader  (了解)

        ClassLoader classLoader = ReflectionTest.class.getClassLoader();
        Class clazz4 = classLoader.loadClass("com.atguigu.java.Person");
     

3.动态代理

静态代理的缺点:
① 代理类和目标对象的类都是在编译期间确定下来,不利于程序的扩展。
② 每一个代理类只能为一个接口服务,这样一来程序开发中必然产生过多的代理。

动态代理是指客户通过代理类来调用其它对象的方法,并且是在程序运行时根据需要动态创建目标类的代理对象。

问题一:如何根据加载到内存中的被代理类,动态的创建一个代理类及其对象。 
(通过Proxy.newProxyInstance()实现)
问题二:当通过代理类的对象调用方法a时,如何动态的去调用被代理类中的同名方法a。
(通过InvocationHandler接口的实现类及其方法invoke())

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值