| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> Java知识库 -> Java从入门到精通(一) -> 正文阅读 |
|
[Java知识库]Java从入门到精通(一) |
Java文章目录
Java 语言基础Java 语言概述基础常识软件:即一系列按照特定顺序组织的计算机数据和指令集合。分为:系统软件和应用软件
人机交互方式:图形化界面 vs 命令行方式 应用程序 = 算法 + 数据结构 常用 DOS 命令:
计算机语言的发展迭代史
Java 语言版本迭代
Java语言的应用领域
Java 语言的特点
JDK、JRE、JVM 的关系JDK 的下载、安装和 path 环境变量的配置为什么配置 path 环境变量?path 环境变量:Windows 重装系统执行命令时所要搜寻的路径 为什么要配置 path:希望 Java 的开发工具在任何文件的文件路径下都可以执行成功 如何配置
访问官网,下载对应的版本
安装注意问题:安装软件的路径中不能包含中文、空格 配置环境变量此电脑 --> 右击“属性” --> 点击“高级系统设置” --> 点击“环境变量” 双击系统变量的PATH 依次点击确定 打开cmd窗口 开发体验 —— HelloWorld编写创建一个 Java 源文件:HelloWorld.java
编译
运行
常见问题解决总结第一个程序
注释:Comment
Java API 文档API:application programming interface 习惯上:将语言提供的类库,都称为 API API 文档:针对于提供的类库如何使用,给的一个说明书 良好的编程风格
开发工具
基本语法关键字与标识符关键字定义:被 Java 语言赋予了特殊含义,用做专门用途的字符串(单词) 特点:关键字中所写字母都为小写 具体关键字: 保留字定义:现有Java版本尚未使用,但以后版本可能会作为关键字使用。 具体哪些保留字:goto、const 注意:自己命名标识符时要避免使用这些保留字 标识符定义:凡是自己可以起名字的地方都叫标识符 涉及到的结构:包名、类名、方法名、变量名、接口名、常量名 规则:(必须遵守,否则,编译不通过)
规范:(可以不遵守,不影响编译和运行,但是要求大家遵守
注意点:
代码整洁之道
变量的使用变量的分类按数据类型分类详细说明:
按声明的位置分类定义变量的格式数据类型 变量名 = 变量值; 或者 数据类型 变量名; 变量名 = 变量值; 变量使用的注意点
基本数据类型变量间运算规则涉及到的基本数据类型除了 boolean 类型之外的其它7种 自动类型转换(只涉及7种基本数据类型)当容量小的数据类型的变量与容量大的数据类型的变量做运算时,结果 自动提升为容量大的数据类型 byte 、short 、char –> int –> long –> float –> double 特别地:当 byte 、short 、char 三种类型的变量做运算时,结果为 int 类型 说明:此时的容量大小指的是,表示数的范围的大小,比如,float 容量要大于 long 的容量 强制类型转换(只涉及7种基本数据类型)自动类型提升运算的逆过程
String 与 8种基本数据类型间的运算
避免:
进制编程中涉及的进制及表示方式
二进制的使用说明
进制间的转换运算符算术运算符代码
特别说明
赋值运算符代码
特别说明
赋值运算符代码
特别说明
逻辑运算符代码
特别说明
位运算符代码
面试题:最高效的计算 2 * 8 ? 答案: 2 << 3 或 8 << 1 特别说明 1.位运算符操作的都是整型的数据 2.<<:在一定范围内,每向左移1位,相当于乘以2
三元运算符代码
特别说明
流程控制分支结构if-else格式:
说明:
switch-case格式:
说明:
循环结构循环结构的四要素
说明:通常情况下,循环结束都是因为循环条件返回 false 了 for结构:
执行过程:1 --> 2 --> 3 --> 4 --> 2 --> 3 --> 4 --> … --> 2 while结构:
执行过程:1 --> 2 --> 3 --> 4 --> 2 --> 3 --> 4 --> … --> 2 说明
for 和 while 循环总结:
do-while结构:
执行过程:1 -->3 --> 4 --> 2 --> 3 --> 4 --> 2 --> … --> 2 说明:
“无限循环”结构
总结:如何结束一个循环结构?
嵌套循环
2.说明
典型练习
补充:衡量一个功能代码的优劣:
break 和 continue 关键字的使用
带标签的 break 和 continue 关键字的使用 Scanner 类的使用
数组数组的概述
数据结构:
算法:
一维数组一维数组的声明和初始化
一维数组的引用通过角标的方式调用
数组的属性length
注意:
遍历一维数组
一维数组元素的默认初始化值
数组的内存解析二维数组如何理解二维数组数组属于引用数据类型,数组的元素也可以是引用数据类型,一个一维数组的元素如果还是一个一维数组类型的,则此数组称为二维数组 二维数组的声明和初始化
二维数组的调用
二维数组的属性
遍历二维数组
数组元素的默认初始化值规定:二维数组分为外层数组的元素,内层数组的元素
数组的内存解析数组的常见算法数组的创建与元素赋值杨辉三角(二维数组)、回形数(二维数组)、6个数,1-30之间随机生成且不重复 针对于数值型的数组最大值、最小值、总和、平均数等 数组的赋值与复制
赋值
理解:将 array1 保存的数组的地址值赋给了 array2,使得 array1 和 array2 共同指向堆空间的同一个数组实体 复制
理解:通过 new 的方式,给 array2 在堆空间中新开辟了数组的空间,将 array1 数组中的元素值一个一个的赋值到 array2 数组中 数组元素的反转
数组中指定元素的查找线性查找实现思路:通过遍历的方式,一个一个的数据进行比较、查找 适用性:具有普遍适用性 二分法查找实现思路:每次比较中间值,折半的方式检索 适用性:(前提 :数组必须有序)
数组的排序算法
理解:
Arrays 工具类的使用理解
使用
数组的常见异常数组角标越界异常:ArrayIndexOutOfBoundsException
空指针异常:NullPointerException
小知识:一旦程序出现异常,未处理时,就终止执行 面向对象类与对象面向对象学习的三条主线
“大处着眼,小处着手” 面向过程与面向对象
完成一个项目(或功能)的思路
面向对象中两个重要的概念
二者的关系:对象,是由类 new 出来的,派生出来的 面向对象思想落地实现的规则一
补充:几个概念的使用说明
对象的创建与对象的内存解析典型代码:
说明:
内存解析: 匿名对象概念:创建的对象,没有显示地赋给一个变量名,即为匿名对象 特点:匿名对象只能调用一次 举例:
应用场景:
理解“万事万物皆对象”
JVM 内存结构编译完源程序以后,生成一个或多个字节码文件。使用 JVM 中的类的加载器和解释器对生成的字节码文件进行解释运行,意味着,需要将字节码文件对应的类加载到内存中,涉及到内存解析
类的结构之一:属性类的设计中,两个重要的结构之一:属性 对比 属性 VS 局部变量
补充:回顾变量的分类
类的结构之二:方法类的设计中,两个重要的结构之二:方法
return 关键字的使用
方法重载概念定义:在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数 类型不同即可。 总结:“两同一不同”
举例Arrays 类中重载的 sort() binarySearch(),PrintStream 中的 println()
判断是否构成方法的重载严格按照定义判断:两同一不同 跟方法的权限修饰符、返回值类型、形参变量名、方法体都没关系 如何确定类中某一个方法的调用方法名 --> 参数列表 面试题:方法的重载与重写的区别?
可变个数形参的方法使用说明
举例说明
调用时:
Java 的值传递机制针对于方法内变量的赋值举例
规则:
针对于方法的参数的概念
Java 中参数传递机制
典型例题和内存解析递归方法定义递归方法:一个方法体内调用它自身 理解
举例
面向对象的特征之一:封装与隐藏为什么引入
问题引入当创建一个类的对象后,可以通过“对象.属性”的方式,对对象的属性进行赋值,这里,赋值操作要受到属性的数据类型和存储范围的制约,除此之外,没有其它制约条件,但是,在实际问题中,往往需要给属性赋值,加入额外的限制条件,这个条件就不能在属性声明时体现,只能通过方法进行限制条件的添加,这时需要避免用户再使用“对象.属性”的方式对属性进行赋值,则需要将属性声明为私有的 (private) --> 此时,针对属性就体现了封装性 封装性思想具体的代码体现
Java 规定的四种权限修饰符权限从小到大顺序private < 缺省 < protected < public 具体的修饰范围
权限修饰符可用来修饰的结构说明具体的,4种权限可以用来修饰类及类的内部结构:属性、方法、构造器、内部类
类的结构:构造器 Constructor作用
使用说明
举例
属性的赋值顺序总结:属性赋值的先后顺序
以上操作的先后顺序:1 --> 2 --> 3 --> 4 JavaBean 的概念所谓 JavaBean,是指符合如下标准的 Java 类:
this 关键字可以调用的结构
调用属性、方法this 理解为:当前对象或当前正在创建的对象
调用构造器
关键字 package / importpackage 关键字的使用使用说明
举例举例一: 举例二:MVC 设计模式 JDK 中的主要包介绍
import 关键字的使用import:导入
面向对象的特征二:继承性为什么要有继承性(继承性的好处)
继承性的格式
子类继承父类之后有哪些不同
Java 中继承性的说明
|
基本数据类型 | 包装类 |
---|---|
int | Integer |
byte | Byte |
short | Short |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
基本数据类型、包装类和 String 类型
基本数据类型 <—> 包装类:JDK 5.0 新特性:自动装箱与自动拆箱
基本数据类型、包装类 --> String:调用 String 重载的 valueOf(Xxx xxx)
String --> 基本数据类型、包装类:调用包装类的 parseXxx(String s)
注意:转换时,可能会报 NumberFormatException
Vector 类中关于添加元素,只定义了形参为 Object 类型的方法
v.addElement(Object obj); // 基本数据类型 --> 包装类 --> 使用多态
static:静态的
主要用来修饰类的内部结构
静态变量(类变量)
属性:按是否使用 static 修饰,又分为:静态属性 VS 非静态属性(实例变量)
实例变量:创建了类的多个对象,每个对象都独立的拥有一套类中的非静态属性,当修改其中一个对象中的非静态属性时,不会导致其它对象中同样的属性值的修改
静态变量:创建了类的多个对象,每个对象都共享同一个静态变量,当通过某一个对象修改静态变量时,会导致其它对象调用此静态变量时,是修改过了的
其它说明
静态变量随着类的加载而加载,可以通过“类.静态变量”的方式进行调用
静态变量的加载早于对象的创建
由于类只会加载一次,则静态变量在内存中也只会存在一份,存在方法区的静态域中
静态方法 | 非静态方法 | |
---|---|---|
类 | yes | no |
对象 | yes | yes |
静态属性举例:System.out, Math.PI
静态方法、类方法
随着类的加载而加载,可以通过“类.静态方法”的方式进行调用
静态方法 | 非静态方法 | |
---|---|---|
类 | yes | no |
对象 | yes | yes |
静态方法中,只能调用静态的方法或属性
非静态方法中,即可以调用静态的方法或属性,也可以调用非静态的方法或属性
关于属性
关于方法
举例一:Arrays、Collections、Math 等工具类
举例二:单例模式
举例三:
class Circle{
private double radius;
/**
* 自动赋值
*/
private int id;
/**
* 记录创建的圆的个数
*/
private static int total;
/**
* static 声明的属性被所有对象共享
*/
private static int init = 1001;
public Circle() {
id = init++;
total++;
}
public Circle(double radius) {
this();
this.radius = radius;
}
public static int getTotal() {
return total;
}
public double getRadius() {
return radius;
}
public void setRadius(double radius) {
this.radius = radius;
}
public int getId() {
return id;
}
public double findArea() {
return Math.PI * radius * radius;
}
}
设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、 以及解决问题的思考方式。
23种经典的设计模式 GoF
所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例
饿汉式1:
class Bank{
// 1.私有化类的构造器
private Bank() {
}
// 2.内部创建类的对象
// 4.要求此对象也必须声明为静态的
private static Bank instance = new Bank();
// 3.提供公共的静态方法,返回类的对象
public static Bank getInstance() {
return instance;
}
}
饿汉式2:
class Bank{
// 1.私有化类的构造器
private Bank() {
}
// 2.内部创建类的对象
// 4.要求此对象也必须声明为静态的
private static Bank instance;
static{
instance = new Bank();
}
// 3.提供公共的静态方法,返回类的对象
public static Bank getInstance() {
return instance;
}
}
懒汉式:
class Order{
// 1.私有化类的构造器
private Order() {
}
// 2.声明当前类对象,没有实例化
// 4.此对象必须声明为 static 的
private static Order instance = null;
// 3.声明 public static 的返回当前类对象的方法
public static Order getInstance() {
if (instance == null) {
instance = new Order();
}
return instance;
}
}
饿汉式
坏处:对象加载时间过长
好处:饿汉式是线程安全的
懒汉式
* 好处:延迟对象的创建
* 目前的写法坏处:线程不安全 --> 到多线程内容时,再修改
main() 方法作为程序的入口
main() 方法是一个普通的静态方法
main() 方法可以作为我们与控制台交互的方式(之前,使用 Scanner)
如何将控制台获取的数据传给形参:String[] args?
运行时:java 类名 “Tom” “123” “true” “Jerry”
sysout(args[0]); // "Tom"
sysout(args[2]); // "true"
sysout(args[4]); // 报异常
小结:
public static void main(String[] args){
// 方法体
}
又叫初始化块
用来初始化类和对象的信息
代码块如果有修饰的话,只能使用 static
静态代码块 VS 非静态代码块
涉及到父类、子类中静态代码块、非静态代码块构造器的加载顺序:由父及子,静态先行
LeafTest.java
package cn.tedu.java3;
class Root{
static{
System.out.println("Root 的静态初始化块");
}
{
System.out.println("Root 的普通初始化块");
}
public Root(){
System.out.println("Root 的无参数的构造器");
}
}
class Mid extends Root{
static{
System.out.println("Mid 的静态初始化块");
}
{
System.out.println("Mid 的普通初始化块");
}
public Mid(){
System.out.println("Mid 的无参数的构造器");
}
public Mid(String msg){
// 通过 this 调用同一类中重载的构造器
this();
System.out.println("Mid 的带参数构造器,其参数值:" + msg);
}
}
class Leaf extends Mid{
static{
System.out.println("Leaf 的静态初始化块");
}
{
System.out.println("Leaf 的普通初始化块");
}
public Leaf(){
// 通过 super 调用父类中有一个字符串参数的构造器
super("尚硅谷");
System.out.println("Leaf 的构造器");
}
}
public class LeafTest{
public static void main(String[] args){
new Leaf();
new Leaf();
}
}
Son.java
package cn.tedu.java3;
class Father {
static {
System.out.println("11111111111");
}
{
System.out.println("22222222222");
}
public Father() {
System.out.println("33333333333");
}
}
public class Son extends Father {
static {
System.out.println("44444444444");
}
{
System.out.println("55555555555");
}
public Son() {
System.out.println("66666666666");
}
public static void main(String[] args) { // 由父及子 静态先行
System.out.println("77777777777");
System.out.println("************************");
new Son();
System.out.println("************************");
new Son();
System.out.println("************************");
new Father();
}
}
默认初始化
显示初始化
对象初始化
有了对象以后,可以通过“对象.属性”或“对象.方法”的方式,进行赋值
代码块赋值
执行的先后顺序: 1 - 2 / 5 - 3 - 4
类、方法、变量
final 用来修饰一个类:此类不能被其它类所继承,比如:String 类、System 类、StringBuffer 类
final 用来修饰方法:表明此方法不可以被重写,比如:Object 类的 getClass();
final 用来修饰变量:此时的“变量”就称为一个常量
static final 用来修饰属性,全局常量
abstract:抽象的
类、方法
abstract 修饰类:抽象类
abstract 修饰方法:
抽象方法抽象方法只有方法的声明,没有方法体
包含抽象方法的类,一定是一个抽象类,反之,抽象类中可以没有抽象方法
若子类重写了父类中的所有抽象方法后,此子类方可实例化
若子类没有重写了父类中的所有抽象方法后,此子类也是一个抽象类,需要使用 abstract 修饰
举例一
public abstract class Vehicle{
public abstract double calcFuelEfficiency(); // 计算燃料效率的抽象方法
public abstract double calcTripDistance(); // 计算行驶距离的抽象方法
}
public class Truck extends Vehicle{
public double calcFuelEfficiency(){
//写出计算卡车的燃料效率的具体方法
}
public double calcTripDistance(){
//写出计算卡车行驶距离的具体方法
}
}
public class RiverBarge extends Vehicle{
public double calcFuelEfficiency() {
//写出计算驳船的燃料效率的具体方法
}
public double calcTripDistance() {
//写出计算驳船行驶距离的具体方法
}
}
举例二:
public class Circle extends GeometricObject{
private double radius;
@Override
public double findArea() {
return Math.PI * radius * radius;
}
}
abstract class GeometricObject {
public abstract double findArea();
}
举例三
IO 流中涉及到的抽象类:InputStream
/ OutputStream
/ Reader
/ Writer
,在其内部定义了 read()
/ write()
在软件开发中实现一个算法时,整体步骤很固定、通用,这些步骤已经在父类中写好了。但是某些部分易变,易变部分可以抽 象出来,供不同子类实现。这就是一种模板模式。
package cn.tedu.java;
public class TemplateTest {
public static void main(String[] args) {
Template template = new SubTemplate();
template.spendTime();
}
}
abstract class Template{
/**
* 计算某段代码所花费的时间
*/
public void spendTime() {
long start = System.currentTimeMillis();
// 易变的部分
code();
long end = System.currentTimeMillis();
System.out.println("花费的时间为:" + (end - start));
}
public abstract void code();
}
class SubTemplate extends Template{
@Override
public void code() {
for(int i = 2; i < 10000000; i++) {
boolean isFlag = true;
for(int j = 2; j <= Math.sqrt(i); j++) {
if (i % j == 0) {
isFlag = false;
break;
}
}
if (isFlag) {
System.out.println(i);
}
}
}
}
接口使用 interface 定义
Java 中,接口和类是并列的两个结构
如何定义接口:定义接口是成员
JDK7及以前:只能定义全局常量和抽象方法
全局常量:public static final,但是书写时,可以省略不写
抽象方法:public abstract,但是书写时,可以省略不写
JDK8:除了定义全局常量和抽象方法之外,还可以定义静态方法和默认方法
接口中不能定义构造器,意味着接口不可以实例化
Java 开发中,接口通过类实现(implements)的方式来使用
如果实现类覆盖了接口中所有的抽象方法,则此实现类就可以实例化
如果实现类没有覆盖接口中所有的抽象方法,则此实现类仍为一个抽象类
Java 类可以实现多个接口 --> 弥补了 Java 单继承的局限性
格式:class AA extends BB implements CC, DD, EE {}
接口与接口之间可以继承,而且可以多继承
接口的具体使用,体现多态性
接口实际上可以看做是一种规范
package cn.tedu.java1;
public class USBTest {
public static void main(String[] args) {
Computer com = new Computer();
// 1.创建了接口的非匿名实现的非匿名对象
Flash flash = new Flash();
com.transforData(flash);
// 2.创建了接口的非匿名实现的匿名对象
com.transforData(new Printer());
// 3.创建了接口的匿名实现的非匿名对象
USB phone = new USB() {
@Override
public void stop() {
System.out.println("手机结束工作");
}
@Override
public void start() {
System.out.println("手机开始工作");
}
};
com.transforData(phone);
// 4.创建了接口的匿名实现的匿名对象
com.transforData(new USB() {
@Override
public void stop() {
System.out.println("mp3 结束工作");
}
@Override
public void start() {
System.out.println("mp3 开始工作");
}
});
}
}
class Computer{
public void transforData(USB usb) {
usb.start();
System.out.println("具体传输数据的细节");
usb.stop();
}
}
interface USB{
void start();
void stop();
}
class Flash implements USB{
@Override
public void start() {
System.out.println("U盘开启工作");
}
@Override
public void stop() {
System.out.println("U盘结束工作");
}
}
class Printer implements USB{
@Override
public void start() {
System.out.println("打印机开启工作");
}
@Override
public void stop() {
System.out.println("打印机结束工作");
}
}
体会:
接口使用上满足多态性
接口实际上定义了一种规范
开发中,体会面向接口编程
面向接口编程,在应用程序中,调用的结构都是 JDBC 中定义的接口,不会出现具体某一个数据库厂商的 API
接口中定义的静态方法,只能通过接口来调用
通过实现类对象,可以调用接口中的默认方法如果实现类重写了接口中的默认方法,调用时,仍然调用的是重写以后的方法
如果子类(或实现类)继承的父类和实现的接口中声明了同名同参数的方法,那么子类在没有重写此方法的情况下,默认调用的是父类中的同名同参数的方法 --> 类优先原则
如果实现类实现了多个接口,而这多个接口中定义了同名同参数的默认方法,那么在实现类没有重写此方法的情况下,报错 --> 接口冲突,这就需要我们必须在实现类中重写此方法
如何在子类(或实现类)的方法中调用父类、接口中被重写的方法
public void myMethod() {
method3(); // 调用自己定义的重写的方法
super.method3(); // 调用父类中声明的
// 调用接口中的默认方法
CompareA.super.method3();
CompareB.super.method3();
}
抽象类和接口有哪些异同?
相同点
不同点
把抽象类和接口的定义、内部结构解释说明
类:单继承
接口:多继承
类与接口:多实现
代理模式是 Java 开发中使用较多的一种设计模式。代理设计就是为其 他对象提供一种代理以控制对这个对象的访问。
public class NetWorkTest {
public static void main(String[] args) {
Server server = new Server();
ProxyServer proxyServer = new ProxyServer(server);
proxyServer.browse();
}
}
interface NetWork{
void browse();
}
// 被代理类
class Server implements NetWork{
@Override
public void browse() {
System.out.println("真实的服务器访问网络");
}
}
// 代理类
class ProxyServer implements NetWork{
private NetWork work;
public ProxyServer(NetWork work) {
this.work = work;
}
@Override
public void browse() {
check();
work.browse();
}
public void check() {
System.out.println("联网之前的检查工作");
}
}
比如你要开发一个大文档查看软件,大文档中有大的图片,有可能一个图片有100MB,在打开文件时,不可能将所有的图片都显示出来,这样就可以使用代理 模式,当需要查看图片时,用 proxy 来进行大图片的打开。
实现了创建者与调用者的分离,即将创建对象的具体过程屏蔽隔离起来,达到提高灵活性的目的。
Java 中允许将一个类 A 声明在另一个类 B 中,则类 A 就是内部类,类 B 就是外部类
如何实例化成员内部类的对象
public static void main(String[] args) {
// 创建 Dog 实例(静态成员内部类)
Dog dog = new Person.Dog();
// 创建 Bird 实例(非静态成员内部类)
Person p = new Person();
Bird bird = p.new Bird();
}
如何在成员内部类中区分调用外部类的结构
class Person{
String name = "小明";
public void eat(){
}
// 非静态成员内部类
class Bird{
String name = "杜鹃";
public void display(String name) {
System.out.println(name); // 方法形参
System.out.println(this.name); // 内部类属性
System.out.println(Person.this.name); // 外部类属性
// Person.this.eat();
}
}
}
public class InnerClassTest1 {
// 开发中很少见
public void method() {
class AA{
}
}
/**
* 返回一个实现了 Comparable 接口的类对象
*/
public Comparable getComparable() {
// 创建一个实现了 Comparable 接口的类:局部内部类
/*class MyComparable implements Comparable{
@Override
public int compareTo(Object o) {
// TODO Auto-generated method stub
return 0;
}
}
return new MyComparable();*/
return new Comparable() {
@Override
public int compareTo(Object o) {
// TODO Auto-generated method stub
return 0;
}
};
}
}
注意点:
在局部内部类的方法中,如果调用局部内部类所声明的方法中的局部变量话,要求此局部变量声明为 final 的
public class InnerClassTest {
public void method() {
// 局部变量
int num = 10;
class AA{
public void show() {
// num = 10;
System.out.println(num);
}
}
}
}
成员内部类和局部内部类,在编译以后,都会生成字节码文件
格式
操作 | 作用 |
---|---|
step into 跳入(F5) | 进入当前行所调用的方法中 |
step over 跳过(F6) | 执行完当前行的语句,进入下一行 |
step return 跳回(F7) | 执行完当前行所在的的方法,进入下一行 |
drop to frame | 回到当前行所在方法的第一行 |
resume 恢复 | 执行完当前行所在断点的所有代码,进入下一个断点,如果没有就结束 |
Terminate 停止 | 停止 JVM,后面程序不会再执行 |
java.lang.Throwable
|--- java.lang.Error:一般不编写针对性的代码进行处理
|--- java.lang.Exception:可以进行异常的处理
|--- 编译时异常(checked)
|--- IOException
|--- FileNotFoundException
|--- ClassNotFoundException
|--- 运行时异常(unchecked)
|--- NullPointerException
|--- ArrayIndexOutOfBoundsException
|--- ClassCastException
|--- NumberFormatException
|--- InputMismatchException
|--- ArithmeticException
编译时异常:执行 javac.exe 命令时,可能出现的异常
运行时异常:执行 java.exe 命令时,出现的异常
public class ExceptionTest {
// NullPointerException
@Test
public void test1() {
int[] arr = null;
System.out.println(arr);
String str = null;
System.out.println(str.charAt(0));
}
// IndexOutOfBoundsException
@Test
public void test2() {
// ArrayIndexOutOfBoundsException
int[] a = new int[10];
System.out.println(a[10]);
// StringIndexOutOfBoundsException
String str = "abc";
System.out.println(str.charAt(3));
}
// ClassCastException
@Test
public void test3() {
Object obj = new Date();
String str = (String) obj;
}
// NumberFormatException
@Test
public void test4() {
String str = "abc";
int parseInt = Integer.parseInt(str);
}
// InputMismatchException
@Test
public void test5() {
int score = new Scanner(System.in).nextInt();
System.out.println(score);
}
// ArithmeticException
@Test
public void test6(){
int a = 1 / 0;
}
//
// @Test
// public void test7() {
// File file = new File("hello.txt");
// FileInputStream fileInputStream = new FileInputStream(file);
// int read = fileInputStream.read();
// while (read != -1) {
// System.out.print((char)read);
// read = fileInputStream.read();
// }
// fileInputStream.close();
// }
}
“抛”,程序在正常执行过程中,一旦出现异常,就会在异常代码处生成一个对应异常类的对象,并将此对象抛出,一旦抛出异常以后,其后的代码就不再执行
关于异常对象的产生:
“抓”,可以理解为异常的处理方式:
try{
// 可能出现异常的代码
} catch(异常类型1 变量名1){
// 处理异常的方式1
} catch(异常类型2 变量名2){
// 处理异常的方式2
} catch(异常类型3 变量名3){
// 处理异常的方式3
}
...
finally{
// 一定会执行的代码
}
finally 是可选的
使用 try 将可能出现的异常代码包装起来,在执行过程中,一旦出现异常,就会生成一个对应异常类的对象,根据此对象的类型,去 cache 中进行匹配
一旦 try 中的异常对象匹配到某一个 catch 时,就进入 catch 中进行异常处理,一旦处理完成,就跳出当前的 try-catch 结构(在没有写 finally 的情况),继续执行其后的代码
catch 中的异常类型如果没有子父类关系,则谁声明在上,谁声明在下无所谓
catch 中的异常类型如果满足子父类关系,则要求子类一定声明在父类的上面,否则报错
常用的异常对象处理的方式
在 try 结构中声明的变量,出了 try 结构以后,就不能被调用
try-catc-finally 结构可以相互嵌套
总结:如何看待代码中的编译时异常和运行时异常?
使用 try-catch-finally 处理编译时异常,是将程序在编译时就不再报错,但是运行时仍可能报错,相当于使用 try-catch-finally 将一个编译时可能出现的异常,延迟到运行时出现
开发中由于运行时异常比较常见,所以通常就不针对运行时异常编写 try-catch-finally,针对于编译时异常,一定要考虑异常的处理
类似:
结构不相似:
"throws + 异常类型"写在方法声明出,指明此方法执行时,可能会抛出的异常类型,一旦当方法体执行时,出现异常,仍然会在异常代码处生成一个异常类的对象。此对象满足 throws 后异常类型时,就会被抛出,异常代码后续的代码,就不再执行
如果父类中被重写的方法没有 throws 方式处理异常,则子类重写的方法也不能使用 throws,意味着如果子类重写的方法有异常,必须使用 try-catch-finally 方式处理
执行的方法中先后又调用了另外的几个方法,这几个方法是递进关系执行的,建议这几个方法使用 throws的方式进行处理,而执行方法 a 可以考虑使用 try-catch-finally 方式进行处理
补充:
方法重写的规则之一:子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型
在程序执行中,除了自动抛出异常对象的情况之外,还可以手动 throw 一个异常类的对象
class Student{
private int id;
public void regist(int id) {
if (id > 0) {
this.id = id;
}else {
// System.out.println("您输入的数据非法");
// 手动抛出异常对象
// throw new RuntimeException("输入的数据非法");
// throw new Exception("输入的数据非法");
throw new MyException("不能输入负数");
}
}
@Override
public String toString() {
return "Student [id=" + id + "]";
}
}
public class MyException extends RuntimeException{
static final long serialVersionUID = 12345678921234L;
public MyException() {
}
public MyException(String msg) {
super(msg);
}
}
是为完成特定任务、用某种语言编写的一组指令的集合。即指一 段静态的代码。
是程序的一次执行过程,或是正在运行的一个程序。
进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域
进程可进一步细化为线程,是一个程序内部的一条执行路径。
线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小
内存结构:
进程可以细化为多个线程
每个线程拥有自己独立的:栈、程序计数器
多个线程共享同一个进程中的结构:方法区、堆
单核 CPU,其实是一种假的多线程,因为在一个时间单元内,也只能执行一个线程 的任务。例如:虽然有多车道,但是收费站只有一个工作人员在收费,只有收了费 才能通过,那么 CPU 就好比收费人员。如果有某个人不想交钱,那么收费人员可以 把他“挂起”(晾着他,等他想通了,准备好了钱,再去收费)。但是因为 CPU 时间单元特别短,因此感觉不出来。
如果是多核的话,才能更好的发挥多线程的效率。(现在的服务器都是多核的)
一个Java应用程序java.exe,其实至少有三个线程:main() 主线程,gc() 垃圾回收线程,异常处理线程。当然如果发生异常,会影响主线程。
创建一个继承于 Thread 类的子类
重写 Thread 类的 run() --> 将此线程声明在 run() 中
创建继承于 Thread 类的子类的对象
通过此对象调用 start():
说明两个问题:
创建一个实现了 Runnable 接口的类
实现类去实现 Runnable 中的抽象方法:run()
创建实现类的对象
将此对象作为参数传递到 Thread 类的构造器中,创建 Thread 类的对象
通过 Thread 类的对象调用 start()
开发中,优先选择实现 Runnable 接口的方式
原因:
public class Tread implements Runnable
start():启动当前线程,调用当前线程的 run()
run():通常需要重写 Thread 类中的此方法,将创建的线程要执行的操作声明在此方法中
currentThread():静态方法,返回执行当前代码的线程
getName():获取当前线程的名字
setName():设置当前线程的名字
yeild():释放当前 CPU 的执行权
join():在线程 a 中调用线程 b 的 join(),此时线程 a 就进入阻塞状态,直到线程 b 完全执行完以后,线程 a 才结束阻塞状态
stop():已过时,当执行此方法时,强制结束当前线程
sleep(long millis):让当前线程“睡眠”指定的 millis 毫秒,在指定的 millis 毫秒时间内,当前线程是阻塞状态
isAlive():判断当前线程是否存活
MAX_PRIORITY:10
MIN _PRIORITY:1
NORM_PRIORITY:5 --> 默认优先级
如何获取和设置当前线程的优先级
getPriority():获取线程的优先级
setPriority(int newPriority):设置线程的优先级
说明:高优先级的线程要抢占低优先级线程 CPU 的执行权,但是只是从概率上讲,高优先级的线程高概率的情况下被执行,并不意味着只有当高优先级的线程执行完以后,低优先级的线程才执行
线程的通信:wait()、notify()、 notifyAll():此三个方法定义在 Object 类中的
说明:
生命周期关注两个概念:状态,相应的方法
关注
阻塞:临时状态,不可以作为最终状态
死亡:最终状态
创建三个窗口买票,总票数为100张,使用实现 Runnable 接口的方式
问题,买票过程中,出现了重票、错票 --> 出现了线程安全的问题
出现的原因:当某个线程操作车票的过程中,尚未操作完成时,其它线程参与进来,也操作车票
如何解决:当一个线程 a 在操作 ticket 的时候,其它线程不能参与进来,直到线程 a 操作完 ticket 时,其它线程才可以开始操作 ticket,这种情况即使线程 a 出现了阻塞,也不能改变
在 Java 中,通过同步机制,来解决线程的安全问题
方式一:同步代码块
synchronized(同步监视器){
// 需要被同步的代码
}
说明:
共享数据:多个线程共同操作的变量,比如:ticket 就是共享数据
同步监视器,俗称:锁。任何一个类的对象,都可以充当锁
要求:多个线程必须要公用同一把锁
**补充 **
在实现 Runnable 接口创建多线程的方式中,可以考虑 this 充当同步监视器
继承 Thread 类创建多线程的方式中,慎用 this 充当同步监视器,考虑使用当前类充当同步监视器
方式二:同步方法
如果操作共享数据的代码完整的声明在一个方法中,不妨将此方法声明为同步的
同步方法仍然涉及到同步监视器,只是不需要显示声明
非静态同步方法的同步监视器是:this
静态同步方法的同步监视器是:当前类本身
方式三:Lock 锁 — JDK 5.0新增
优先使用顺序:Lock –> 同步代码块(已经进入了方法体,分配了相应资源) –> 同步方法(在方法体之外)
class Bank{
private static Bank instance = null;
private Bank(){
}
public static Bank getInstance() {
// 方式一:效率稍差
/*synchronized (Bank.class) {
if (instance == null){
instance = new Bank();
}
return instance;
}*/
// 方式二:效率更高
if (instance == null){
synchronized (Bank.class) {
if (instance == null){
instance = new Bank();
}
}
}
return instance;
}
}
面试题:写一个线程安全的单例模式
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
public class ThreadTest {
public static void main(String[] args) {
StringBuffer s1 = new StringBuffer();
StringBuffer s2 = new StringBuffer();
new Thread(){
@Override
public void run() {
synchronized (s1){
s1.append("a");
s2.append("1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s2){
s1.append("b");
s2.append("2");
System.out.println("s1 = " + s1);
System.out.println("s2 = " + s2);
}
}
}
}.start();
new Thread(() -> {
synchronized (s2){
s1.append("c");
s2.append("3");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s1){
s1.append("d");
s2.append("4");
System.out.println("s1 = " + s1);
System.out.println("s2 = " + s2);
}
}
}).start();
}
}
java.lang.Object
类中sleep() 和 wait() 的异同
相同点:
不同点:
两个方法声明的位置不同:Thread 类中声明 sleep(),Object 类中声明 wait()
调用的要求不同:sleep() 可以在任何需要的场景下调用,wait() 必须使用在同步代码块或同步方法中
关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep() 不会释放锁,wait() 会释放锁
创建线程的方式三:实现 Callable 接口 — JDK 5.0新增
步骤
public class ThreadNew {
public static void main(String[] args) {
NumTread numTread = new NumTread();
FutureTask future = new FutureTask(numTread);
new Thread(future).start();
try {
// get() 返回值即为 FutureTask 构造器形参 Callable 实现类重写的 call() 的返回值
Object sum = future.get();
System.out.println("总和为:" + sum);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
class NumTread implements Callable{
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 0; i <= 100; i++) {
if (i % 2 == 0){
System.out.println(i);
sum += i;
}
}
return sum;
}
}
说明:
创建线程的方式四:使用线程池
步骤
public class ThreadPool {
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(10);
ThreadPoolExecutor executor = (ThreadPoolExecutor) service;
// 设置线程池属性
// System.out.println(service.getClass());
// executor.setCorePoolSize(15);
// executor.setKeepAliveTime();
// 适合使用于 Runnable
service.execute(new NumberThread());
service.execute(new NumberThread1());
// service.submit(); 适合使用于 Callable
service.shutdown();
}
}
class NumberThread implements Runnable{
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
if (i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
class NumberThread1 implements Runnable{
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
if (i % 2 != 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
好处
面试题:Java 中多线程的创建有几种方式?四种
String:字符串,使用一对""引起来表示
String 声明为 final 的,不可被继承
String 实现了 Serializable
接口:表示字符串是支持序列化的
String 实现了 Comparable
接口:表示 String 可以比较大小
String 内部定义了 final char[] value
用于存储字符串数据
通过字面量的方式给一个字符串赋值,此时的字符串值声明在字符串常量池中
字符串常量池中是不会存储相同内容(使用 String 类的 equals() 比较,返回 true)的字符串的
当对字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的 value 进行赋值
当对现有的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的 value 进行赋值
当调用 String 的 replace() 方法修改指定的字符或字符串时,也需要重新指定内存区域赋值,不能使用原有的 value 进行赋值
@Test
public void test1(){
// 字面量的定义方式
String s1 = "abc";
String s2 = "abc";
s1 = "hello";
// 比较 s1 和 s2 的地址值
System.out.println("s1 == s2 = " + (s1 == s2));
System.out.println("s1 = " + s1);
System.out.println("s2 = " + s2);
String s3 = "abc";
s3 += "def";
System.out.println("s3 = " + s3);
System.out.println("s2 = " + s2);
String s4 = "abc";
String s5 = s4.replace('a', 'm');
System.out.println("s4 = " + s4);
System.out.println("s5 = " + s5);
}
@Test
public void test2(){
// 声明在方法区中的字符串常量池中
String s1 = "JavaEE";
String s2 = "JavaEE";
// 保存的地址值是数据在堆空间中开辟以后对应的地址值
String s3 = new String("JavaEE");
String s4 = new String("JavaEE");
System.out.println(s1 == s2); // true
System.out.println(s1 == s3); // false
System.out.println(s1 == s4); // false
System.out.println(s3 == s4); // false
}
String s = new String(“abc”); 方式创建对象,在内存中创建了几个对象
两个:一个是堆空间中 new 结构,另一个是 char[] 对应的常量池中的数据:“abc”
@Test
public void test3(){
String s1 = "javaEE";
String s2 = "hadoop";
String s3 = "javaEEhadoop";
String s4 = "javaEE" + "hadoop";
String s5 = s1 + "hadoop";
String s6 = "javaEE" + s2;
String s7 = s1 + s2;
System.out.println(s3 == s4); // true
System.out.println(s3 == s5); // false
System.out.println(s3 == s6); // false
System.out.println(s3 == s7); // false
System.out.println(s5 == s6); // false
System.out.println(s5 == s7); // false
System.out.println(s6 == s7); // false
// 返回值得到的 s8 使用的常量值中已经存在的"javaEEhadoop"
String s8 = s5.intern();
System.out.println(s8 == s4); // true
}
@Test
public void test4(){
String s1 = "javaEEhadoop";
String s2 = "javaEE";
String s3 = s2 + "hadoop";
System.out.println(s1 == s3);
final String s4 = "javaEE";
String s5 = s4 + "hadoop";
System.out.println(s1 == s5);
}
int length()
:返回字符串的长度: return value.length
char charAt(int index)
: 返回某索引处的字符 return value[index]
boolean isEmpty()
:判断是否是空字符串:return value.length == 0
String toLowerCase()
:使用默认语言环境,将 String 中的所有字符转换为小写
String toUpperCase()
:使用默认语言环境,将 String 中的所有字符转换为大写
String trim()
:返回字符串的副本,忽略前导空白和尾部空白
boolean equals(Object obj)
:比较字符串的内容是否相同
boolean equalsIgnoreCase(String anotherString)
:与 equals 方法类似,忽略大小写
String concat(String str)
:将指定字符串连接到此字符串的结尾。 等价于用“+”
int compareTo(String anotherString)
:比较两个字符串的大小
String substring(int beginIndex)
: 返回一个新的字符串, 它是此字符串的从 beginIndex 开始截取到最后的一个子字符串。
String substring(int beginIndex, int endIndex)
:返回一个新字符串,它是此字符串从 beginIndex 开始截取到 endIndex (不包含)的一个子字符串。
boolean endsWith(String suffix)
:测试此字符串是否以指定的后缀结束
boolean startsWith(String prefix)
:测试此字符串是否以指定的前缀开始
boolean startsWith(String prefix, int toffset)
:测试此字符串从指定索引开始的子字符串是否以指定前缀开始
boolean contains(CharSequence s)
:当且仅当此字符串包含指定的 char 值序列时,返回 true
int indexOf(String str)
:返回指定子字符串在此字符串中第一次出现处的索引
int indexOf(String str, int fromIndex)
:返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始
int lastIndexOf(String str)
:返回指定子字符串在此字符串中最右边出现处的索引
int lastIndexOf(String str, int fromIndex)
:返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索
indexOf 和 lastIndexOf 方法如果未找到都是返回-1
String replace(char oldChar, char newChar)
:返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 得到的。
String replace(CharSequence target, CharSequence replacement)
:使 用指定的字面值替换序列替换此字符串所有匹配字面值目标序列的子字符串。
String replaceAll(String regex, String replacement)
:使用给定的 replacement 替换此字符串所有匹配给定的正则表达式的子字符串。
String replaceFirst(String regex, String replacement)
:使用给定的 replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。
boolean matches(String regex)
:告知此字符串是否匹配给定的正则表达式。
String[] split(String regex)
:根据给定正则表达式的匹配拆分此字符串。
String[] split(String regex, int limit)
:根据匹配给定的正则表达式来拆分此字符串,最多不超过 limit 个,如果超过了,剩下的全部都放到最后一个元素中。
@Test
public void test1(){
String str1 = "123";
int num = Integer.parseInt(str1);
String str2 = String.valueOf(num);
String str3 = num + "";
System.out.println(str1 == str3);
}
@Test
public void test2(){
String str1 = "abc123";
char[] charArray = str1.toCharArray();
for (int i = 0; i < charArray.length; i++) {
System.out.println(charArray[i]);
}
char[] arr = new char[]{'h', 'e', 'l', 'l', 'o'};
String str2 = new String(arr);
System.out.println(str2);
}
编码:字符串 --> 字节
解码:编码的逆过程 字节 --> 字符串
说明:解码时,要求解码使用的字符集必须与编码时使用的字符集一致,否则会出现乱码
@Test
public void test3() throws UnsupportedEncodingException {
String str1 = "abc123中国";
// 使用默认的字符集进行转换
byte[] bytes = str1.getBytes();
System.out.println(Arrays.toString(bytes));
// 使用 gbk 进行编码
byte[] gbks = str1.getBytes("gbk");
System.out.println(Arrays.toString(gbks));
String str2 = new String(bytes);
System.out.println(str2);
String str3 = new String(gbks, "gbk");
System.out.println(str3);
}
public String myTrim(String str) {
if (str != null) {
// 用于记录从前往后首次索引位置不是空格的位置的索引
int start = 0;
// 用于记录从后往前首次索引位置不是空格的位置的索引
int end = str.length() - 1;
while (start < end && str.charAt(start) == ' ') {
start++;
}
while (start < end && str.charAt(end) == ' ') {
end--;
}
if (str.charAt(start) == ' ') {
return "";
}
return str.substring(start, end + 1);
}
return null;
}
/**
* 方式一:转换为 char[]
* @param str
* @param startIndex
* @param endIndex
* @return
*/
public String reverseChar(String str, int startIndex, int endIndex){
if (str != null) {
char[] arr = str.toCharArray();
for(int x = startIndex, y = endIndex; x < y; x++, y--){
char temp = arr[x];
arr[x] = arr[y];
arr[y] = temp;
}
return new String(arr);
}
return null;
}
/**
* 方式二:使用 String 的拼接
* @param str
* @param startIndex
* @param endIndex
* @return
*/
public String reverseString(String str, int startIndex, int endIndex){
if (str != null) {
String reverseStr = str.substring(0, startIndex);
for(int i = endIndex; i >= startIndex; i--){
reverseStr += str.charAt(i);
}
reverseStr += str.substring(endIndex + 1);
return reverseStr;
}
return null;
}
/**
* 方式三:使用 StringBuffer / StringBuilder 替换 String
* @param str
* @param startIndex
* @param endIndex
* @return
*/
public String reverseStringBuilder(String str, int startIndex, int endIndex){
if (str != null) {
StringBuilder builder = new StringBuilder(str.length());
builder.append(str.substring(0, startIndex));
for(int i = endIndex; i >= startIndex; i--){
builder.append(str.charAt(i));
}
builder.append(str.substring(endIndex + 1));
}
return null;
}
/**
* 获取 subStr 在 mainStr 中出现的次数
* @param mainStr
* @param subStr
* @return
*/
public int getCount(String mainStr, String subStr){
int mainLength = mainStr.length();
int subLength = subStr.length();
int count = 0;
int index = 0;
if (mainLength >= subLength){
// 方式一
/*while ((index = mainStr.indexOf(subStr)) != -1){
count++;
mainStr = mainStr.substring(index + subStr.length());
}*/
// 方式二
while ((index = mainStr.indexOf(subStr, index)) != -1){
count++;
index += subLength;
}
}
return count;
}
获取两个字符串中最大相同子串。比如 str1 = "abcwerthelloyuiodef“;str2 = “cvhellobnm”
提示:将短的那个串进行长度依次递减的子串与较长的串比较。
/**
* 如果存在多个长度相同的最大相同子串
* 此时先返回String[],后面可以用集合中的ArrayList替换,较方便
* @param str1
* @param str2
* @return
*/
public String[] getMaxSameSubString(String str1, String str2) {
if (str1 != null && str2 != null) {
StringBuffer sBuffer = new StringBuffer();
String maxString = (str1.length() > str2.length()) ? str1 : str2;
String minString = (str1.length() > str2.length()) ? str2 : str1;
int len = minString.length();
for (int i = 0; i < len; i++) {
for (int x = 0, y = len - i; y <= len; x++, y++) {
String subString = minString.substring(x, y);
if (maxString.contains(subString)) {
sBuffer.append(subString + ",");
}
}
// System.out.println(sBuffer);
if (sBuffer.length() != 0) {
break;
}
}
String[] split = sBuffer.toString().replaceAll(",$", "").split("\\,");
return split;
}
return null;
}
对字符串中字符进行自然顺序排序。
提示:
以 StringBuffer 为例
String str = new String(); // char[] value = new char[0];
String str = new String("abc"); // char[] value = new char[]{'a', 'b', 'c'};
StringBuffer sb1 = new StringBuffer(); // char[] value = new char[16];底层创建了一个长度是16的数组
sb1.append('a') // value[0] = 'a';
sb1.append('b') // value[1] = 'b';
StringBuffer sb2 = new StringBuffer("abc"); // char[] value = new char["abc".length() + 16];
问题1:System.out.println(sb2.length()); // 3
问题2:扩容问题:如果要添加的数据底层数组撑不下了,那就需要扩容底层的数组,默认情况下,扩容为原来容量的2倍 + 2,同时将原有数组中的元素复制到新的数组中
指导意义:开发中建议使用:StringBuffer(int capacity)
或 StringBuilder(int capacity)
StringBuffer append(xxx)
:提供了很多的append()方法,用于进行字符串拼接StringBuffer delete(int start,int end)
:删除指定位置的内容StringBuffer replace(int start, int end, String str)
:把[start,end)位置替换为strStringBuffer insert(int offset, xxx)
:在指定位置插入 xxxStringBuffer reverse()
:把当前字符序列逆转int indexOf(String str)
String substring(int start,int end)
:返回一个 从 start 开始到 end 索引结束的左闭右开区间的子字符串int length()
char charAt(int n)
void setCharAt(int n ,char ch)
总结:
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()
System 类中的 currentTimeMillis()
:返回当前时间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差,称为时间戳
@Test
public void test2(){
// 构造器一:Date():创建一个对应当前时间的 Date 对象
Date date1 = new Date();
System.out.println(date1);
System.out.println(date1.getTime());
// 构造器二:Date(long date):创建指定毫秒数的 Date 对象
Date date2 = new Date(1637809383273L);
System.out.println(date2);
java.sql.Date date3 = new java.sql.Date(99999323820232L);
System.out.println(date3);
Date date4 = new Date(221223445L);
// java.sql.Date date5 = (java.sql.Date) date4;
java.sql.Date date5 = new java.sql.Date(date3.getTime());
}
SimpleDateFormat 对日期 Date 类的格式化和解析
// 按照指定的方式格式化和解析:调用带参数的构造器
// SimpleDateFormat sdf1 = new SimpleDateFormat("yyyyy.MMMMM.dd GGG hh:mm aaa");
SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
// 格式化
System.out.println(sdf1.format(date));
// 解析:要求字符串必须符合 SimpleDateFormat 识别的格式(通过构造器参数体现),否则抛异常
System.out.println(sdf1.parse(sdf1.format(date)));
练习:
/**
* 练习一:字符串“2020-09-08”转换为 java.sql.Date
* 练习二:三天打鱼两天晒网 1990-01-01 xxxx-xx-xx 打鱼 晒网
* 总天数 % 5 == 1, 2, 3 : 打鱼
* 总天数 % 5 == 4, 0 : 晒网
* 总天数的计算
* 方式一:(date2.getTime() - date1.getTime()) / (1000 * 60 * 60 * 24)
* 方式二:1990-01-01 --> 2019-12-31 + 2020-01-01 --> 2020-09-08
*/
@Test
public void testExer() throws ParseException {
String birth = "2020-09-08";
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
Date date = simpleDateFormat.parse(birth);
java.sql.Date birthDate = new java.sql.Date(date.getTime());
System.out.println(birthDate);
}
@Test
public void testCalendar(){
// 1.实例化
// 方式一:创建其子类(GregorianCalendar)的对象
// 方式二:调用其静态方法 getInstance()
Calendar calendar = Calendar.getInstance();
// System.out.println(calendar.getClass());
// 2.常用方法
// get()
int days = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(days);
System.out.println(calendar.get(Calendar.DAY_OF_YEAR));
// set()
// Calender 可变性
calendar.set(Calendar.DAY_OF_MONTH, 22);
System.out.println(calendar.get(Calendar.DAY_OF_MONTH));
// add()
calendar.add(Calendar.DAY_OF_MONTH, -3);
System.out.println(calendar.get(Calendar.DAY_OF_MONTH));
// getTime():日历类 --> Date
Date date = calendar.getTime();
System.out.println(date);
// setTime():Date --> 日历类
Date date1 = new Date();
calendar.setTime(date1);
System.out.println(calendar.get(Calendar.DAY_OF_MONTH));
}
第一代:JDK1.0 Date 类
第二代:JDK1.1 Calendar 类,一定程度上替代了 Date 类
第三代:JDK1.8 提出了一套新的 API
可变性:像日期和时间这样的类应该是不可变的。
偏移性:Date中的年份是从1900开始的,而月份都从0开始。
格式化:格式化只对Date有用,Calendar则不行。
此外,它们也不是线程安全的;不能处理闰秒等。
说明:大多数开发者只会用到基础包和format包,也可能会用到temporal包。因此,尽管有68个新的公开类型,大多数开发者,大概将只会用到其中的三分之一。
方法 | 描述 |
---|---|
now() / * now(ZoneId zone) | 静态方法,根据当前时间创建对象/指定时区的对象 |
of() | 静态方法,根据指定日期/时间创建对象 |
getDayOfMonth() / getDayOfYear() | 获得月份天数(1-31) /获得年份天数(1-366) |
getDayOfWeek() | 获得星期几(返回一个 DayOfWeek 枚举值) |
getMonth() | 获得月份, 返回一个 Month 枚举值 |
getMonthValue() / getYear() | 获得月份(1-12) /获得年份 |
getHour() / getMinute() / getSecond() | 获得当前对象对应的小时、分钟、秒 |
withDayOfMonth() / withDayOfYear() / withMonth() / withYear() | 将月份天数、年份天数、月份、年份修改为指定的值并返回新的对象 |
plusDays() / plusWeeks() / plusMonth() / plusYears() / plusHours() | 向当前对象添加几天、几周、几个月、几年、几小时 |
minusMonths() / minusWeeks() / minusDays() / minusYears() / minusHours() | 从当前对象减去几月、几周、几天、几年、几小时 |
方法 | 描述 |
---|---|
now() | 静态方法,返回默认UTC时区的Instant类的对象 |
ofEpochMilli(long epochMilli) | 静态方法,返回在1970-01-01 00:00:00基础上加上指定毫秒 数之后的Instant类的对象 |
atOfSet(ZoneOfSet ofSet) | 结合即时的偏移来创建一个 OffsetDateTime |
toEpochMilli() | 返回1970-01-01 00:00:00到当前时间的毫秒数,即为时间戳 |
时间戳是指格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在的总秒数。
实例化方式
常用方法
方法 | 描述 |
---|---|
ofPattern(String pattern) | 静态方法,返回一个指定字符串格式的 DateTimeFormatter |
format(TemporalAccessor t) | 格式化一个日期、时间,返回字符串 |
parse(CharSequence text) | 将指定格式的字符序列解析为一个日期、时间 |
特别的:自定义的格式。如:ofPattern(“yyyy-MM-dd hh:mm:ss”)
// 方式三:自定义的格式。如:ofPattern(“yyyy-MM-dd hh:mm:ss”)
DateTimeFormatter formatter3 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
System.out.println(formatter3.format(localDateTime));
TemporalAccessor accessor = formatter3.parse("2021-11-25 21:53:44");
System.out.println(accessor);
/**
* ZoneId:类中包含了所有时区信息
*/
@Test
public void test4(){
// getAvailableZoneIds():获取对应的 ZoneId
Set<String> zoneIds = ZoneId.getAvailableZoneIds();
for (String zoneId : zoneIds) {
System.out.println(zoneId);
}
System.out.println();
// 获取"Asia/Tokyo"时区对应的时间
System.out.println(LocalDateTime.now(ZoneId.of("Asia/Tokyo")));
}
/**
* ZonedDateTime:带时区的日期时间
*/
@Test
public void test5(){
// now():获取本时区的 ZonedDateTime 对象
System.out.println(ZonedDateTime.now());
// now(ZoneId id):获取指定时区的 ZonedDateTime 对象
ZonedDateTime.now(ZoneId.of("Asia/Tokyo"));
}
用于计算两个“时间”间隔,以秒和纳秒为基准
方法 | 描述 |
---|---|
between(Temporal start, Temporal end) | 静态方法,返回 Duration 对象,表示两个时间的间隔 |
getNano() / getSeconds() | 返回时间间隔的纳秒数 / 返回时间间隔的秒数 |
toDays() / toHours() / toMinutes() / toMillis() / toNano() | 返回时间间隔的天数、小时数、分钟数、毫秒数、纳秒数 |
@Test
public void test6(){
//Duration:用于计算两个“时间”间隔,以秒和纳秒为基准
LocalTime localTime = LocalTime.now();
LocalTime localTime1 = LocalTime.of(15, 23, 32);
//between():静态方法,返回Duration对象,表示两个时间的间隔
Duration duration = Duration.between(localTime1, localTime);
System.out.println(duration);
System.out.println(duration.getSeconds());
System.out.println(duration.getNano());
LocalDateTime localDateTime = LocalDateTime.of(2016, 6, 12, 15, 23, 32);
LocalDateTime localDateTime1 = LocalDateTime.of(2017, 6, 12, 15, 23, 32);
Duration duration1 = Duration.between(localDateTime1, localDateTime);
System.out.println(duration1.toDays());
}
用于计算两个“日期”间隔,以年、月、日衡量
方法 | 描述 |
---|---|
between(LocalDate start, LocalDate end) | 静态方法,返回 Period 对象,表示两个本地日期的间隔 |
getYears() / getMonths() / getDays() | 返回此期间的年数,月数、天数 |
withYears(int years) / withMonths(int months) / withDays(int days) | 返回设置间隔指定年、月、日数以后的 Period 对象 |
@Test
public void test7(){
LocalDate localDate = LocalDate.now();
LocalDate localDate1 = LocalDate.of(2028, 3, 18);
Period period = Period.between(localDate, localDate1);
System.out.println(period);
System.out.println(period.getYears());
System.out.println(period.getMonths());
System.out.println(period.getDays());
Period period1 = period.withYears(2);
System.out.println(period1);
}
@Test
public void test8(){
// 获取当前日期的下一个周日是哪天?
TemporalAdjuster temporalAdjuster = TemporalAdjusters.next(DayOfWeek.SUNDAY);
LocalDateTime localDateTime = LocalDateTime.now().with(temporalAdjuster);
System.out.println(localDateTime);
// 获取下一个工作日是哪天?
LocalDate localDate = LocalDate.now().with(new TemporalAdjuster() {
@Override
public Temporal adjustInto(Temporal temporal) {
LocalDate date = (LocalDate) temporal;
if (date.getDayOfWeek().equals(DayOfWeek.FRIDAY)) {
return date.plusDays(3);
} else if (date.getDayOfWeek().equals(DayOfWeek.SATURDAY)) {
return date.plusDays(2);
} else {
return date.plusDays(1);
}
}
});
System.out.println("下一个工作日是:" + localDate);
}
Java 中的对象,正常情况下,只能进行比较:== 或 != ,不能使用 > 或 < 的,但是在开发场景中,需要对多个对象进行排序,言外之意,就需要比较对象的大小,如何实现?使用两个接口中的任何一个:Comparable 或 Comparator
public class Goods implements Comparable{
private String name;
private double price;
public Goods() {
}
public Goods(String name, double price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
@Override
public String toString() {
return "Goods{" +
"name='" + name + '\'' +
", price=" + price +
'}';
}
/**
* 指明商品比较大小的方式:按照价格从低到高排序,再按照产品名称从低到高排序
* @param o
* @return
*/
@Override
public int compareTo(Object o) {
if (o instanceof Goods){
Goods goods = (Goods) o;
// 方式一
if (this.price > goods.price){
return 1;
}else if (this.price < goods.price){
return -1;
}else {
return this.name.compareTo(goods.name);
}
// 方式二
// return Double.compare(this.price, goods.price);
}
throw new RuntimeException("传入的数据类型不一致");
}
}
背景
当元素的类型没有实现 java.lang.Comparable 接口而又不方便修改代码,或者实现了java.lang.Comparable 接口的排序规则不适合当前的操作,那么可以考虑使用 Comparator 的对象来排序
重写 compare(Object o1,Object o2) 方法,比较 o1 和 o2 的大小:
Comparator comparator = new Comparator() {
/**
* 按照字符串从大到小排序
* @param o1
* @param o2
* @return
*/
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof String && o2 instanceof String){
String s1 = (String) o1;
String s2 = (String) o2;
return -s1.compareTo(s2);
}
throw new RuntimeException("输入的数据类型不一致");
}
};
System类代表系统,系统级的很多属性和控制方法都放置在该类的内部。 该类位于java.lang包。
由于该类的构造器是private的,所以无法创建该类的对象,也就是无法实 例化该类。其内部的成员变量和成员方法都是static的,所以也可以很方便 的进行调用。
方法
native long currentTimeMillis()
:该方法的作用是返回当前的计算机时间,时间的表达格式为当前计算机时间和GMT时间(格林威治时间)1970年1月1号0时0分0秒所差的毫秒数。
void exit(int status)
: 该方法的作用是退出程序。其中status的值为0代表正常退出,非零代表 异常退出。使用该方法可以在图形界面编程中实现程序的退出功能等。
void gc()
:该方法的作用是请求系统进行垃圾回收。至于系统是否立刻回收,则 取决于系统中垃圾回收算法的实现以及系统执行时的情况。
String getProperty(String key)
:该方法的作用是获得系统中属性名为key的属性对应的值。系统中常见的属性名以及属性的作用如下表所示:
属性名 | 属性说明 |
---|---|
java.version | Java 运行时环境版本 |
java.home | Java安装目录 |
os.name | 操作系统的名称 |
os.version | 操作系统的版本 |
user.name | 用户的账户名称 |
user.home | 用户的主目录 |
user.dir | 用户的当前工作目录 |
java.lang.Math 提供了一系列静态方法用于科学计算。其方法的参数和返回值类型一般为 double 型。
说明:
代码举例:
@Test
public void testBigInteger() {
BigInteger bi = new BigInteger("12433241123");
BigDecimal bd = new BigDecimal("12435.351");
BigDecimal bd2 = new BigDecimal("11");
System.out.println(bi);
// System.out.println(bd.divide(bd2));
System.out.println(bd.divide(bd2, BigDecimal.ROUND_HALF_UP));
System.out.println(bd.divide(bd2, 25, BigDecimal.ROUND_HALF_UP));
}
JDK5.0之前,自定义枚举类
// 自定义枚举类
class Season{
// 1.声明 Season 对象的属性,private final 修饰
private final String seasonName;
private final String seasonDesc;
// 2.私有化类的构造器
private Season(String seasonName, String seasonDesc){
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}
// 3.提供当前枚举类的多个对象:public static final 的
public static final Season SPRING = new Season("春天", "春暖花开");
public static final Season SUMMER = new Season("夏天", "夏日炎炎");
public static final Season AUTUMN = new Season("秋天", "秋高气爽");
public static final Season WINTER = new Season("冬天", "冰天雪地");
// 4.其它诉求1:获取枚举类对象的属性
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
// 4.其它诉求2:提供 toString()
@Override
public String toString() {
return "Season{" +
"seasonName='" + seasonName + '\'' +
", seasonDesc='" + seasonDesc + '\'' +
'}';
}
}
// 使用 enum 关键字定义枚举类
enum Season1{
// 1.提供当前枚举类的对象,多个对象之间用逗号隔开
SPRING("春天", "春暖花开").
SUMMER("夏天", "夏日炎炎"),
AUTUMN("秋天", "秋高气爽"),
WINTER("冬天", "冰天雪地");
// 2.声明 Season 对象的属性,private final 修饰
private final String seasonName;
private final String seasonDesc;
// 3.私有化类的构造器
Season1(String seasonName, String seasonDesc){
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}
// 4.其它诉求1:获取枚举类对象的属性
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
}
继承于 java.lang.Enum
类
values()
:返回枚举类型的对象数组。该方法可以很方便地遍历所有的枚举值。valueOf(String str)
:可以把一个字符串转为对应的枚举类对象。要求字符串必须是枚举类对象的“名字”。如不是,会有运行时异常:IllegalArgumentException。toString()
:返回当前枚举类对象常量的名称public static void main(String[] args) {
Season1 summer = Season1.SUMMER;
System.out.println(summer);
System.out.println(Season1.class.getSuperclass());
// values()
Season1[] values = Season1.values();
for (Season1 value : values) {
System.out.println(value);
}
for (Thread.State value : Thread.State.values()) {
System.out.println(value);
}
// valueOf(String objName):返回枚举类中对象名是 objName 的对象
// 如果没有 objName 的枚举类对象,则抛异常 IllegalArgumentsException
Season1 winter = Season1.valueOf("WINTER");
System.out.println(winter);
}
interface Info{
void show();
}
// 使用 enum 关键字定义枚举类
enum Season1 implements Info{
// 1.提供当前枚举类的对象,多个对象之间用逗号隔开
SPRING("春天", "春暖花开"){
@Override
public void show() {
System.out.println("春天在哪里");
}
},
SUMMER("夏天", "夏日炎炎") {
@Override
public void show() {
System.out.println("宁夏");
}
},
AUTUMN("秋天", "秋高气爽") {
@Override
public void show() {
System.out.println("秋天不回来");
}
},
WINTER("冬天", "冰天雪地") {
@Override
public void show() {
System.out.println("大约在冬季");
}
};
// 2.声明 Season 对象的属性,private final 修饰
private final String seasonName;
private final String seasonDesc;
// 3.私有化类的构造器
Season1(String seasonName, String seasonDesc){
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}
// 4.其它诉求1:获取枚举类对象的属性
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
}
JDK5.0新增
Annotation 其实就是代码里的特殊标记, 这些标记可以在编译, 类加载,运行时被读取,并执行相应的处理,通过使用 Annotation, 程序员可以在不改变原有逻辑的情况下, 在源文件中嵌入一些补充信息
在 JavaSE 中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。在 JavaEE / Android 中注解占据了更重要的角色,例如用来配置应用程序的任何切面,代替 JavaEE 旧版中所遗留的繁冗代码和 XML 配置等
框架 = 注解 + 反射机制 + 设计模式
参照 @SuppressWarings 定义
@Inherited
@Repeatable(MyAnnotations.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({
ElementType.TYPE,
ElementType.FIELD,
ElementType.METHOD,
ElementType.LOCAL_VARIABLE,
ElementType.TYPE_PARAMETER,
ElementType.TYPE_USE
})
public @interface MyAnnotation {
String value() default "hello";
}
说明:
元注解:对现有的注解进行解释说明的注解
通过反射获取注解信息
前提:要求此注解的元注解 Retention 中声明的生命周期状态为:RUNTIME
集合、数组都是对多个数据进行存储操作的结构,简称 Java 容器
说明:此时的存储,主要指的是内存层面的存储,不涉及到持久化的存储(.txt, .jpg, .avi,数据库中)
一旦初始化以后,其长度就确定了
数组一旦定义好,其元素的类型也就确定了,也就只能操作指定类型的数据了
比如:String[] arr int[] arr;
一旦初始化以后,其长度就不可修改
数组中提供方法非常有限,对于添加、删除、插入数据等操作,非常不便
获取数组中实际元素的个数的需求,数组没有现成的属性或方法可用
数组存储数据的特点:有序、可重复,对于无序、不可重复的需求,数组不能满足
|---- Collection 接口:单列集合,用来存储一个一个的数据
|---- List 接口:存储有序的、可重复的数据 --> “动态"数组
|---- ArrayList
|---- LinkedList
|---- Vector
|---- Set 接口:存储无序的、不可重复的数据 --> 高中讲的"集合“
|---- HashSet
|---- LinkedHashSet
|---- TreeSet
|---- Map 接口:双列集合,用来存储一对(key : value)一对的数据 --> 高中函数: y = f(x)
|---- HashMap
|---- LinkedHashMap
|---- TreeMap
|---- HashTable
|---- Properties
|---- Collection 接口:单列集合,用来存储一个一个的数据
|---- List 接口:存储有序的、可重复的数据 --> “动态"数组
|---- ArrayList
|---- LinkedList
|---- Vector
|---- Set 接口:存储无序的、不可重复的数据 --> 高中讲的"集合“
|---- HashSet
|---- LinkedHashSet
|---- TreeSet
对应图示:
add(Object obj)
:将元素 obj 添加到元素集合中addAll(Collection collection)
:将 collection 集合中的元素添加到当前的集合中size()
:获取添加的元素的个数isEmpty()
:判断当前集合是否为空clear()
:清空集合元素contains(Object obj)
:判断当前集合中是否包含 obj,在判断时会调用 obj 对象所在类的 equals()containsAll(Collection collection)
:判断形参 collection 中的所有元素是否都存在于当前集合中remove(Object obj)
:从当前集合中移除 obj 元素removeAll(Collection collection)
:差集,从当前集合中移除 collection 中所有的元素retainsAll(Collection collection)
:交集,获取当前集合和 collection 集合的交集,并返回给当前集合equals(Object obj)
:要想返回 true,就要判断当前集合和形参集合元素都相同hashCode()
:返回当前对象的哈希值toArray()
:集合转换为数组iterator()
返回此集合中的元素的迭代器// 8.toArray():集合转换为数组
Object[] arr = collection.toArray();
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
// 扩展:数组转换为集合:调用 Arrays 类的静态方法 asList()
List<String> list = Arrays.asList(new String[] {"aa", "bb", "cc"});
System.out.println(list);
java.utils
包下定义的迭代器接口:Iterator遍历集合 Collection 元素
collection.iterator() 返回一个迭代器实例
Iterator iterator = collection.iterator();
// hasNext():判断是否会有下一个元素
while (iterator.hasNext()){
// next():指针下移;将下移以后集合位置上的元素返回
System.out.println(iterator.next());
}
// 如果还未调用 next() 或在上一次调用 next 方法之后已经调用了 remove 方法,
// 再调用 remove 都会报 IllegalStateException。
// 内部定义了 remove(),可以在遍历的时候,删除集合中的元素,此方法不同于集合直接调用 remove()
@Test
public void test3() {
Collection collection = new ArrayList();
collection.add(123);
collection.add(456);
collection.add(new Person("Jerry", 20));
collection.add(new String("Tom"));
collection.add(false);
Iterator iterator = collection.iterator();
// 删除集合中 “Tom” 数据
while (iterator.hasNext()){
Object obj = iterator.next();
if ("Tom".equals(obj)){
iterator.remove();
}
}
iterator = collection.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
@Test
public void test1() {
Collection collection = new ArrayList();
collection.add(123);
collection.add(456);
collection.add(new Person("Jerry", 20));
collection.add(new String("Tom"));
collection.add(false);
// for(集合中元素的类型 局部变量 : 集合对象)
// 内部仍然调用了迭代器
for(Object obj : collection){
System.out.println(obj);
}
}
说明:内部仍然调用了迭代器
@Test
public void test2(){
int[] arr = new int[]{1, 2, 3, 4, 5, 6};
for (int a : arr){
System.out.println(a);
}
}
存储有序的、可重复的数据
add(Object obj)
remove(int index)
/ remove(Object obj)
set(int index, Object ele)
get(int index)
add(int index, Object ele)
size()
|---- Collection 接口:单列集合,用来存储一个一个的数据
|---- List 接口:存储有序的、可重复的数据 --> “动态"数组,替换原有数组
|---- ArrayList
作为 List 接口的主要实现类
线程不安全,效率高
底层使用 Object[] elementData 存储
|---- LinkedList
对于频繁的插入和删除,使用此类效率比 ArrayList 高
底层使用双向列表存储
|---- Vector
作为 List 接口的古老实现类
线程安全,效率低
底层使用 Object[] elementData 存储
ArrayList 的源码分析
JDK7 情况下
ArrayList list = new ArrayList(); // 底层创建了长度是10的 Object[] 数组 elementData
list.add(123); // elementData[0] = new Integer(123);
…
list.add(11); // 如果此次的添加导致底层 elementData 数组容量不够,则扩容,默认情况下,扩容为原来容量的1.5倍,同时需要将原有数组中的数据复制到新的数组中
结论:建议开发中使用带参的构造器:ArrayList list = ArrayList(int initialCapacity);
JDK8 中的变化
ArrayList list = new ArrayList(); // 底层 Object[] elementData = {},并没有创建长度为10的数组
list.add(123); // 第一次调用 add() 时,底层才创建了长度为10的数组,并将数据添加到 elementData 中
…
后续的添加和扩容操作与 JDK7 无异
小结
LinkedList 的源码分析
LinkedList list = new LinkedList(); // 内部声明了 Node 类型的 first 和 last 属性,默认值为 null
list.add(123); // 将123封装到 Node 中,创建了 Node 对象
其中,Node 定义为:体现了 LinkedList 的双向链表的说法
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
Vector 的源码分析
通过 Vector() 构造器创建对象时,底层都创建了长度为10的数组,在扩容反码,默认扩容为原来数组长度的2倍
添加的对象所在的类要重写 equals()
面试题:ArrayList、LinkedList、Vector 三者的异同?
具体的:
以 HashSet 为例说明
以 HashSet 为例
Set 接口中没有额外定义新的方法,使用的都是 Collection 中声明过的方法
|---- Collection 接口:单列集合,用来存储一个一个的数据
|---- Set 接口:存储无序的、不可重复的数据 --> 高中讲的"集合“
|---- HashSet
作为 Set 接口的主要实现类
线程不安全的
可以存储 null 值
|---- LinkedHashSet
作为 HashSet 的子类,在添加数据的同时,每个数据还维护了两个引用,
记录此数据的前一个数据和后一个数据
遍历其内部数据时,可以按照添加的顺序遍历
对于频繁的遍历操作:LinkedHashSet 效率高于 HashSet
|---- TreeSet
可以按照添加对象的指定属性,进行排序
自然排序
@Test
public void test1(){
TreeSet set = new TreeSet();
// 失败:不能添加不同类的对象
// set.add(456);
// set.add(123);
// set.add("AA");
// set.add("CC");
// set.add(new User("Tom", 12));
// set.add(34);
// set.add(-34);
// set.add(43);
// set.add(11);
// set.add(8);
set.add(new User("Tom", 12));
set.add(new User("Jerry", 32));
set.add(new User("Jim", 22));
set.add(new User("Mike", 65));
set.add(new User("Jack", 33));
set.add(new User("Jack", 56));
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
定制排序
@Test
public void test2() {
Comparator comparator = new Comparator() {
/**
* 按照年龄从小到大排列
* @param o1
* @param o2
* @return
*/
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof User && o2 instanceof User){
User u1 = (User) o1;
User u2 = (User) o2;
return Integer.compare(u1.getAge(), u2.getAge());
}
throw new RuntimeException("输入的数据类型不匹配");
}
};
TreeSet set = new TreeSet(comparator);
set.add(new User("Tom", 12));
set.add(new User("Jerry", 32));
set.add(new User("Jim", 22));
set.add(new User("Mike", 65));
set.add(new User("Jack", 33));
set.add(new User("Jack", 56));
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
HashSet / LinkedHashSet
向 Set(HashSet / LinkedHashSet) 中添加的数据,其所在的类一定要重写 hashCode() 和 equals()
要求
重写的 hashCode() 和 equals() 尽可能保持一致性:相等的对象必须具有相等的散列码
重写两个方法的小技巧:对象中用作 equals() 方法比较的 Field,都应该用来计算 hashCode 值。
TreeSet
|---- Map:双列数据,存储 key-value 对的数据 --> 类似于高中函数:y = f(x)
|---- HashMap
作为 Map 的主要实现类
线程不安全的,效率高
存储 null 的 key 和 value
|---- LinkedHashMap
保证在遍历 map 元素时,可以按照添加的顺序实现遍历
原因:在原有的 HashMap 底层结构基础上,添加了一对指针,指向前一个和后一个元素
对于频繁的遍历操作,此类执行效率高于 HashMap
|---- TreeMap
保证按照添加的 key-value 对进行排序,实现排序遍历,此时考虑 key 的自然排序和定制排序
底层使用红黑树
|---- Hashtable
作为 Map 的古老实现类
线程安全的,效率低
不能存储 null 的 key 和 value
|---- Properties
常用来处理配置文件
key 和 value 都是 String 类型
HashMap 的底层:
面试题:
Map 中的 key:无序的、不可重复的,使用 Set 存储所有的 key --> key 所在的类要重写 equals() 和 hashCode()
Map 中的 value:有序的、可重复的,使用 Collection 存储所有的 value --> value 所在的类要重写 equals()
一个键值对:key-value 构成了一个 Entry 对象
Map 中的 Entry:无序的、不可重复的,使用 Set 存储所有的 entry
图示:
put(Object key,Object value)
remove(Object key)
put(Object key,Object value)
get(Object key)
size()
keySet()
values()
entrySet()
关于情况2和情况3:此时的 key1-value1 和原来的数据以链表的方式存储
在不断的添加过程中,会涉及到扩容问题,默认的扩容方式:扩容为原来容量的2倍,并将原有的数据复制过来
LinkedHashMap 底层使用的结构与 HashMap 相同,因为 LinkedHashMap 继承于 HashMap,区别就在于:LinkedHashMap 内部提供了 Entry,替换 HashMap 中的 Node
HashMap 中的 Node:
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + "=" + value; }
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}
LinkedHashMap 中的 Entry
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after; // 能够记录添加元素的先后顺序
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
向 TreeMap 中添加 key-value,要求 key 必须是有同一个类创建的对象,因为要按照 key 进行排序:自然排序 定制排序
常用于处理属性文件,key 和 value 都是字符串类型
public static void main(String[] args) {
FileInputStream fileInputStream = null;
try {
Properties prop = new Properties();
fileInputStream = new FileInputStream("jdbc.properties");
prop.load(fileInputStream);
String name = prop.getProperty("name");
String password = prop.getProperty("password");
System.out.println("name = " + name);
System.out.println("password = " + password);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
操作 Collection 和 Map 的工具类
reverse(List)
:反转 List 中元素的顺序shuffle(List)
:对 List 集合元素进行随机排序sort(List)
:根据元素的自然顺序对指定 List 集合元素按升序排序sort(List,Comparator)
:根据指定的 Comparator 产生的顺序对 List 集合元素进行排序swap(List,int, int)
:将指定 list 集合中的 i 处元素和 j 处元素进行交换Object max(Collection)
:根据元素的自然顺序,返回给定集合中的最大元素Object max(Collection,Comparator)
:根据 Comparator 指定的顺序,返回给定集合中的最大元素Object min(Collection)
:根据元素的自然顺序,返回给定集合中的最小元素Object min(Collection,Comparator)
:根据 Comparator 指定的顺序,返回给定集合中的最小元素int frequency(Collection,Object)
:返回指定集合中指定元素的出现次数void copy(List dest, List src)
:将 src 中的内容复制到 dest 中boolean replaceAll(List list,Object oldVal,Object newVal)
:使用新值替换 List 对象的所有旧值说明:ArrayList 和 HashMap 都是线程不安全的,如果程序要求线程安全,可以将 ArrayList 和 HashMap转换为线程安全的,使用 synchronizedList(List list)
和 synchronizedMap(Map map)
Collection 和 Collections 的区别
数据结构(Data Structure)是一门和计算机硬件与软件都密切相关的学科,它的研究重点是在计算机的程序设计领域中探讨如何在计算机中组织和存储数据并进行高效率的运用,涉及的内容包含:数据的逻辑关系、数据的存储结构、排序算法(Algorithm)、查找(或搜索)等。
**序能否快速而高效地完成预定的任务,取决于是否选对了数据结构,而程序是否能清楚而正确地把问题解决,则取决于算法。**算法是计算机处理信息的本质,因为计算机程序本质上是一个算法来告诉计算机确切的步骤来执行一个指定的任务。
所以大家认为:“Algorithms + Data Structures = Programs”(出自:Pascal之父Nicklaus Wirth)
总结:算法是为了解决实际问题而设计的,数据结构是算法需要处理的问题载体。
说明:
所谓泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类 型或者是某个方法的返回值及参数类型。这个类型参数将在使用时(例如, 继承或实现这个接口,用这个类型声明变量、创建对象时)确定(即传入实 际的类型参数,也称为类型实参)。
集合容器类在设计阶段/声明阶段不能确定这个容器到底实际存的是什么类型的 对象,所以在JDK1.5之前只能把元素类型设计为Object,JDK1.5之后使用泛型来 解决。因为这个时候除了元素的类型不确定,其他的部分是确定的,例如关于 这个元素如何保存,如何管理等是确定的,因此此时把元素的类型设计成一个 参数,这个类型参数叫做泛型。Collection,List,ArrayList 这个就是类型参数,即泛型。
@Test
public void test1(){
ArrayList list = new ArrayList();
list.add(78);
list.add(77);
list.add(89);
list.add(88);
// 问题一:类型不安全
// list.add("Tom");
for (Object score : list) {
// 问题二:强转时,可能出现 ClassCastException
int stuScore = (int) score;
System.out.println(stuScore);
}
}
图示:
@Test
public void test2(){
// ArrayList<Integer> list = new ArrayList<Integer>();
// JDK7 新特性:类型推断
ArrayList<Integer> list = new ArrayList<>();
list.add(78);
list.add(87);
list.add(99);
list.add(65);
// 编译时,就会进行类型检查,保证数据的安全
// list.add("65");
/*for(Integer score : list){
// 避免了强转操作
int stuScore = score;
System.out.println(stuScore);
}*/
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()){
int stuScore = iterator.next();
System.out.println(stuScore);
}
}
图示:
@Test
public void test3(){
Map<String, Integer> map = new HashMap<>();
map.put("Tom", 87);
map.put("Jerry", 87);
map.put("Jack", 67);
// map.put(123, "67");
Set<Map.Entry<String, Integer>> entry = map.entrySet();
Iterator<Map.Entry<String, Integer>> iterator = entry.iterator();
while (iterator.hasNext()){
Map.Entry<String, Integer> e = iterator.next();
String key = e.getKey();
Integer value = e.getValue();
System.out.println(key + " --- " + value);
}
}
[Order.java]
public class Order<T> {
String orderName;
int orderId;
// 类的内部结构就可以使用类的泛型
T orderT;
public Order(){
// 编译不通过
// T[] arr = new T[10];
// 编译通过
T[] arr = (T[]) new Object[10];
}
public Order(String orderName, int orderId, T orderT) {
this.orderName = orderName;
this.orderId = orderId;
this.orderT = orderT;
}
public String getOrderName() {
return orderName;
}
public void setOrderName(String orderName) {
this.orderName = orderName;
}
public int getOrderId() {
return orderId;
}
public void setOrderId(int orderId) {
this.orderId = orderId;
}
public T getOrderT() {
return orderT;
}
public void setOrderT(T orderT) {
this.orderT = orderT;
}
@Override
public String toString() {
return "Order{" +
"orderName='" + orderName + '\'' +
", orderId=" + orderId +
", orderT=" + orderT +
'}';
}
// 静态方法中不能使用类的泛型
/*public static void show(){
System.out.println(orderT);
}*/
public void show(){
// 编译不通过
/*try {
}catch (T t){
}*/
}
// 泛型方法:在方法中出现了泛型的结构,泛型参数与类的泛型参数没有任何关系
// 换句话说,泛型方法所属的类是不是泛型类都没有关系
// 泛型方法可以声明为静态的,原因:泛型参数是在调用方法时确定的,并非在实例化类时确定
public static <E> List<E> copyFromArrayToList(E[] arr){
ArrayList<E> list = new ArrayList<>();
for (E e : arr) {
list.add(e);
}
return list;
}
}
[SubOrder.java]
public class SubOrder extends Order<Integer> {
public static <E> List<E> copyFromArrayToList(E[] arr){
ArrayList<E> list = new ArrayList<>();
for (E e : arr) {
list.add(e);
}
return list;
}
}
[SubOrder1.java]
public class SubOrder1<T> extends Order<T>{
}
测试:
public class GenericTest1 {
@Test
public void test1(){
// 如果定义了泛型类,实例化没有指明类的泛型,则认为此泛型类型为 Object 类型
// 要求:如果定义了类是带泛型的,建议在实例化时要指明类的泛型
Order order = new Order();
order.setOrderT(123);
order.setOrderT("123");
// 建议:实例化时指明类的泛型
Order<String> order1 = new Order<>("orderAA",
1001, "order:AA") ;
order1.setOrderT("AA:hello");
}
@Test
public void test2(){
// 由于子类在继承带泛型的父类时,指明了泛型类型,则实例化子类对象时,不再需要指明泛型
SubOrder subOrder = new SubOrder();
subOrder.setOrderT(1122);
SubOrder1<String> subOrder1 = new SubOrder1<>();
subOrder1.setOrderT("order2...");
}
@Test
public void test3(){
// 泛型不同的引用不能相互赋值
ArrayList<String> list1 = null;
ArrayList<Integer> list2 = new ArrayList<>();
// list1 = list2;
Person p1 = null;
Person p2 = null;
p1 = p2;
}
// 测试泛型方法
@Test
public void test4(){
Order<String> order = new Order<>();
Integer[] arr = {1, 2, 3, 4};
// 泛型方法在调用时,指明泛型参数的类型
List<Integer> list = order.copyFromArrayToList(arr);
System.out.println(list);
}
}
泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如:
<E1,E2,E3>
泛型类的构造器如下:public GenericClass(){}。 而下面是错误的:public GenericClass(){}
实例化后,操作原来泛型位置的结构必须与指定的泛型类型一致。
泛型不同的引用不能相互赋值。
尽管在编译时ArrayList和ArrayList是两种类型,但是,在运行时只有
一个ArrayList被加载到JVM中。
泛型如果不指定,将被擦除,泛型对应的类型均按照Object处理,但不等价 于Object。经验:泛型要使用一路都用。要不用,一路都不要用。
如果泛型结构是一个接口或抽象类,则不可创建泛型类的对象。
jdk1.7,泛型的简化操作:ArrayList flist = new ArrayList<>();
泛型的指定中不能使用基本数据类型,可以使用包装类替换。
在类/接口上声明的泛型,在本类或本接口中即代表某种类型,可以作为非静态 属性的类型、非静态方法的参数类型、非静态方法的返回值类型。但在静态方法 中不能使用类的泛型。
异常类不能是泛型的
不能使用new E[]。但是可以:E[] elements = (E[])new Object[capacity];
参考:ArrayList源码中声明:Object[] elementData,而非泛型参数类型数组。
父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型:
结论:子类必须是“富二代”,子类除了指定或保留父类的泛型,还可以增加自己的泛型
[DAO.java]:定义了操作数据库中的表的通用操作。ORM 思想(数据库中的表和 Java 中的类对应)
public class DAO<T> {
// 添加一条记录
public void add(T t){
}
// 删除一条记录
public boolean remove(int index){
return false;
}
// 修改一条记录
public void update(int index, T t){
}
// 查询一条记录
public T getIndex(int index){
return null;
}
// 查询多条记录
public List<T> getForList(int index){
return null;
}
public <E> E getValue(){
return null;
}
}
[CustomerDAO.java]
public class CustomerDAO extends DAO<Customer>{
}
[StudentDAO.java]
public class StudentDAO extends DAO<Student>{
}
虽然类 A 是类 B 的父类,但是 G 和 G 二者不具备子父类关系,二者是并列关系
补充:类 A 是类 B 的父类,A
@Test
public void test1(){
Object obj = null;
String str = null;
obj = str;
Date date = new Date();
// 编译不通过
// str = date;
Object[] arr1 = null;
String[] arr2 = null;
arr1 = arr2;
List<Object> list1 = null;
List<String> list2 = new ArrayList<>();
// 此时的 list1 和 list2 的类型不具备子父类关系
// 编译不通过
// list1 = list2;
/*
反证法
假设 list1 = list2; // 导致混入非 String 的数据,出错
*/
show(list1);
// show(list2);
}
public void show(List<Object> list){
}
@Test
public void test2(){
AbstractList<String> list1 = null;
List<String> list2 = null;
ArrayList<String> list3 = null;
list1 = list3;
list2 = list3;
}
通配符:?
类 A 是类 B 的父类,G 和 G 是没有关系的,二者共同的父类是:G<?>
@Test
public void test3() {
List<Object> list1 = null;
List<String> list2 = null;
List<?> list = null;
list = list1;
list = list2;
// print(list1);
// print(list2);
List<String> list3 = new ArrayList<>();
list3.add("AA");
list3.add("BB");
list3.add("CC");
list = list3;
// 添加(写入):对于 List<?> 就不能向其内部添加数据,除了添加 null 之外
// list.add("DD");
list.add(null);
// 获取(读取):允许读取数据,读取的数据类型为 Object
Object o = list.get(0);
System.out.println(o);
}
public void print(List<?> list){
Iterator<?> iterator = list.iterator();
while (iterator.hasNext()){
Object obj = iterator.next();
System.out.println(obj);
}
}
@Test
public void test3() {
List<Object> list1 = null;
List<String> list3 = new ArrayList<>();
list3.add("AA");
list3.add("BB");
list3.add("CC");
list = list3;
// 添加(写入):对于 List<?> 就不能向其内部添加数据,除了添加 null 之外
// list.add("DD");
list.add(null);
// 获取(读取):允许读取数据,读取的数据类型为 Object
Object o = list.get(0);
System.out.println(o);
}
@Test
public void test4(){
List<? extends Person> list1 = null;
List<? super Person> list2 = null;
List<Student> list3 = new ArrayList<>();
List<Person> list4 = new ArrayList<>();
List<Object> list5 = new ArrayList<>();
list1 = list3;
list1 = list4;
// list1 = list5;
// list2 = list3;
list2 = list4;
list2 = list5;
// 读取数据
list1 = list3;
Person person = list1.get(0);
// 编译不通过
// Person person1 = list1.get(0);
list2 = list4;
Object obj = list2.get(0);
// 写入数据:
// list1.add(new Student());
list2.add(new Person());
list2.add(new Student());
}
File(String filepath)
File(String parentPath, String childPath)
File(File parentFile, String childPath)
说明:
IDEA
Eclipse
File类的获取功能
public String getAbsolutePath()
:获取绝对路径
public String getPath()
:获取路径
public String getName()
:获取名称
public String getParent()
:获取上层文件目录路径。若无,返回 null
public long length()
:获取文件长度(即:字节数)。不能获取目录的长度。
public long lastModified()
:获取最后一次的修改时间,毫秒值
如下的两个方法适用于文件目录
public String[] list()
:获取指定目录下的所有文件或者文件目录的名称数组
public File[] listFiles()
:获取指定目录下的所有文件或者文件目录的 File 数组
File类的重命名功能
public boolean renameTo(File dest)
:把文件重命名为指定的文件路径File类的判断功能
public boolean isDirectory()
:判断是否是文件目录public boolean isFile()
:判断是否是文件public boolean exists()
:判断是否存在public boolean canRead()
:判断是否可读public boolean canWrite()
:判断是否可写public boolean isHidden()
:判断是否隐藏File 类的创建功能
public boolean createNewFile()
:创建文件。若文件存在,则不创建,返回falsepublic boolean mkdir()
:创建文件目录。如果此文件目录存在,就不创建了。 如果此文件目录的上层目录不存在,也不创建。public boolean mkdirs()
:创建文件目录。如果上层文件目录不存在,一并创建注意事项:如果你创建文件或者文件目录没有写盘符路径,那么,默认在项目 路径下。
File 类的删除功能
public boolean delete()
:删除文件或者文件夹
删除注意事项:Java中的删除不走回收站。 要删除一个文件目录,请注意该文件目录内不能包含文件或者文件目录
操作数据单位
数据的流向
流的角色
图示:
分类 | 字节输入流 | 字节输出流 | 字符输入流 | 字符输出流 |
---|---|---|---|---|
抽象基类 | InputStream | OutputStream | Reader | Writer |
访问文件 | FileInputStream | FileOutputStream | FileReader | FileWriter |
访问数组 | ByteArrayInputStream | ByteArrayOutputStream | CharArrayReader | CharArrayWriter |
访问管道 | PipedInputStream | PipedOutputStream | PipedReader | PipedWriter |
访问字符串 | StringReader | StringWriter | ||
缓冲流 | BufferedInputStream | BufferedOutputStream | BufferedReader | BufferedWriter |
转换流 | InputStreamReader | OutPutStreamWriter | ||
对象流 | ObjectInputStream | ObjectOutputStream | ||
打印流 | PrintStream | PrintWriter | ||
推回输入流 | PushbackInputStream | PushbackReader | ||
特殊流 | DataInputStream | DataOutputStream |
抽象基类 | 文件流 | 缓冲流 |
---|---|---|
InputStream | FileInputStream(read(byte[] buffer)) | BufferedInputStream(read(byte[] buffer)) |
OutputStream | FileOutputStream(write(byte[] buffer, 0, len)) | BufferedInputStream(write(byte[] buffer, 0, len) / flush()) |
Reader | FileReader(read(char[] cbuf)) | BufferedReader(read(char[] cbuf) / readLine()) |
Writer | FileWriter(write(char[] cbuf, 0, len)) | BufferedWriter(write(char[] cbuf, 0, len) / flush()) |
说明:程序中出现的异常需要使用 try-catch-finally 处理
write(char[] / byte[] buffer, 0, len)
说明:程序中出现的异常需要使用 try-catch-finally 处理
说明点:
@Test
public void testFileReader1() {
FileReader fileReader = null;
try {
// 1.File 类的实例化
File file = new File("hello.txt");
// 2.FileReader 流的实例化
fileReader = new FileReader(file);
// 3.读入的操作
// read(char[] cbuf):返回每次读入 cbuf 数组中的字符的个数,如果达到文件末尾,返回-1
char[] cbuf = new char[5];
int len = 0;
while ((len = fileReader.read(cbuf)) != -1){
// 错误的写法
/*for (int i = 0; i < cbuf.length; i++) {
System.out.print(cbuf[i]);
}*/
// 正确的写法
/*for (int i = 0; i < len; i++) {
System.out.print(cbuf[i]);
}*/
// 错误的写法
// System.out.print(new String(cbuf));
// 正确的写法
System.out.print(new String(cbuf, 0, len));
}
} catch (IOException e){
e.printStackTrace();
} finally{
// 4.资源的关闭
if (fileReader != null) {
try {
fileReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
说明:
@Test
public void testFileWriter(){
FileWriter fileWriter = null;
try {
// 1.提供 File 类的对象,指明写出到的文件
File file = new File("hello1.txt");
// 2.提供 FileWriter 的对象,用于数据的写出
fileWriter = new FileWriter(file, false);
// 3.写出的操作
fileWriter.write("I have a dream!\n");
fileWriter.write("you need to have a dream!");
} catch (IOException e) {
e.printStackTrace();
} finally {
// 4.流资源的关闭
if (fileWriter != null) {
try {
fileWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
@Test
public void testFileReaderFileWriter() {
FileReader fileReader = null;
FileWriter fileWriter = null;
try {
// 1.创建 File 类的对象,指明读入和写出的文件
// File srcFile = new File("hello.txt");
// File destFile = new File("hello2.txt");
// 不能使用字符流来处理图片等字节数据
File srcFile = new File("爱情与友情.png");
File destFile = new File("爱情与友情1.png");
// 2.创建输入流和输出流的对象
fileReader = new FileReader(srcFile);
fileWriter = new FileWriter(destFile);
// 3.数据的读入和写出操作
char[] cbuf = new char[5];
// 记录每次读入到 cbuf 数组中的字符的个数
int len = 0;
while ((len = fileReader.read(cbuf)) != -1){
// 每次写出 len 个字符
fileWriter.write(cbuf, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 4.关闭流资源
if (fileWriter != null) {
try {
fileWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fileReader != null) {
try {
fileReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 实现对图片的复制
*/
@Test
public void testFileInputOutputStream(){
FileInputStream fileInputStream = null;
FileOutputStream fileOutputStream = null;
try {
File srcFile = new File("爱情与友情.png");
File destFile = new File("爱情与友情2.png");
fileInputStream = new FileInputStream(srcFile);
fileOutputStream = new FileOutputStream(destFile);
// 复制的过程
byte[] buffer = new byte[5];
int len = 0;
while ((len = fileInputStream.read(buffer)) != -1){
fileOutputStream.write(buffer, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
if (fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fileOutputStream != null) {
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
注意点:相对路径在 IDEA 和 Eclipse 中使用的区别?
作用:提高流的读取、写入的速度
提高读写速度的原因:内部提供了一个缓冲区,默认情况下是8kb
public class BufferedInputStream extends FilterInputStream {
private static int DEFAULT_BUFFER_SIZE = 8192;
}
处理非文本文件
/**
* 实现文件复制的方法
*/
public void copyFileWithBuffered(String srcPath, String destPath){
BufferedInputStream bufferedInputStream = null;
BufferedOutputStream bufferedOutputStream = null;
try {
// 造文件
File srcFile = new File(srcPath);
File destFile = new File(destPath);
// 2.造流
// 2.1.造节点流
FileInputStream fileInputStream = new FileInputStream(srcFile);
FileOutputStream fileOutputStream = new FileOutputStream(destFile);
// 2.2.造缓冲流
bufferedInputStream = new BufferedInputStream(fileInputStream);
bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
// 3.复制的细节
byte[] buffer = new byte[1024];
int len = 0;
while ((len = bufferedInputStream.read(buffer)) != -1){
bufferedOutputStream.write(buffer, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 4.资源关闭
// 要求:先关闭外层的流,再关闭内层的流
if (bufferedOutputStream != null) {
try {
bufferedOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bufferedInputStream != null) {
try {
bufferedInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
// 说明:在关闭外层流的同时,内层流也会自动进行关闭,对于内层流的关闭,可以省略
// fileOutputStream.close();
// fileInputStream.close();
}
}
处理文本文件
/**
* 使用 BufferedReader 和 BufferedWriter 实现文本文件的复制
*/
@Test
public void testBufferedReaderBufferedWriter(){
BufferedReader bufferedReader = null;
BufferedWriter bufferedWriter = null;
try {
// 创建文件和相应的流
bufferedReader = new BufferedReader(new FileReader(new File("dbcp.txt")));
bufferedWriter = new BufferedWriter(new FileWriter(new File("dbcp1.txt")));
// 读写操作
// 方式一,使用 char[] 数组
/*char[] cbuf = new char[1024];
int len = 0;
while ((len = bufferedReader.read(cbuf)) != -1){
bufferedWriter.write(cbuf, 0, len);
}*/
// 方式二:使用 String
String data;
while ((data = bufferedReader.readLine()) != null){
// 方法一:
// data 中不包含换行符
// bufferedWriter.write(data + "\n");
// 方法二:
bufferedWriter.write(data);
// 提供换行的操作
bufferedWriter.newLine();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bufferedWriter != null) {
try {
bufferedWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
属于字符流
InputStreamReader:将一个字节的输入流转换为字符的输入流
解码:字节、字节数组 --> 字符数组、字符串
OutputStreamWriter:将一个字符的输出流转换为字节的输出流
编码:字符数组、字符串 --> 字节、字节数组
说明:编码决定了解码的方式
提供字节流与字符流之间的转换
@Test
public void test1(){
InputStreamReader inputStreamReader = null;
try {
FileInputStream fileInputStream = new FileInputStream("dbcp.txt");
// 使用系统默认的字符集
// InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream);
// 参数2指明了字符集:具体使用哪个字符集,取决于文件 dbcp.txt 保存时使用的字符集
inputStreamReader = new InputStreamReader(fileInputStream, StandardCharsets.UTF_8);
char[] cbuf = new char[20];
int len = 0;
while ((len = inputStreamReader.read(cbuf)) != -1){
System.out.print(new String(cbuf,0, len));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (inputStreamReader != null) {
try {
inputStreamReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 综合使用 InputStreamReader 和 OutputStreamWriter
*/
@Test
public void test2(){
InputStreamReader inputStreamReader = null;
OutputStreamWriter outputStreamWriter = null;
try {
File file1 = new File("dbcp.txt");
File file2 = new File("dbcp_gbk.txt");
FileInputStream fileInputStream = new FileInputStream(file1);
FileOutputStream fileOutputStream = new FileOutputStream(file2);
inputStreamReader = new InputStreamReader(fileInputStream, StandardCharsets.UTF_8);
outputStreamWriter = new OutputStreamWriter(fileOutputStream, "gbk");
char[] cbuf = new char[20];
int len = 0;
while ((len = inputStreamReader.read(cbuf)) != -1){
outputStreamWriter.write(cbuf, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
if (inputStreamReader != null) {
try {
inputStreamReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (outputStreamWriter != null) {
try {
outputStreamWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
文件编码的方式,决定了解析时使用的字符集
客户端 / 游览器端 <—> 后台(Java, Go, Python, Node.js, PHP) <—> 数据库
要求前前后后使用的字符集都要统一:UTF-8
修改默认的输入和输出行为:System 类的 setIn(InputStream InputStream)
/ setOut(OutputStream outputStream)
方式重新指定输入和输出的流
说明:
作用:用于读取或写出基本数据类型的变量或字符串
示例代码:
/**
* 数据流:DataInputStream 和 DataOutputStream
* 1.作用:用于读取或写出基本数据类型的变量或字符串
*/
@Test
public void test3(){
DataOutputStream dataOutputStream = null;
try {
dataOutputStream = new DataOutputStream(new FileOutputStream("data.txt"));
dataOutputStream.writeUTF("刘建辰");
dataOutputStream.writeInt(23);
dataOutputStream.writeBoolean(true);
// 刷新操作,将内存中的数据写入文件
dataOutputStream.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (dataOutputStream != null) {
try {
dataOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 将文件中存储的基本数据类型变量和字符串读取到内存中
* 注意点:读取不同类型数据的顺序要以当初写入文件时,保存的数据的顺序一致
*/
@Test
public void test4(){
DataInputStream dataInputStream = null;
try {
dataInputStream = new DataInputStream(new FileInputStream("data.txt"));
System.out.println(dataInputStream.readUTF());
System.out.println(dataInputStream.readInt());
System.out.println(dataInputStream.readBoolean());
} catch (IOException e) {
e.printStackTrace();
} finally {
if (dataInputStream != null) {
try {
dataInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
###### 使用 BufferedReader 和 BufferedWriter
处理文本文件
```java
/**
* 使用 BufferedReader 和 BufferedWriter 实现文本文件的复制
*/
@Test
public void testBufferedReaderBufferedWriter(){
BufferedReader bufferedReader = null;
BufferedWriter bufferedWriter = null;
try {
// 创建文件和相应的流
bufferedReader = new BufferedReader(new FileReader(new File("dbcp.txt")));
bufferedWriter = new BufferedWriter(new FileWriter(new File("dbcp1.txt")));
// 读写操作
// 方式一,使用 char[] 数组
/*char[] cbuf = new char[1024];
int len = 0;
while ((len = bufferedReader.read(cbuf)) != -1){
bufferedWriter.write(cbuf, 0, len);
}*/
// 方式二:使用 String
String data;
while ((data = bufferedReader.readLine()) != null){
// 方法一:
// data 中不包含换行符
// bufferedWriter.write(data + "\n");
// 方法二:
bufferedWriter.write(data);
// 提供换行的操作
bufferedWriter.newLine();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bufferedWriter != null) {
try {
bufferedWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
属于字符流
InputStreamReader:将一个字节的输入流转换为字符的输入流
解码:字节、字节数组 --> 字符数组、字符串
OutputStreamWriter:将一个字符的输出流转换为字节的输出流
编码:字符数组、字符串 --> 字节、字节数组
说明:编码决定了解码的方式
提供字节流与字符流之间的转换
[外链图片转存中…(img-VVvy7P8O-1638542599462)]
@Test
public void test1(){
InputStreamReader inputStreamReader = null;
try {
FileInputStream fileInputStream = new FileInputStream("dbcp.txt");
// 使用系统默认的字符集
// InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream);
// 参数2指明了字符集:具体使用哪个字符集,取决于文件 dbcp.txt 保存时使用的字符集
inputStreamReader = new InputStreamReader(fileInputStream, StandardCharsets.UTF_8);
char[] cbuf = new char[20];
int len = 0;
while ((len = inputStreamReader.read(cbuf)) != -1){
System.out.print(new String(cbuf,0, len));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (inputStreamReader != null) {
try {
inputStreamReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 综合使用 InputStreamReader 和 OutputStreamWriter
*/
@Test
public void test2(){
InputStreamReader inputStreamReader = null;
OutputStreamWriter outputStreamWriter = null;
try {
File file1 = new File("dbcp.txt");
File file2 = new File("dbcp_gbk.txt");
FileInputStream fileInputStream = new FileInputStream(file1);
FileOutputStream fileOutputStream = new FileOutputStream(file2);
inputStreamReader = new InputStreamReader(fileInputStream, StandardCharsets.UTF_8);
outputStreamWriter = new OutputStreamWriter(fileOutputStream, "gbk");
char[] cbuf = new char[20];
int len = 0;
while ((len = inputStreamReader.read(cbuf)) != -1){
outputStreamWriter.write(cbuf, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
if (inputStreamReader != null) {
try {
inputStreamReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (outputStreamWriter != null) {
try {
outputStreamWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
文件编码的方式,决定了解析时使用的字符集
客户端 / 游览器端 <—> 后台(Java, Go, Python, Node.js, PHP) <—> 数据库
要求前前后后使用的字符集都要统一:UTF-8
修改默认的输入和输出行为:System 类的 setIn(InputStream InputStream)
/ setOut(OutputStream outputStream)
方式重新指定输入和输出的流
说明:
作用:用于读取或写出基本数据类型的变量或字符串
示例代码:
/**
* 数据流:DataInputStream 和 DataOutputStream
* 1.作用:用于读取或写出基本数据类型的变量或字符串
*/
@Test
public void test3(){
DataOutputStream dataOutputStream = null;
try {
dataOutputStream = new DataOutputStream(new FileOutputStream("data.txt"));
dataOutputStream.writeUTF("刘建辰");
dataOutputStream.writeInt(23);
dataOutputStream.writeBoolean(true);
// 刷新操作,将内存中的数据写入文件
dataOutputStream.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (dataOutputStream != null) {
try {
dataOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 将文件中存储的基本数据类型变量和字符串读取到内存中
* 注意点:读取不同类型数据的顺序要以当初写入文件时,保存的数据的顺序一致
*/
@Test
public void test4(){
DataInputStream dataInputStream = null;
try {
dataInputStream = new DataInputStream(new FileInputStream("data.txt"));
System.out.println(dataInputStream.readUTF());
System.out.println(dataInputStream.readInt());
System.out.println(dataInputStream.readBoolean());
} catch (IOException e) {
e.printStackTrace();
} finally {
if (dataInputStream != null) {
try {
dataInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
|
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/24 3:20:30- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |