Java复习(一)
JAVA基础易错总结
面向对象
写程序时沿着思路写一段 然后再写一段进行补充 最终完成
1 static静态变量可以起到计数作用 根据栈stack 堆heap 数据区data seg的分析(内存分析)来得出结论
2 final和abstract只能有一个 因为final是最终类 不能继承 必须创建实例 而abstract是抽象类 只能继承 不能实例
3 1.7接口只能定义抽象方法 即所有非抽象方法都必须在类内定义 1.8中接口可以定义default和static方法 1.9中接口可以定义私有方法
4 数组引用类型变量的默认值为null 当实例化的时候 若没有赋值 java就会把元素初始化为相应类型的默认值
5 定义在类中的变量是成员变量 可以自动初始化 局部变量是方法中的变量 必须要初始化 否则编译不成功
6 被static关键字修饰的变量是静态的 静态变量随着类的加载而加载 所以被称为类变量
7 被final修饰的变量是常量
8 java的立即回收不是在程序结束后就回收的 在程序执行过程中就会回收 如对象赋值为null 该对象成为回收对象
9 static属于类 在编译阶段就已经确定函数名和地址 并已经分配了内存 所以不存在运行期多态 它是一个静态绑定
无法重写
10 基本类型初始化赋值为0 包装类初始化赋值为null
11 抽象类和接口都不可以实例化 final类不能被继承 abstract不能与final同时修饰一个类
12 抽象类中可以没有抽象方法 但有抽象方法的类一定是抽象类
13 构造函数的作用是完成对象的初始化 new对象 和 反射用到了构造方法 构造方法和类同名 无返回值
14 动态绑定是只有运行起来 实际对象才能指向相应的方法 方法是重写了的方法
15 动态绑定是指一个类调用其他类的引用对象及其名字 动态地指向其他类的方法(自己理解)
16动态绑定是指在执行期间 判断引用对象的实际类型 根据其实际类型调用其相应的方法
17 多态要有三个必要条件: 继承 重写 父类引用指向子类对象
18 抽象类必须被继承 被重写 抽象类不能被实例化 它只需声明 不需实现(实现的意思是写具体的方法)
19 接口是一种特殊的抽象类 它是抽象方法和常量值定义的集合 而没有变量和方法的实现 接口之间可以相互继承
20 接口应用多态时 前面是接口的对象 后面new一个类对象 则调用方法时只能看到前面那个接口的方法
21 父类的引用变量指向子类变量时 子类对象向父类对象 向上转换 子类向父类的转换不需要什么限制
22 在父类调用子类特有的 不是从父类继承来的方法和变量时 需要父类变量向子类转换的称为向下转换
23 子类的构造方法总是先调用父类的构造方法 若子类构造方法没有明显的指明使用父类哪个构造方法 则调用父类不带参数的构造方法 若父类没有无参构造方法 子类需要在自己的构造函数中显示调用(super)父类构造函数 否则会报错
24 final除了能读取类成员变量 还能读取类变量 其修饰的成员变量只能赋值一次 可以在类方法中赋值 也可以在声明时赋值
常用类
1 初始化的时候 new后面的数组括号内传入的是数组大小即最大存储个数 而不是最大下标 注意下标是从0开始的
2 有一个String构造方法 里面是char字符 String(char[] value, int offset, int count) 意为截取字符的开始及结束
3 String的其他方法 charAt(1) 里面是数字 若为1 则表示显示出第几个字符
4 String的其他方法indexOf("java")表示出现java的第一个位置
5 String的其他方法 subString() 表示从第几个字符开始截取一直到最后
6 String的其他方法String valueOf()可以将基本数据类型转换成字符串
7 String的其他方法split(";") 例如使用分号隔开
8 StringBuffer可变的方法 append(添加),insert(1,9)(插入 从第0开始插入到第9), delete(1,9)
9 parseInt() 函数可解析字符串 并返回整数
10 还有Math和file的各种方法
11 String StringBuffer StringBuilder都是final类型的 但是StringBuffer StringBuilder可以改变内容 原因:final修饰的成员变量为基本数据类型时,赋值后无法改变 当修饰为引用变量时 赋值后其指向地址无法改变 但对象内容可以改变
12 基本类型用 == 时 比较的是里面的数字 但包装类型比较的是地址 需要注意的是 包装类数字 1-127地址是一样的 但以后的地址是不一样的 底层用的是new 包装类 == 比较的是地址
容器
1 Collection接口包含子接口set(Hashset), list(LinkedList)(ArrayList) Map存储键值对的方法包含(HashMap)
2 Set 无顺序 不可重复 List 有顺序 可重复 互相equals就可以重复
3 找人事信息时 调用对象的hashcode() 相当于地址 找到对应的值 哈希算法可以实现
4 哈希码相等的两个对象的equals方法比较的结果可以不等
5 两个对象的equals方法相同 hashcode方法也必须相等 确保相同对象存储在相同位置上
6 问题 为什么要重写hashcode方法: 一开始是重写equals方法 比较内容相等 若不重写hashcode方法 则其保留的内容不变 当用其中一个键保留到容器中 再以相同的键值去查找 根本找不到 但重写之后可以保证相同对象返回相同的hash值
7 利用hashcode方法可以根据对象的内存地址换算成一个值 当寻找或添加新元素时 调用其方法 可以一下子定位到物理位置上 而不用使用equals来一个一个比较
8 Iterator对象迭代器 实现遍历元素的操作 就像一个指针 指在对象左边 方法 boolean hasnext()判断右边是否有元素 object next() 移动指针 void remove 删除指针左边的元素
9 泛型Generic 在定义集合时同时定义对象类型 List<> Collection<> Map<key,value> 用到集合时尽量使用泛型
10 一个图 一个类(Collections) 六接口 (除了上述四个接口 还有Iterator Comparable)
IO流
线程
1 线程是一个程序里不同的执行路径 进程是一个静态的概念 实际上运行的是线程
什么是进程呢 进程是指系统中正在运行的一个应用程序 程序一旦运行就是进程
2 在一个时间点上 CPU只有一个线程在运行
3 线程实现: 继承Thread类来创建线程 在重写run方法里 写什么东西就执行什么东西 通过start() 方法来启动线程。 若用Runnable接口实现线程 启动线程必须在主方法里new Thread(把线程对象传进去) start方法启动后 主线程和其他线程交替执行
若只使用run方法 只能等其他线程执行完毕
第二个就是实现Runnable接口 只定义一个方法run() (推荐)
4 线程状态:创建 就绪 阻塞 运行 终止
5 实现Runnable接口相对于继承Thread类来说 有如下优势:
1 适合多个相同代码的线程同时处理同一个资源
2 可以避免单继承带来的局限性
3 增强程序的健壮性 代码能被多个线程共享 代码与数据是独立的
6 java支持单继承 只能继承一个类 但可以实现多接口 所以使用Runnable增强了可扩展性
多线程
1 多线程:指在单个程序中可以同时运行多个不同的线程 执行不同的任务
2 线程池:就是创建若干个线程放入一个池中 有任务需要处理时 会提交到线程池中的任务队列 处理完之后线程并不会被销毁 而是仍然在线程池中等待下一个任务
3 为什么创建线程池:Java中创建一个线程 需要调用操作系统api 之后还要给线程分配资源 成本高 使用资源大 对性能产生了影响 线程池避免了频繁的创建与销毁
4 newFiexedThreadPool(int Threads):创建固定数目线程的线程池。
5 设计龟兔赛跑:1 赛道 2 兔子和乌龟两个线程 3 判断比赛是否结束 4 打印胜利者 5 模拟兔子睡觉 6 乌龟获胜
6 静态代理模式总结:真实对象和目标代理对象都要实现同一个接口 代理对象要代理真实对象
好处:代理对象可以做很多真实对象做不了的事情 真实对象只需要专注自己的事情即可
7 线程休眠:sleep 线程礼让:yield (不一定成功 看cpu心情) 线程强制执行:join 守护线程:daemon(保证正常线程执行完毕)
8 线程同步:同一个对象被多个线程访问 比如同一个资源 学生排队(队列)吃饭
锁:保证线程执行时保证安全
9 线程同步的形成条件:队列+锁
10 同步方法: synchronized方法和synchronized方法块 一旦执行 就只能等方法执行完毕才能出来
11 synchronized方法锁的是类本身 synchronized方法块锁的是变化的量 需要增删改的量
12 测试JUC 安全类型的集合 CopyOnWriteArrayList<>()
13 死锁: 一个同步块同时拥有两个以上对象的锁时,就有可能发生死锁问题 通俗的说 :多个线程互相抱着对方的资源,互相僵持
14 Lock锁:其功能是和synchronized是一样的 需创建ReentrantLock可重入锁 调用加锁lock.lock 解锁lock.unlock来实现 性能高 显示锁 手动开关锁
15 生产者消费者模型:1 利用缓冲区解决:管程法 2 通过标志位boolean解决:信号灯法
正则表达式(字符串处理利器)
m.reset 恢复到正常的匹配
matches匹配整个字符串
find找子串
lookingAt每次从头上找
compile 把正则表达式编译一下
pattern 字符串要被匹配的模式
matcher 来匹配输入进来的字符串
7 确定电话号码时用到的while 配套continue(终止当次循环) break(跳出循环)
8 记住这个要求 Pattern p = Pattern.compile("^1[389645]\d{9}$"); ^(起始字符串) $(终止字符串)
网络编程
注解与反射
1 java在运行时结构不可变 但可以利用反射机制获得类似动态语言的特性
2 反射过程:实例化对象----getClass方法-----得到完整包类名称
3 加载完类之后 在堆内存方法区中就产生了一个class类型的对象 这个对象可以看到完整的类的结构信息 称为反射
4 一个类在内存中只有一个class对象
5 java反射的源头:class类
JavaWeb
1 Servlet: 开发动态web的技术 把实现Servlet接口的java程序叫做Servlet
2 编写一个Servlet程序:1 编写一个普通类 2 实现Servlet接口 直接继承HttpServlet
3 为什么编写Servlet映射?
我们写的是java程序 但是要通过浏览器访问 而浏览器需要连接web服务器 需要在web服务器中注册Servlet 还需要给浏览器一个需要访问的路径 需要在web.xml中配置
Spring
IOC
1 使用spring:spring作为容器管理对象 开发人员从spring中获取要使用的对象 spring属于业务层 管理service
2 步骤:创建对象需要类 自己去定义接口和实现类
创建spring配置文件 作用:声明对象 把对象交给spring创建和管理
使用
使用容器中的对象 创建一个表示容器中的对象 ApplicationContext
从容器中 根据名称获取对象 使用getbean("对象名称")
3 spring根据xml文件
Spring内容不一一阐述
下面复习高频考点 JVM底层原理
JVM
概念:jvm是java虚拟机 它是java运行环境的一部分 是用来解析和运行java程序的 java编译的语言程序只需生成在jvm上运行 就可以在多平台上运行
构成:主要由字节码指令集 寄存器 栈 垃圾回收堆和存储方法等构成
结构体系:每个jvm都有两种机制
一 :类装载子系统(类加载器):装载具有适合名称的接口或类 加载器分为 启动类 扩展类 系统类加载器
类加载器不仅加载类文件 还有一系列步骤
1 加载: 寻找并导入class的二进制信息
2 连接: 进行验证(确保导入类型的正确性)、准备(为类型分配内存并初始化)、解析(将字符引用解析为直接引用)
3 初始化:调用java代码 初始化类变量为指定初始值
二: 执行引擎:负责执行包含在已装载的类或接口中的指令
jvm包含的内容: 方法区 java堆 java栈 本地方法栈 指令计数器及其他寄存器

执行完一段代码 时间片分配给其他线程 重新拿到时间片时 线程为什么知道它上次执行到哪 因为有程序计数器
每个线程抓住一个程序计数器 用来记录当前的字节码的句柄 所有才知道每次从哪里执行
优点:在执行字节码class文件时 jvm负责将每一条要执行的字节码送给解释器 解释器再将其翻译成平台特定的机器指令去执行 实现了跨平台运行
原理 图

开发人员编写java代码文件(.java文件) 然后用java编译器将其编译成.class文件 jvm才能识别运行它
jvm是针对每个操作系统开发其对应的解释器 这就是为什么java能一次编译 到处运行的原因
自动内存管理机制
包括 java内存区域与内存溢出异常
内存溢出:对象数量到达最大堆的容量限制后就会产生内存溢出异常
垃圾收集器与内存溢出分配策略
GC算法:
引用计数算法:判断对象的存活 每当一个地方引用它时 计数器值就加1 引用失效时 就减一 计数器为0时的对象就是不可能再被使用的
可达性分析算法: 来判定对象是否存活
以GC Root(当前存活的对象)对象为起点 从这些节点开始向下搜索 当一个对象到GC Root没有任何引用链时 则证明此对象不可用 判定为可回收对象
GC对java堆新生代 老年代的不同策略(分代回收策略) HotSpot还有永久代
新生成的对象优先存放在新生代中,新生代对象存活率很低,在新生代中,常规应用进行一次垃圾收集一般可以回收 70%~95% 的空间,回收效率很高。新生代中因为要进行一些复制操作,所以一般采用的 GC 回收算法是复制算法
一个对象如果在新生代存活了足够长的时间而没有被清理掉,则会被复制到老年代。老年代的内存大小一般比新生代大,能存放更多的对象。如果对象比较大(比如长字符串或者大数组),并且新生代的剩余空间不足,则这个大对象会直接被分配到老年代上。老年代因为对象的生命周期较长,不需要过多的复制操作,所以一般采用标记压缩的回收算法
虚拟机类加载机制
类加载时机 过程
类加载生命周期:包括加载 验证 准备 解析 初始化 使用 卸载 验证准备和解析统称为连接

