大学期间接触 Java 的时间也不短了,不论学习还是实习,都让我发觉基础的重要性。互联网发展太快了,各种框架各种技术更新迭代的速度非常快,可能你刚好掌握了一门技术的应用,它却已经走在淘汰的边缘了。
而学习新技术总要付出一定的时间成本,那么怎么降低时间成本呢?那就是打好基础,技术再怎么革新,底层的东西也很少会变动,牢固的基础会帮助你在各种新技术的学习中游刃有余,快速上手。
因为我选择的方向是后台开发,所以谈谈我认为的基础有哪些。其他方向肯定也有自己的体系,从低层到高层,可以自己摸索。后台的话,我觉得网络知识,各种协议,web 知识,数据库知识,Linux 基本操作以及自己选择的后台语言知识,这些是最基础最需要牢固掌握的。
所以从今天起,会出一系列与后台基础相关的博文,一是对自己过去学习的一个总结,二是分享出来,希望可以帮助到需要的人。
Java 基础我做了 10 个方面的总结,包括基本概念,面向对象,关键字,基本类型与运算,字符串与数组,异常处理,Java 平台与内存管理,分布式 Java 应用,多线程,IO。
一、基本概念
1.1 语言特点
- 纯面向对象
- 平台无关性
- 内置类库
- 支持web
- 安全性
- 防止代码攻击
- 健壮性
- 强类型机制
- 垃圾回收器
- 异常处理
- 安全检查机制
- 去除C++中难以理解易混淆的特性
1.2 与 C++ 比较
- 解释编译混合型语言,执行速度慢,跨平台
- 纯面向对象,只有类,不存在全局变量或全局函数
- 无指针,无多继承,可多实现
- 垃圾回收器自动管理内存
1.3 main函数知识
- Java 程序入口方法
- 可由
final
,synchronized
修饰,不能用abstract
1.4 Java 程序初始化顺序
- 静态优于非静态
- 父类优于子类
- 按照成员变量的定义顺序
- 总共10个:
- 父静态变量 -> 父静态代码块 -> 子静态变量 -> 子静态代码块 -> 父非静态代码变量 ->
- 父非静态代码块 -> 父构造器 -> 子非静态变量 -> 子非静态代码 -> 块子构造器
1.5 作用域与可见性
- 静态变量属于类
- 局部变量属于花括号
- 成员变量根据
public
、protected
、default
、private
可见性依次递减
1.6 构造函数
- 与类名相同,无返回值
- 可重载,不能被继承,即不能被覆盖
- 参数个数任意
- 伴随
new
一起调用,为系统调用 - 完成对象的初始化工作
- 子类可通过
super
显式调用父类。父类没有提供无参,子类必须显式调用 - 未定义,默认无参,修饰符取决于类修饰符
1.7 标识接口
- 无任何方法声明
- 表示实现它的类属于一个特定的类型
1.8 clone 方法
- 实现
Cloneable
接口 - 重写
Object
类中的clone()
clone()
中调用super.clone()
- 把浅复制引用指向新的克隆体
1.9 反射
- 定义:允许程序在运行时进行自我检查,也允许对其内部成员进行操作
- 功能
- 得到一个对象所属的类
- 获取一个类的所有成员和方法
- 运行时创建对象
- 在运行时调用对象的方法
- 获取类的方式
class.forName("类路径")
类名.class
实例.getClass()
1.10 创建对象的四种方式
new
- 反射机制
clone()
- 反序列化
1.11 package 作用
- 提供多层命名空间,解决命名冲突
- 对类按功能进行分类,使项目组织更加清晰
二、面向对象
2.1 与面向过程区别
- 层次逻辑关系不同。
- 面向对象是通过类的层次结构来体现类之间的继承与发展
- 面向过程是通过模块的层次结构概括模块与模块间的关系与功能
- 数据处理方式不同与控制程序方式不同
- 面向对象是数据与操作封装成一个整体,通过事件驱动来激活和运行程序
- 面向过程是数据单独存储,控制程序方式上按照设计调用或返回程序
2.2 特性
- 抽象
- 继承
- 多态
- 封装
2.3 这种开发方式优点
- 开发效率高。代码重用
- 保证软件的鲁棒性。经过长期测试的已有代码
- 保证软件的高可维护性。设计模式成熟
2.4 继承
- 单继承
- 只能继承父类的非私有成员变量和方法
- 同名成员变量,子类覆盖,不会继承
- 相同函数签名,子类覆盖,不会继承
2.5 组合和继承区别
- 组合:在新类中创建原有类的对象。has a
- 继承是 is a
2.6 多态
- 方法重载
- 编译时多态
- 方法覆盖
- 运行时多态
- 成员变量无多态概念
2.7 覆盖和重载区别
- 子父类关系,垂直;同类方法间关系,水平
- 一对方法发生关系;多个方法发生关系
- 参数列表相同;参数列表不同
- 调用的方法根据对象的类型决定;根据调用时的实参表决定方法体
2.8 抽象类与接口异同
同
- 不能被实例化
- 接口的实现类实现了接口,抽象类的子类实现了方法,才能被实例化
异
- 接口只能定义方法,不能实现;抽象类可以有定义和实现
- 接口需要被实现;抽象类需要被继承
- 接口强调特定功能的实现;抽象类强调所属关系
- 接口成员变量默认为 public static final,成员方法 public abstract
- 抽象类变量默认default,方法不能用 private、static、synchronized、native 修饰
2.9 内部类
- 静态内部类
- static 修饰
- 只能访问外部类中的static数据
- 成员内部类
- 与实例绑定
- 不可定义静态属性和方法
- 外部实例化后,该内部类才能被实例化
- 局部内部类
- 代码块内
- 不能被public、protected、private以及static修饰
- 只能访问final 局部变量
- 匿名内部类
- 无类名
- 无构造函数,必须继承或实现其他类
- 原则
- 无构造函数
- 无静态成员,方法和类
- 不能是public、protected、private、static
- 只能创建匿名内部类的一个实例
- new 后面有继承或实现
- 特殊的局部内部类
2.10 如何获取父类类名
- 利用反射:obj.getClass().getSuperClass().getName()
- 不使用super.getClass()原因:该方法在 Object中为final与native,子类不能覆盖,返回此Object运行时类
2.11 this
- 指向当前实例对象
- 区分成员变量与方法形参
2.12 super
- 访问父类成员变量或方法
- 子类同名会覆盖,访问父类只能通过super
- 子类构造函数需显示调用父类构造函数时,super()必须为构造函数的第一条语句
三、关键字
3.1 变量命名
- 英文字母
- 数字
- _和$
- 不能包含空白字符
- 首字符不能为数字
- 保留字不能做标识符
- 区分大小写
3.2 assert
- 软件调试
- 运行时开启 -ea
3.3 static
- 特定类的统一存储空间,类绑定
- 成员变量:属于类,内存中只有一个复制
- 成员方法:调静态数据。可实现单例模式
- 代码块:初始化静态变量,只被执行一次
- 内部类:不能与外部类重名,只能访问外部类静态数据(包括私有)
3.4 switch
- 多分支选择
- 整型或字符类型变量或整数表达式
- Java 7 开始支持 String。原理是String的hashCode()返回的int类型值匹配
3.5 volatile
- 保证线程间的可见性
- 从内存中取数据,而不是缓存
- 不保证原子性
3.6 instanceof
- 二元运算符
- 判断一个引用类型的变量所指向的对象是否是一个类的实例
- 即左边对象是否是右边类的实例
3.7 strictfp
- 精确浮点
- 确保浮点运算的准确性
- 若不指定,结果依赖于虚拟机平台
- 指定后依赖于统一标准,保证各平台的一致性
3.8 null
- 不是合法的Object实例
- 无内存
- 表明该引用目前没有指向任何对象
四、基本类型与运算
4.1 基本数据类型
- int长度
- byte(8 bit)
- short(16 bit)
- int(32 bit)
- long(64 bit)
- float长度
- 单精度(32 bit float)
- 双精度(64 bit double)
- boolean 类型变量的取值
- true
- false
- char数据类型:Unicode字符(16 bit)
- void:java.lang.Void 无法直接对其进行操作
4.2 不可变类
- 实例创建后,值不可变
- 所有的基本类型的包装类+String
- 优点
- 使用简单
- 线程安全
- 节省内存
- 缺点:会因为值的不同而产生新的对象,导致无法预料的问题
4.3 类型转换
- 隐式类型转换
- 低精度到高精度
- byte->short->char->int->long->float->double
- 显式类型转换
- 反之
- 可能会损失精度
- 类型自动转换
- 低到高
- char类型会转换为其对应的ASCII码
- byte、char、short参与运算自动转为int,但”+=”,不转
- 基本数据类型与boolean不能相互转换
- 多种类型混合运算,自动转成容量最大类型
- 运算符优先级
点 () []
+(正) -(负) ++ -- ~ !
* / %
+(加) -(减)
<< >> >>>
< <= > >= instanceof
== !=
&
|
^
&&
||
?:
= += -= *= /= %= &= |= ^= ~= <<= >>= >>>=
五、字符串与数组
5.1 字符串创建与存储机制
- 堆
- 常量池
- new String(“abc”)创建1个或2个对象
5.2 ==、equals和hashCode区别
- == 比较引用,内存
- 未覆盖,同==;比较内容
- hashCode鉴定对象是否相等,返回整数
5.3 String,StringBuffer,StringBuilder
- String:不可变,执行效率最低
- StringBuffer:可修改,线程安全,效率较高
- StringBuilder:可修改,线程不安全,效率最高
5.4 其他
- 数组初始化方式
- length属性和length()方法
六、异常处理
6.1 finally块执行时机
- 若try中有return,在return前
- 若try-finally或catch-finally中都有return,finally会覆盖
6.2 finally代码块不是一定会被执行
- 程序进入try之前出现异常
- try中调用System.exit(0)
6.3 Error
严重错误,不可恢复
6.4 Exception
- 可恢复,编译器可捕捉
- 检查性异常
- IO
- SQL
- 运行时异常
- JVM处理
- NullPointException
- ClassCastException
- ArrayIndexOutOfBoundsException
- 出现异常后,一直往上层抛,直到遇到处理代码或最上层
- 多态。若先捕获基类,再捕获子类。子类处理代码将永远不会得到执行
七、Java平台与内存管理
7.1 Java平台与其他语言平台的区别
- 纯软件,包括JVM与JAVA API
- JVM虚拟,不跨平台
7.2 JAVA代码的执行
- 代码编译为class:sun jdk 中javac
- 装载class:ClassLoader
- 执行class
- 解释执行
- 编译执行
- client compiler
- server compiler
7.3 java源码编译机制
- 词法分析器组件:Token流
- 语法分析器组件:语法树
- 语义分析器组件:注解语法树
- 将语法树中的名字、表达式等元素与变量、方法、类型等联系到一起
- 检查变量使用前是否已声明
- 推导泛型方法的类型参数
- 检查类型匹配性
- 进行常量折叠
- 检查所有语句都可到达
- 检查变量的确定性赋值
- 解除语法糖
- 将泛型JAVA转成普通Java
- 检查所有checked exception都被捕获或抛出
- 将含语法糖的语法树转成简单语法树eg:foreach,自动折叠
- 代码生成器组件:字节码
7.4 类加载机制
- 装载:全限定名+类加载器加载类
- 链接
- 校验
- 格式不符,抛VerifyError
- 加载引用的类失败:抛NoClassDefFoundError
- 准备:静态变量默认初始化
- 解析:属性、方法验证(可选)
- 校验
- 初始化(不是类加载必须触发的)
- 静态初始化代码
- 构造器代码
- 静态属性初始化
- 触发时机
- 调用了new
- 反射调用了类中的方法
- 子类调用了初始化
- JVM启动过程中指定的初始化类
- Bootstrap Class Loader:$JAVA_HOME/jre/lib/rt.jar
- Extension Class Loader:$JAVA_HOME/jre/lib/ext/*.jar
- System Class Loader:$CLASSPATH
- User Defined Class Loader
7.5 类执行机制
- 解释执行
- JVM字节码为中间代码,由JVM在运行期对其解释并执行
- invokestatic
- invokevirtual
- invokeinterface
- invokespecial
- 基于栈
- 代码紧凑,体积小
- 线程创建后,产生PC和Stack
- 指令解释执行
- 栈顶缓存:栈顶值缓存在寄存器上
- 部分栈帧共享
- JVM字节码为中间代码,由JVM在运行期对其解释并执行
- 编译执行
- client compiler
- 轻量级,占内存少
- 方法内联
- 去虚拟化
- 冗余消除
- server compiler
- 重量级,占内存多
- 逃逸分析是C2进行很多优化的基础
- 标量替换:用标量替换聚合量
- 栈上分配
- 若对象未逃逸,C2会选择在栈上直接创建Point对象实例,而不是在堆上
- 栈上分配更快速,对象易回收
- 同步消除:如果发现同步的对象未逃逸,那也没有同步的必要。C2会去掉同步代码块
- client compiler
7.6 内存空间
- 方法区:类信息,线程共享
- 堆
- 对象实例+数组
- 分代管理
- 新生代
- 旧生代
- 本地方法栈:支持native方法,Sun JDK的实现中本地方法栈和JVM方法栈是同一个
- PC寄存器:线程私有
- JVM方法栈:线程私有
7.7 内存分配
- Java对象,堆上分配,分配需加锁,开销大
- 当堆上空间不足—>GC—>仍不足—>抛OutOfMemory
- Sun JDK 为新创建的线程在Eden上分配TLAB
- 多个小对象比大对象分配更高效
- 基于逃逸分析直接从栈上分配
7.8 内存回收
- 收集器
- 引用计数收集器
- 计数器增减有消耗
- 不适合循环引用
- 跟踪收集器
- 集中式管理
- 全局记录数据的引用状态
- 从根集合扫描对象,可能会造成应用程序暂停
- 三种实现算法
- 复制
- 适用于回收空间中存活对象较少
- 缺点:需要增加一块空的内存空间及进行对象的移动
- 标记-清除:会产生内存碎片
- 标记-压缩:不产生内存碎片
- 复制
- 引用计数收集器
- Sun JDK中可用GC
- 新生代
- 串行GC(Serial GC):复制算法
- Minor GC
- 强软弱虚
- 并行回收GC(Parrallel Scavenge):扫描复制多线程
- 并行 GC(ParNew):配合旧生代 CMS
- 串行GC(Serial GC):复制算法
- 旧生代和持久代可用GC
- 串行:标记压缩+清除
- 并行:标记压缩
- 并发:CMS
- 标记:暂停
- 并发标记:恢复,轮询着色对象,以标记它们
- 重新标记:暂停
- 并发收集:恢复
- CMS内存回收易产生碎片,但是它提供了整理碎片的功能
- 浮动垃圾:CMS回收时产生应该回收但要等到下次CMS才能被回收掉的对象
- 新生代
- Full GC
- 对新生代旧生代及持久代都进行的GC
- 触发的四种情况
- 旧生代空间不足
- 持久代空间满
- CMS GC出现promotion failed和concurrent mode failure
- 统计得到的Minor GC晋升到旧生代的平均大小大于旧生代的剩余空间
7.9 内存泄露
- 一个不再被程序使用的对象或变量还在内存中占有存储空间
- 符合垃圾回收标准
- 对象赋空值null
- 给对象赋予新值,重新分配了内存空间
- 泄露的两种情况
- 堆中申请的空间没有被释放
- 对象不再被使用,但仍然存活在内存中
- 泄露原因
- 静态集合类
- 各种连接
- 监听器
- 变量不合理的作用域
- 单例模式
八、分布式Java应用
8.1 基于消息方式实现系统间的通信
- TCP/IP+BIO
- socket.setSoTimeOut()设置等待响应的超时时间
- 一连接一线程
- 缺点:无论连接是否真实,都要创建线程
- BIO下服务器端所能支撑的连接数目有限
- TCP/IP+NIO
- Channel
- SocketChannel:建立连接,监听事件,操作读写
- ServerSocketChannel:监听端口,监听连接事件
- Selector:获取是否要处理的事件
- Buffer:存放处理的数据
- NIO Reactor模式,通过注册感兴趣的事件及扫描是否有感兴趣的事件发生,从而做出相应的动作
- 多个请求,连接复用
- 只有在有真实的请求时,才会创建线程
- 一请求一线程
- Channel
- UDP/IP+BIO
- DatagramSocket:负责监听端口,读写数据
- DatagramPacket:作为数据流对象进行传输
- UDP/IP+NIO
- DatagramChannel:监听端口,进行读写
- ByteBuffer:数据流传输
- NIO好处:只在有流要读取或可写入流时才做出相应的IO操作,而不像BIO方式阻塞当前线程
8.2 基于远程调用方式实现系统间的通信
- 远程调用方式
- 系统间通信和系统内一样
- 让使用者感觉调用远程同调用本地一样
- 基于Java自身技术
- RMI:客户端代理,stub,封装对象,序列化为流,TCP/IP BIO,Skeleton,反序列化,获取对象实例,调用
- WebService
- 服务端的服务生成WSDL文件
- 将应用+WSDL文件放入HTTP服务器
- 借用Java辅助工具根据WSDL文件生成客户端stub代码
- stub将产生的对象请求信息封装为标准化的SOAP格式数据,并发请求到服务器端
- 服端在接收到SOAP格式数据时进行转化,反射调用相应的Java类
- SOAP优点支持跨语言,缺点对复杂对象结构难支持
8.3 基于开源框架
- Spring RMI
九、多线程
9.1 线程资源同步机制
- JVM保证以下操作顺序
- 同一线程操作
- 对于main Memory 上的同一个变量的操作
- 对于加了锁的main Memory上的对象操作
- 为避免资源操作的脏数据问题,JVM提供了
- synchronized
- volatile
- lock/unlock
- 目的是控制资源竞争
9.2 线程交互机制
- 基于Object的wait/notify/notifyAll
- 为避免假唤醒,需要double check
- 调用对象的wait—>wait sets—->释放锁—->其他线程notify——>wait sets——>执行此对象线程—->删除sets中此线程
- 基于JDK 5 并发包,支持线程交互
- Semphore的acquire,release
- Condition的await,signal
- CountDownLatch的await和countDown
9.3 线程状态
- New
- Runnable
- Running
- Wait
- TimedWait
- Blocked
- Terminated
9.4 sleep()与wait()方法的区别
- sleep
- 暂停一段时间执行
- Thread的静态方法
- 不释放锁
- 需要捕获异常
- wait
- 使线程暂停执行
- Object方法,用于线程间通信
- 释放锁
9.5 守护线程
- 后台提供服务
- 用户线程全部终止,只剩下守护线程时,JVM就会退出
- 调用start()之前,调用线程对象的setDaemon(true)
9.6 join
- 调用该方法的线程在执行完run()后,再执行join方法后面的代码
- 线程合并,实现同步功能
十、IO
10.1 流本质
- 数据传输
10.2 流分类
- 字节流:不使用缓存
- 字符流
- 码表映射
- 使用缓存
10.3 装饰者模式
- 运行时动态给对象增加额外的职责
- 是你还有你,一切拜托你
- FilterInputStream
10.4 Java Socket
- ServerSocket server = new ServerSocket(2000);
- Socker socket = server.accept();
- 客户端:Socket socket = new Socket(“localhost”,2000);
10.5 NIO
- Channel—Selector—Buffer
- 反应器模式
10.6 序列化
- 对象持久化方式
- 解决在对对象流进行读写操作时引发的问题
- 对象写进流里进行网络传输,保存到文件,数据库
10.7 如何实现序列化
- 实现Serializable接口
- 使用FileOutputStream来构造ObjectOutputStream对象
- 使用该对象的writeObject(obj)方法将对象写出
- 要恢复时,使用对应的输入流
10.8 序列化特点
- 一个类能被序列化,它的子类也能被序列化
- static代表类成员,transient代表临时数据。均不能被序列化
- 序列化影响性能,需要才使用
- 需要通过网络来发送对象,或对象的状态需要被持久化到数据库或文件中
- 序列化能实现深复制,即可以复制引用的对象
10.9 反序列化
- 将流转化为对象
- UID最好自己定义。优点
- 提高程序运行效率。省去计算过程
- 提高程序不同平台兼容性。不同计算方式,反序列化失败
- 增强程序各个版本的可兼容性。加入新属性,默认UID变化
10.10 外部序列化
- 实现Externalizable接口控制