类加载器的了解
类加载器双亲委托模型:
ClassLoader使用的是双亲委托模型来搜索加载类的 jvm内置的类加载器本身没有父类加载器 但可以用作其他classloader实例的父类加载器

解释:当一个ClassLoader实例需要加载某个类时,它会试图亲自搜索某个类之前,先把这个任务委托给它的父类加载器,这个过程是由上至下依次检查的,首先由最顶层的类加载器Bootstrap ClassLoader试图加载,如果没加载到,则把任务转交给Extension ClassLoader试图加载,如果也没加载到,则转交给App ClassLoader 进行加载,如果它也没有加载得到的话,则返回给委托的发起者,由它到指定的文件系统或网络等URL中加载该类。如果它们都没有加载到这个类时,则抛出ClassNotFoundException异常。否则将这个找到的类生成一个类的定义,并将它加载到内存当中,最后返回这个类在内存中的Class实例对象。
好处:因为这样可以避免重复加载,当父亲已经加载了该类的时候,就没有必要 ClassLoader再加载一次
高效并发
java内存模型与线程
线程安全与锁优化
锁优化:
1 自旋锁
通过自旋锁,可以减少线程阻塞造成的线程切换(包括挂起线程和恢复线程)。
- 当前线程竞争锁失败时,打算阻塞自己
- 不直接阻塞自己,而是自旋(空等待,比如一个空的有限for循环)一会
- 在自旋的同时重新竞争锁
- 如果自旋结束前获得了锁,那么锁获取成功;否则,自旋结束后阻塞自己
2 自适应自旋
自适应意味着自旋的时间不再固定了,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定
3 偏向锁
偏向锁假定将来只有第一个申请锁的线程会使用锁(不会有任何线程再来申请锁),因此,只需要在Mark Word中CAS记录owner(本质上也是更新,但初始值为空),如果记录成功,则偏向锁获取成功,记录锁状态为偏向锁,以后当前线程等于owner就可以零成本的直接获得锁;否则,说明有其他线程竞争,膨胀为轻量级锁
4 轻量级锁
轻量级锁是相对于重量级锁而言的。使用轻量级锁时,不需要申请互斥量,仅仅将Mark Word中的部分字节CAS更新指向线程栈中的Lock Record,如果更新成功,则轻量级锁获取成功,记录锁状态为轻量级锁;否则,说明已经有线程获得了轻量级锁
上述的锁不是Java语言层面的锁优化方法,是内置在JVM当中的。
首先偏向锁是为了避免某个线程反复获得/释放同一把锁时的性能消耗,如果仍然是同个线程去获得这个锁,尝试偏向锁时会直接进入同步块,不需要再次获得锁。
而轻量级锁和自旋锁都是为了避免直接调用操作系统层面的互斥操作,因为挂起线程是一个很耗资源的操作。
为了尽量避免使用重量级锁(操作系统层面的互斥),首先会尝试轻量级锁,轻量级锁会尝试使用CAS操作来获得锁,如果轻量级锁获得失败,说明存在竞争。但是也许很快就能获得锁,就会尝试自旋锁,将线程做几个空循环,每次循环时都不断尝试获得锁。如果自旋锁也失败,那么只能升级成重量级锁。
CAS机制中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。
更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B。
可见偏向锁,轻量级锁,自旋锁都是乐观锁。
注意:synchronized属于悲观锁,悲观的认为程序中的并发情况严重,所以严防死守,CAS属于乐观锁,乐观地认为程序中的并发情况不那么严重,所以让线程不断去重试更新。
谈一谈Mark word
HotSpot虚拟机的对象头由两部分组成,一部分用于存储自身的运行时数据,称之为mark word,另外一部分是类型指针,及对象指向它的类元数据的指针
当要从未锁定到轻量级锁定时 将使用cas操作将对象的Mark Word更新指向为Lock Reord锁记录空间的指针 如果更新成功 那么这个线程就拥有了该对象的锁 并且此对象的Mark Word锁标志位将转变为“00” 表示此对象处于轻量级锁定状态

浙公网安备 33010602011771号