JAVA学习笔记(上)
Java基础
标识符
- 所有标识符都应该以字母(A-Z或a-z),美元符($),下划线(_)开始
- 不能使用关键字作为变量名或方法名
- 标识符是大小写敏感的
数据类型
Java是强类型语言:要求变量的使用要严格符合规定,所有变量都必须先定义后使用。
Java的数据类型分为两大类
- 基本类型
- 整数类型
- byte 1字节 -128-127
- short 2字节 -32768-32767
- int 4字节 -2147483648-2147483647
- long 8字节 -9223372036854775808-9223372036854775807
- 浮点型
- float 4字节
- double 8字节
- 字符型
- char 2字节
- boolean类型 1字节
- 整数类型
- 引用类型
- 强引用
- 软引用
- 弱引用
- 虚引用
什么是字节
位(bit):是计算机内部数据存储的最小单位,11001100是一个八位二进制数
字节(byte):是计算机中数据处理的基本单位,习惯上用大写的B来表示
1B(byte,字节)= 8bit(位)
字符:是指计算机中使用的字母、数字、字和符号
1bit表示1位
1Byte表示一个字节1B=8b
1024B=1KB
1024KB=1M
1024M=1G
基本数据类型举例
public class HelloWorld {
public static void main(String[] args) {
//=============================整型==================================
//进制 二进制0b 十进制 八进制0 十六进制0x
int i = 10;
int i1 = 0b10; //2进制
int i2 = 010; //8进制 0x8^0+1x8^1=8
int i3 = 0x10; //16进制 0~9 A~F 16 0x16^0+1x16^1=16
System.out.println(i);
System.out.println(i1);
System.out.println(i2);
System.out.println(i3);
System.out.println("==============================");
//==========================浮点类型====================================
//BigDecimal 数学工具类
//float 有限 离散 舍入误差 大约 接近但不等于
//最好避免使用浮点数进行比较
float f = 0.1f;
double d = 1.0/10;
System.out.println(f==d);//false
float d1 = 2312321313f;
float d2 = d1 + 1;
System.out.println(d1==d2);//true
//==============================字符类型=============================
char c1 = 'a';
char c2 = '中';
System.out.println(c1);
System.out.println((int) c1); //强制类型转换
//所有字符本质上还是数字
//编码 Unicode 2字节 0-65536
//转义字符
//\t 相当于Tab键
//\n换行
System.out.println("Hello\tWorld");
System.out.println("Hello\nWorld");
//=============================String===================================
String sa = new String("hello world");
String sb = new String("hello world");
System.out.println(sa==sb);//false
String sc = "hello world";
String sd = "hello world";
System.out.println(sc==sd);//true
}
}
类型转换
低------------------------------》高
byte,short,char-->int-->long-->float-->double
public class TestDemo {
public static void main(String[] args) {
int i = 128;
byte b = (byte) i;//强制类型转换
double d = i; //自动转换
System.out.println(b); //内存溢出
System.out.println(d); //自动转换
//强制转换 (类型)变量名 高--低
//自动转换 低--高
/**
* 注意点:
* 1、不能对布尔值进行转换
* 2.不能把对象类型转化为不相干的类型
* 3.在把高容量转化为低容量的时候,强制转换
* 4.转换的时候可能存在内存溢出,或精度问题!
*/
System.out.println((int) 23.7); //23
System.out.println((int)-45.23f);//-45
//==========================================
char c = 'a';
int q = c+1;
System.out.println(q);// 98
System.out.println((char) q);// b
}
}
public class TestDemo {
public static void main(String[] args) {
int money = 10_0000_0000;
int years = 20;
int total = money*years;
System.out.println(total); // -1474836480 因为超过21亿,所以产生内存溢出
long total1 = money*years;
System.out.println(total1); //还是 -1474836480 因为money*years算出来还是int类型 -1474836480
//要想改变就要
long total2 = money*(long)years; //先把years转换成long型 计算出来就是long类型了
}
}
## 变量
1. 类变量 :被static修饰的变量
2. 实例变量 :不属于任何方法,直接在类中定义的变量
3. 局部变量:在方法中定义的变量
```java
public class TestDemo {
//实例变量:从属于对象,如果不进行初始化,就使用这个类型的默认值 数值类型默认值 0 或 0.0 布尔值默认是false 除了基本类型其他默认值都为null;
String name;
int age;
public static void main(String[] args) {
//局部变量,必须声明和初始化
int i = 10;
TestDemo testDemo = new TestDemo();
System.out.println(testDemo.name);
System.out.println(testDemo.age);
}
}
常量
常量:初始化后不能再被改变的值。
常量名一般使用大写字符。
final double PI = 3.14;
变量的命名规范
- 所有变量、方法名、类名:见名知意
- 类成员变量:首字母小写和驼峰原则monthSalary
- 局部变量:首字母小写和驼峰原则
- 常量:大写字母和下划线:MAX_VALUE
- 类名:首字母大写和驼峰原则:Man,GoodMan
- 方法名:首字母小写和驼峰原则:run(),ranRun()
&&:是短路且,如果&&左边的的不成立则&&右边的语句不会执行
位运算
public static void main(String[] args) {
/*
A = 0011 1100
B = 0000 1101
A&B = 0000 1100 A与B 如果都为1那么就是1 否则为0
A|B = 0011 1101 A或B 如果都为0那么就是0 否则为1
A^B = 0011 0001 A异或B 如果两个相同为 0 不相同为 1
~B = 1111 0010 取反 跟B完全取反
>> 相当于 /2
<< 相当于 *2
*/
System.out.println(2<<3); //16
}
三元运算符
X ?Y :Z
如果x==true,则结果为Y,否则为Z
Scanner对象
public static void main(String[] args) {
//创建一个扫描器对象,用于接收键盘数据
Scanner scanner = new Scanner(System.in);
System.out.println("使用next方式接收:");
//判断用户有没有输入字符串
// if (scanner.hasNext()){
String str = scanner.next();
System.out.println(str);
// }
scanner.close();//关闭资源 凡是属于IO流的类,如果不关闭会一直占用资源。
}
next()和nextLine()的区别:next是以空格为结束,而nextLine是以回车为结束
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
if (scanner.hasNextInt()){
int i = scanner.nextInt();
System.out.println(i);
}else {
System.out.println("你输入的不是整数");
}
if (scanner.hasNextFloat()){
float v = scanner.nextFloat();
System.out.println(v);
}else {
System.out.println("你输入的不是小数");
}
scanner.close();
}
顺序结构
if单选则结构
if(flag){
//如果flag==tuue那么执行if块里面的代码
//否则不执行if块里面的代码
}
if双选择结构()
if(){
}else{
}
if多选择结构(多级判断,只要有一个if里面为true那么执行完此块就停止)
if(){
}else if(){
}else if(){
}else{
}
swith多选择结构
变量类型可以为:byte,short,int,char从jdk7开始支持String,会拿grade和case后面的分支进行比较,如果相等则执行相应的代码块。如果不加break会出现case穿透
public static void main(String[] args) {
char grade = 'z';
switch (grade){
case 'A':
System.out.println("优秀");
break;
case 'B':
System.out.println("良好");
break;
case 'C':
System.out.println("及格");
break;
case 'D':
System.out.println("再接再厉");
break;
case 'E':
System.out.println("挂科");
break;
default:
System.out.println("你需要再努力");
}
}
循环结构
while循环
如果while(为true)则执行代码块
public static void main(String[] args) {
int i = 0;
int sum = 0;
while (i<100){
i++;
sum+=i;
}
System.out.println(sum);
}
doWhile循环
不管括号里是真还是假,代码至少会执行一次
public static void main(String[] args) {
int i = 0;
int sum = 0;
do {
sum = sum+i;
i++;
}while (i<=100);
System.out.println(sum);
}
for循环
for(初始化;布尔表达式;更新){}
for(; ;){}//死循环写法
执行顺序
- 最开始先初始化,
- 然后判断布尔值
- 执行代码块
- 执行更新 i 操作
- 判断布尔值
- 执行代码块
- 更新 i ……
输出99乘法表
public static void main(String[] args) {
for (int i = 1; i <= 9; i++) {
System.out.println();
for (int j = 1; j <= i; j++) {
System.out.print(j+"x"+i+"="+i*j+"\t");
}
}
}
增强for循环
JDK5才引用,主要是遍历数组和集合的
public static void main(String[] args) {
int[] numbers = {10,20,30,40,50};
//把numbers每一项的值赋值给number
for (int number : numbers) {
System.out.println(number);
}
}
break和continue
break用于跳出循环
continue:在循环中碰到就回到循环开始的地方,用于跳过某些东西。
例如可以把100以内10的倍数跳过
public static void main(String[] args) {
int i =0;
while (i<100){
i++;
if (i%10==0){
System.out.println();
continue;
}
System.out.print(i);
}
}
输出:
123456789
111213141516171819
212223242526272829
313233343536373839
414243444546474849
515253545556575859
616263646566676869
717273747576777879
818283848586878889
919293949596979899
求1-100以内的所有质数
public static void main(String[] args) {
//求100以内的质数 方法一
for (int i = 2; i <= 100; i++) {
boolean flag = true;
for (int j = 2;j<i;j++){
if (i%j==0){
flag=false;
break;
}
}
if (flag){
System.out.print(i+",");
}
}
}
结果:
2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,
使用debug
在for循环打一个断点,然后一步一步执行,可以看到每一行代码的运行效果
//打印三角形 5行
public static void main(String[] args) {
for (int i = 1; i <= 5; i++) {
for (int j = 5;j>=i;j--){
System.out.print(" ");
}
for (int j = 1;j<=i;j++){
System.out.print("*");
}
for (int j = 1;j<i;j++){
System.out.print("*");
}
System.out.println();
}
}
结果:
*
***
*****
*******
*********
方法
一个方法只做一个功能 return可以退出方法
Java只有值传递
方法的重载
规则:
- 方法名称必须相同
- 参数列表必须不同(个数不同,类型不同,参数排列顺序不相同)
- 方法的返回值可以相同也可以不相同
可变参数
- 在方法声明中,在指定参数类型后加一个省略号(...)
- 一个方法只能指定一个可变参数,它必须是方法的最后一个参数,任何普通的参数必须在它之前声明。
public static void main(String[] args) {
Demo01 demo01 = new Demo01();
demo01.test(1);
}
public void test(int...i){
System.out.println(i[0]);
}
方法递归
可以处理一些问题,比如求阶乘,但是如果基数太大,也容易出现问题
public static void main(String[] args) {
int f = Demo02.f(5);
System.out.println(f);
}
public static int f(int n){
if (n==1){
return 1;
}else {
return n*f(n-1);
}
}
数组
int[] nums ;//声明数组第一种方法
int nums[] ;//声明数组第二种方法
数组的初始化:
nums = new int[5];//给数组分配内存空间
//给数组赋值
nums[0] = 1;
nums[1] = 2;
nums[2] = 3;
nums[3] = 4;
nums[4] = 5;
//计算数组内所有元素的和 num.length 获取数组的长度
int sum = 0;
for(int i = 0;i<nums.length;i++){
sum += nums[i];
}
异常:ArrayIndexOutOfBoundsException: 8 数组下标越界异常
//静态初始化:创建+赋值
int[] a = {1,2,3,4,5,6,7,8}
//动态初始化:包含默认值初始化
int[] b = new int[10];
数组的四个基本特点
- 数组长度是确定的。数组一但被创建,它的大小就是不可以改变的。
- 数组元素必须是相同类型,不允许出现混合类型
- 数组元素可以是任何数据类型,包括基本数据类型和引用数据类型
- 数组变量属于引用类型,数组也可以看成对象,数组的每个元素相当于改对象的成员变量。
- 数组本身就是对象,Java中对象是在堆中的,因此数组无论保存原始类型还是保存其他对象类型,数组对象本身是在堆中的
数组的反转
public static int[] reverse(int[] arrays){
//定义一个和要反转数组相同长度的数组
int[] result = new int[arrays.length];
for (int i = 0,j=result.length-1;i<arrays.length;i++,j--){
result[j] = arrays[i];
}
return result;
}
二维数组
public static void main(String[] args) {
/*
1,2 a[0]
3,4 a[1]
5,6 a[2]
*/
int[][] a = {{1,2},{3,4},{5,6}};
System.out.println(a[0][0]); // 1
System.out.println(a[0][1]);// 2
}
Arrays工具类
- 该类包含用于操作数组的各种方法(如排序和搜索)。
常用方法
public static void main(String[] args) {
int[] a = {1,33,5,88,95,2,789,54};
System.out.println(a); // 如果直接输出数组则输出的是数组的地址[I@1540e19d
//打印数组元素
System.out.println(Arrays.toString(a)); //[1, 33, 5, 88, 95, 2, 789, 54]
Arrays.sort(a);//数组进行排序 升序
System.out.println(Arrays.toString(a));// [1, 2, 5, 33, 54, 88, 95, 789]
}
冒泡排序
冒泡排序是最出名的排序算法之一,总共有八大排序!!
冒泡排序的时间复杂度是O(n^2)
public static void main(String[] args) {
int[] a = {1,55,3,77,213,43,8,342};
int[] sort = Demo04.sort(a);
System.out.println(Arrays.toString(sort));
}
//冒泡排序
//1.比较数组中两个相邻的元素,如果第一个数比第二个数大,我们就交换他们的位置
//2.每次比较都会产生一个最大或最小的数字。
//3、下一轮则可以少一次排序
//4.依次循环,直到结束!
public static int[] sort(int[] array){
//临时变量
int temp = 0;
//i<array.length-1的意思是,一共比较几轮,因为如果数组中有4个数的话,每一轮都会把一个最大或最小的数排到最顶部,当比完3轮之后,第4个数也就排好了,所以要-1
for (int i = 0; i < array.length-1 ; i++) {
//-i的意思是当每一轮比较的时候,因为上一轮比较已经确定一个最小或最大数了,每确定一个就少比一次,第一轮确定一个,第二轮确定两个,所以第i轮就确定i个,所以要-i
for (int j = 0; j < array.length-1-i; j++) {
// if (array[j+1]<array[j]){ 升序
if (array[j+1]>array[j]){ //降序
temp = array[j+1];
array[j+1] = array[j];
array[j] = temp;
}
}
}
return array;
}
稀疏数组
==和equals的区别
面试题:自己写了一个实体类,new两个属性值相同的对象,用equals去比较,请问结果是什么?
结果是false,因为这个对象没有重写equals()方法,还是用的Object的,而Object的equals方法比的是地址值。所以为false。
/*
面试题:==和equals的区别:
==:运算符
1、可以使用在基本数据类型变量和引用数据类型变量中
2、如果比较的是基本数据类型变量:比较两个变量保存的数值是否相等。(不一定要类型形同,例如自动类型提升)
如果比较的是两个引用类型变量,比较两个对象的地址值是否相同
equals()方法的使用:
1、是一个方法,而非运算符
2、只能试用于引用数据类型
3、Object类中的equals()的定义:
public boolen equals(object obj){
return (this == obj);
}
说明:Object中的equals()和==作用是相同的
4、像String、Date、File、包装类等都重写了object类中equals()。重写以后比较的不是两个对象的地址值是否相同,而是比较两个对象的实体内容。
5、通常情况下,我们自定义的类如果用equals()的话,也通常是比较两个对象的实体内容是否相等,那么我们就需要重写Object类中的equals()方法
*/
@Test
public void test1(){
int i = 10;
int j = 10;
double b = 10.0;
System.out.println(i == j);//true
System.out.println(i == b);//true 因为有自动类型提升
char a = 10;
System.out.println(i==a); //true
char c1 = 'A';
char c2 = 65;
System.out.println(c1==c2);//true
}
hashcode和equals()的区别?
因为重写的equal()里一般比较的比较全面比较复杂,这样效率就比较低,而利用hashCode()进行对比,则只要生成一个hash值进行比较就可以了,效率很高,那么hashCode()既然效率这么高为什么还要equal()呢?
因为hashCode()并不是完全可靠,有时候不同的对象他们生成的hashcode也会一样(生成hash值得公式可能存在的问题),所以hashCode()只能说是大部分时候可靠,并不是绝对可靠,所以我们可以得出:
1.equal()相等的两个对象他们的hashCode()肯定相等,也就是用equal()对比是绝对可靠的。
2.hashCode()相等的两个对象他们的equal()不一定相等,也就是hashCode()不是绝对可靠的。
length和length()区别?
length()是求String字符串对象中字符的个数,而length是求字符串数组中有多少个字符串。
面向对象编程(OOP)
面向对象的本质:以类的方式组织代码,以对象组织(封装)数据
三大特性:封装,继承,多态
public void a(){ };
public static void b(){
a();
};
static是类存在的时候这个方法已经存在,所以一个存在的调用不了不存在的
Java中只有值传递
值传递机制:
- 如果参数是基本数据类型,此时实参赋给形参的是实参真实存储的数据值。
- 如果参数是引用类型的数据,此时实参赋给形参的是实参存储数据的地址。
构造器:
特点:
- 和类名相同
- 没有返回值
作用:
- 使用new关键字本质上是在调用构造器
- 用来初始化值
注意点:
- 定义了有参构造以后,如果想用无参构造,显示定义一个无参构造
封装性:
概念:一个电视机,你不知道里面是有什么东西在运转,但是你可以通过遥控器或者电视表面的按钮来调整电视的分辨率之类的,所以封装性就是指将用户不需要了解的东西封装起来,只提供服务的接口
封装就是把普通的对象进行封装,对象的属性设为私有的,对外提供get和set方法,其他类只能通过get和set对对象属性值进行操作
属性是私有的,通过get/set方法来操作数据
作用:
- 提高程序的安全性,保护数据
- 隐藏代码的实现细节
- 统一接口
- 系统可维护性增加了
继承:
概念:继承是发生在两个类之间的,类A继承类B,那么A中就有类B中的所有属性和方法(public,protect),并且类A也可以扩展类B不具备的属性和方法。
在Java中所有的类,都直接或间接继承Object类
super关键字
- super调用父类的构造方法,必须在构造方法的第一个
- super和this不能同时调用构造方法
VS this
代表对象不同:
this:本身调用者对象
super:代表父类对象的引用
前提
this:没有继承也可以使用
super:只能在继承条件下才能使用
构造方法
this():本类的构造
super():父类的构造
方法的重写
前提:需要有继承关系,子类重写父类的方法
- 方法名,参数列表,返回类型必须相同
- 参数列表的形参名称可以不同
- 修饰符:范围可以扩大但不能缩小 public>Protected>default>private
- 抛出的异常:范围可以被缩小,可以被扩大。
- 非静态方法才能重写,静态方法不能够被重写
- 不能重写被final和private关键字修饰的类
重写,子类的方法和父类必须要一直,方法体不同!
为什么需要重写:
- 父类的功能子类不一定需要。
- 复用性和扩展性
多态
概念:用最简单的一句话就是:父类型的引用指向子类型的对象。用一句比较通俗的话:同一操作作用于不同的对象,可以产生不同的效果。这就是多态。
作用:1、提高了代码的维护性bai(继承保证)
2、提高了代码的扩du展性(由多态保证)
注意事项:
- 多态是方法的多态,属性没有多态
- 父类和子类,有联系的才能类型转换
- 子类转父类不许要强制转换,但是可能丢失一些方法
- 父类转子类,必须强制类型转换,转换后可以调用子类的方法
存在条件:
- 继承关系
- 方法需要重写
- 父类引用指向子类对象!
表现形式:
- 方法重载
- 方法重写
- 抽象类
- 接口
Static关键字
静态代码块,匿名代码快和构造方法的顺序
第一次执行:1.静态 2.匿名 3.构造方法
第二次: 1.匿名 2.构造方法
静态代码块只执行一次,是在类加载的时候,所以排第一个,匿名代码块和构造方法都是创建对象的时候执行,不过匿名要快于构造
特点
- static修饰的成员变量和方法,从属于类
- 普通变量和方法从属于对象
- 静态方法不能调用非静态成员,编译会报错
- 静态变量被所有的对象所共享,在内存中只有一个副本
static变量和普通成员变量的区别:
- 区别1:所属不同。static变量属于类,不单属于任何对象;普通成员变量属于某个对象
- 区别2:存储区域不同。static变量位于方法区;普通成员变量位于堆区。
- 区别3:生命周期不同。static变量生命周期与类的生命周期相同;普通成员变量和其所属的对象的生命周期相同。
- 区别4:在对象序列化时(Serializable),static变量会被排除在外(因为static变量是属于类的,不属于对象)
抽象类
特点
- 抽象方法和抽象类使用 abstract 修饰
- 只要有抽象方法的类,必须是抽象类
- 抽象类中不一定有抽象方法,也可以有非抽象方法
- 抽象类的子类不重写所有抽象方法,那么子类还是一个抽象类
- 抽象类有构造方法,目的是让子类来调用构造方法初始化
- 抽象类只有方法的声明没有方法体
- 类之间只能够单继承
面试题:
abstract 不能和哪些关键字共存?
abstract和static
被abstract修饰的方法没有方法体,如果用类名调用一个没有方法体的方法必然会出错。
abstract和final
被abstract修饰的方法强制子类重写
被final修饰的不让子类重写,所以他俩是矛盾
abstract和private
被abstract修饰的是为了让子类看到并强制重写
被private不让子类访问,所以他俩是矛盾的
接口
特点:
- 接口中的方法都是 public abstruct的
- 属性都是 public static final的
- 接口不能被实例化,接口中没有构造方法
- implements 可以多实现
- 一个类如果实现一个接口,必须实现他的所有方法
接口和抽象类的区别:
- 抽象类要被子类继承,接口要被类实现
- 接口只能做方法声明,抽象类中可以作方法声明,也可以做方法实现
- 接口里定义的变量只能是公共的静态的常量,抽象类中的变量是普通变量
- 抽象类可以有具体的方法和属性,接口只能有抽象方法和不可变常量
- 接口可以多继承接口,类只能单继承
异常机制
/**
* java.lang.Throwable
* |----java.lang.Error:一般不编写针对的代码进行处理
* |----java.lang.Exception:可以进行异常的处理
* |----编译时异常(checked)
* |----IOException
* |----FileNotFountException
* |----ClassNotFoundException
* |----运行时异常(unchecked)
* |----NullPointerException 空指针异常
* |----ArrayIndexOutOfBoundsException 数组下标越界异常
* |----ClassCastException 类转型异常
* |----NumberFormatException 数据类型转换
* |----InputMismatchException 输入类型不一致
* |----ArithmeticException 算术异常
*
* 面试题:常见的异常有哪些?举例说明
如果try中有return,此时还有findally,那么先做finally再return
对于编译时异常用try-catch处理,而运行时异常,运行时就会出错,再去找到改正就可以了,不需要catch
1.throws+可能出现的异常:写在方法的声明处,一旦方法体执行时出现异常,仍然会在异常出代码生成一个异常对象,此对象满足throws后的异常类型时,就会被抛出。异常后续的代码不再执行!!
2.try-catch-finally:真正的吧异常给处理掉了
throws的方式只是将异常抛给了方法的调用者。并没有真正的处理异常。
开发中如何选择使用try-catch-finally 还是throws?
1.如果父类中被重写的方法没有throws方式处理异常,则子类重写的方法,也不能throws意味着,子类重写的方法有异常,必须使用try-catch-finally来处理
2.执行的方法a中,先后有调用了另外的几个方法,这几个方法是递进关系执行的。我们建议这几个方法使用throws进行处理。而执行方法a可以考虑使用try-catch-finally进行处理。
关于异常对象的产生:①系统自动生成的异常对象
②手动的生成一个异常对象,并抛出throw
*/
Error:Java虚拟机无法解决的严重问题。如:JVM系统内部错误,资源耗尽等严重情况。比如StackOverflowError栈溢出和OOM堆溢出
一般不编写针对性的代码进行处理。
Exception:
throw和throws的区别
throw语句用在方法体内,表示抛出异常,由方法体内的语句处理。
throws语句用在方法声明后面,表示抛出异常,由该方法的调用者来处理。
throws主要是声明这个方法会抛出这种类型的异常,使它的调用者知道要捕获这个异常。
throw是当程序出现某种逻辑错误时由程序员主动抛出某种特定类型的异常是,具体向外抛异常的动作,所以它是抛出一个异常实例。
手动throw出一个异常
/**
一般的异常,像空指针异常,都是系统自动抛出了一个异常对象,我们也可以手动生成一个异常对象,并抛出
*/
public class shoudongpaochu {
public static void main(String[] args) {
Student student = new Student();
student.regist(-100);
}
}
class Student{
private int id;
public void regist(int id){
if (id>0){
this.id=id;
}else {
try {
throw new Exception("您输入的格式有误!");
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
自定义异常
步骤:
- 创建自定义异常类
- 在方法中通过throw关键字抛出异常对象
- 如果在抛出异常的方法中处理异常,可以使用try-catch语句进行捕获和处理,否则在方法的声明处通过throws关键字指明要抛出给方法调用者的异常
- 在出现异常方法的调用者中捕获并进行异常处理
public class MyException extends Exception{
//自定义异常
public MyException(String msg) {
super(msg);
}
//抛出异常
public void a() throws MyException {
throw new MyException("你好!");
}
//捕获并处理异常
public static void main(String[] args) {
try {
new MyException().a();
} catch (MyException e) {
System.out.println(e.getMessage());
}
}
}
Java中的常用类
String
String字符串,使用一对""引起来表示
1.String声明为final,不可被继承
2.String实现了Seriallizable接口:表示字符串是支持序列化的。
实现了Comparable接口:表示string可以比较大小
3.String内部定义了final char[] value用于存储字符串数据
4.String:代表不可变的字符序列。简称:不可变性。
体现:1.当对字符串重新赋值时,需要重新制定内存区域赋值,不能使用原有的value进行赋值。
2.当对现有的字符串进行操作时,也需要重新制定内存区域进行赋值,不能在原有的value上进行赋值
3.当调用String的replace()修改指定的字符或字符串时,也需要重新制定内存区域进行赋值,不能在原有的value上进行赋值。
5.通过字面量的方式(区别于new方式)给一个字符串赋值,此时字符串声明在字符串常量池中。
6.字符串常量池中是不会存储相同内容的字符串的。
String的实例化方式:
方式一:通过字面量定义的方式
方式二:通过new+构造器的方式
面试题
String s = new String(“abc”)创建方式,在内存中创建了几个对象?
两个:1. 是堆空间中new的
2. 是char[]对应的常量池中的数据“abc”
结论:
- 常量与常量的的拼接结果在常量池,且常量池中不会存在相同内容的常量。
- 只要其中有一个是变量,结果就在堆中
- 如果拼接的结果调用intern()方法,返回值就在常量池中。
- 如果被final修饰的变量和字面量进行拼接,结果也是在常量池中
String与基本数据类型、包装类之间的转换
String-->基本数据类型、包装类调用包装类的静态方法:parseXxx(str)
基本数据类型、包装类-->String:调用String重载的valueOf(xxx)
String与char[]之间的转换
String--->char[]:调用String的toCharArray()
char[]--->String:调用String的构造器
String和byte[]的转换
String--->byte[]:调用String的getBytes()方法 括号中可以使用特定的字符值比如gbk或utf-8
byte[]--->String:调用String的构造器 new String( byte[],编码集)
UTF-8中一个汉字是3字节
GBK中是2个字节
编码:字符串--->字节(看得懂--->看不懂)
解码:字节--->字符串(看不懂得二进制--->看的懂得)
用什么编码集编的的就用什么解码,不然就会出现乱码
StringBuffer,StringBuilder
面试题
StringBuffer,StringBuilder,String的异同 ?
String:不可变的字符序列;底层char[]数组进行存储
StringBuffer:可变的字符序列;线程安全,效率低。底层char[]数组进行存储
StringBuilder:可变的字符序列;线程不安全,效率高。底层char[]数组进行存储
源码分析:
String str = new String();//char[] value = new char[]
String str1 = new String("abc); //char[] value = new cahr[] {'a','b','c'};
StringBuffer sb1 = new StringBuufer() //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.输出sb2的length 返回的是数组内元素的个数
//问题2扩容问题:如果要添加的数据底层素组盛不下了,那就要扩容底层数组。
默认情况下,扩容为原来容量的2倍+2,同时将原有数组中的元素复制到新的数组中去
三者的效率是:StringBuilder>StringBuffer>String
时间类
System类的currentTimeMillis方法
@Test
public void a(){
//返回当前时间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差。
//成为时间戳
long l = System.currentTimeMillis();
System.out.println(l);
}
结果:1594882285191
java.util.Date类和java.sql.Date类
/*
java.util.Date类
1.两个构造器:
>构造器一:Date()
>构造器二:创建指定毫秒数的Date对象
2.两个方法:
>toString():显示当前的年、月、日、时、分、秒
>getTime():获取当前date对应的毫秒数
3.java.sql.Date 对用数据库中的日期类型的变量
>如何实例化
>sql.date--->util.date
*/
@Test
public void test01(){
//构造器一:Date()
Date date = new Date();
System.out.println(date.getTime());//1594882763377
System.out.println(date.toString());//Thu Jul 16 14:59:23 CST 2020
//构造器二:创建指定毫秒数的Date对象
Date date1 = new Date(1594882763377L);
System.out.println(date1.toString());//Thu Jul 16 14:59:23 CST 2020
}
@Test
public void test2(){
//创建 java.sql.Date date对象
java.sql.Date date = new java.sql.Date(1594882763377L);
System.out.println(date); //2020-07-16
//将util.data转化为sql.data
Date date1 = new Date();
java.sql.Date date2 = new java.sql.Date(date1.getTime());
System.out.println(date2);//2020-07-16
}
SimpleDateFormat类
/*
SimpleDateFormat的使用:SimpleDateFormat对日期Date类的格式化和解析
1.两个操作:
1)格式化:日期--->字符串
2)解析:格式化逆过程,字符串---> 日期
2.SimpleDateFormat的实例化
*/
@Test
public void test() throws ParseException {
//实例化SimpleDateFormat
SimpleDateFormat sdf = new SimpleDateFormat();
//格式化:日期--->字符串
Date date = new Date();
String format = sdf.format(date);
System.out.println(format);// 20-7-16 下午3:20
//解析 字符串--->日期
String str = "20-7-16 下午3:20";
Date parse = sdf.parse(str);
System.out.println(parse); // Thu Jul 16 15:20:00 CST 2020
//格式化指定格式
SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
String format1 = sdf1.format(date);
System.out.println(format1); //2020-07-16 03:25:57
//解析(只认sdf1格式化格式的字符串)
Date parse1 = sdf1.parse("2020-07-16 03:25:57");
System.out.println(parse1);//Thu Jul 16 03:25:57 CST 2020
}
练习
Calender类
/*
Calender日历类(抽象类)的使用 注:我今天是7月16号
*/
@Test
public void test1(){
//1.实例化调用静态方法
Calendar calendar = Calendar.getInstance();
//常用方法
//get 获得一些属性,比如今天是xx星期、xx月、xx年的第几天
int i = calendar.get(Calendar.DAY_OF_MONTH); //今天是这一月的第几天
int i1 = calendar.get(Calendar.DAY_OF_YEAR); //今天是这一月的第几天
System.out.println(i); //16
System.out.println(i1); //198
//set 可以手动设置是多少天
calendar.set(Calendar.DAY_OF_MONTH,22);
int i3 = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(i3); //22
//add 在原有的基础上加上3天
calendar.add(Calendar.DAY_OF_MONTH,3);
int i4 = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(i4); //25
//getTime 日历类对象-->java.util.Date对象
Date time = calendar.getTime();
System.out.println(time); //Sat Jul 25 15:47:02 CST 2020
//setTime java.util.Date-->日历类对象
Date d = new Date();
calendar.setTime(d);
int i5 = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(i5);//16
}
注意Calender 有偏移量
以上api都是jdk8之前的
Jdk8中新日期时间API
/*
jdk 8中日期时间API测试
LocalDate、LocalTime、LocalDateTime 的使用 注:我今天是7月16号
*/
@Test
public void test1(){
//实例化 now(获取当前的日期,时间,日期时间)
LocalDate localDate = LocalDate.now();
LocalTime localTime = LocalTime.now();
LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(localDate); //2020-07-16
System.out.println(localTime); //16:04:54.827
System.out.println(localDateTime); //2020-07-16T16:04:54.827
//of() 设置指定的年月日,时分秒
LocalDateTime localDateTime1 = LocalDateTime.of(2020, 10, 6, 13, 23, 43);
System.out.println(localDateTime1); //2020-10-06T13:23:43
//getXxx()
System.out.println(localDateTime.getDayOfMonth()); //16 今天是本月第几天
System.out.println(localDateTime.getDayOfWeek()); //THURSDAY 今天是星期几
System.out.println(localDateTime.getMonth()); //JULY 本月是几月
System.out.println(localDateTime.getMonthValue()); //7 本月是几月转化为阿拉伯数字
}
Java中的比较器
自然排序Comparable的CompareTo(obj)方法
/*
一、说明:Java中的对象,正常情况下,只能进行比较:== 或 != .不能使用 > 或 <
但是在开发的场景中,我们需要比较对象中的大小。
如何实现? 实现两个接口中的任何一个 Comparable 或 Comparator
二、Comparable接口的使用 :自然排序
1、像String、包装类等实现了Comparable接口,重写了compaeTo方法,
2、重写CompareTo(obj)的规则:
如果当前对象this大于形参obj,则返回正整数,小于返回负整数,等于返回0
3、对于自定义类来说,如果需要排序,我们可以让自定义类实现Comparable接口,重写compareTo(obj)方法
在compareTo(obj)方法中知名如何排序
*/
测试:
@Test
public void test(){
Goods[] arr = new Goods[5];
arr[0] = new Goods("lianxiangMouse",34.0);
arr[1] = new Goods("dellMouse",43.0);
arr[2] = new Goods("xiaomiMouse",12.0);
arr[3] = new Goods("huaweiMouse",65.0);
arr[4] = new Goods("microsoftMouse",43.0);
Arrays.sort(arr);
System.out.println(Arrays.toString(arr));
}
输出:[Goods{name='xiaomiMouse', price=12.0}, Goods{name='lianxiangMouse', price=34.0}, Goods{name='dellMouse', price=43.0}, Goods{name='microsoftMouse', price=43.0}, Goods{name='huaweiMouse', price=65.0}]
Goods实体类
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 +
'}';
}
//指明商品比较大小的方式:先按照价格从低到高,再按照产品名称从高到低。
@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 0;
return -this.name.compareTo(goods.name);
}
//方式二
// return Double.compare(this.price,goods.price);
}
throw new RuntimeException("传入的数据类型不一致!");
}
}
定制排序Comparator的(实体类和上面一样)
@Test
public void test2(){
Goods[] arr = new Goods[5];
arr[0] = new Goods("lianxiangMouse",34.0);
arr[1] = new Goods("dellMouse",43.0);
arr[2] = new Goods("xiaomiMouse",12.0);
arr[3] = new Goods("huaweiMouse",65.0);
arr[4] = new Goods("microsoftMouse",43.0);
Arrays.sort(arr, new Comparator<Goods>() {
//指明商品比较大小的方式:再按照产品名称从低到高。,先按照价格从高到低,
@Override
public int compare(Goods o1, Goods o2) {
if (o1.getName().equals(o2.getName())){
return Double.compare(o1.getPrice(),o2.getPrice());
}else {
return o1.getName().compareTo(o2.getName());
}
}
});
System.out.println(Arrays.toString(arr));
}
Comparable和Comparator接口的区别是什么?
Comparable接口的方式一旦确定,保证Comparable接口实现类的对象在任何位置,都可以比较大小
Comparator接口属于临时性的比。
集合
数组在存储多个数据方面的缺点:
>一但初始化以后,长度就不可以改变
>数组中提供的方法非常有限,对于添加,删除,插入数据等操作,非常不便,同时效率不高
>数组的存储特点:有序、可重复。对于无序不可重复的需求。不能满足。
List集合
一、List结合介绍
|----Collection接口:单列集合。用来存储一个一个对象
|----List接口:存储有序、可重复的数据 “动态数组”
|---ArrayList:作为List接口的主要实现类,线程不安全的,效率高 底层使用object[] elementData数组存储
|---LinkedList:对于频繁的插入、删除操作,使用此类比ArrayList高 底层使用的双项链表
|---Vector:作为List接口的古老实现类,线程安全的,效率低 底层使用object[] elementData数组存储数组存储
面试题:ArrayList、LinkedList、Vector三者的异同?
同:三个类都实现了List接口,存储数据的特点都相同
不同:见上
1. ArrayList的源码分析:jdk7 的情况下:
ArrayList arr = new ArrayList() //底层创建了长度是10的Object[] 数组elementData
list.add(11)//如果此次添加导致底层elementData数组容量不够,则扩容。
默认情况下,扩容为原来的1.5倍,同时需要将原有的数组复制到新的数组中。
结论:建议开发过程中使用带参的构造器:ArrayList list = new ArrayList(int capacity)
2. ArrayList的源码分析:jdk8 的情况下:
ArrayList arr = new ArrayList() ;//底层object[] elementData初始化为{},并没有创建长度为10的数组
list.add(123) //第一次调用add()时,底层才创建了长度为10的数组,并将数据123添加到elementData数组中
后续的添加和扩容操作与jdk7一样。
小结:jdk7中的创建类似于饿汉式,而8中的类似懒汉式,延迟了数组的创建,节省了内存。
3.LinkList的源码分析:
LinkedList list = new LinkedList();//内部声明了Node类型的first和last属性,默认值为null
list.add(123);//将123封装到Node中,创建了Node对象
4.Vector的源码分析:在jdk7和jdk8中,通过Vector()构造器创建对象时,底层都创建了长度为10的数组
在扩容方面,默认扩容大小为原来的2倍
List常用方法:增:add(Object obj)
删:remove(int index)/remove(Object obj)
改:set(int index,Object ele)
查:get(int index)
插:add(int index,Object ele)
长度:size()
遍历:①Iterator迭代器 ②增强for循环 ③普通的循环
二、Collection接口种方法的使用:
add() 向集合中添加元素
size() 查看集合的大小
addAll() 将一个集合全部添加到另一个集合中去
isEmpty() 判断集合元素是否为空
clear() 将集合元素清空
contains(obj) 集合中是否包含该obj 如果用contaions()判断两个对象的值相同,要求obj所在类要重写equals(),contains()方法调用本类的equals()方法进行比较
containsAll(coll) 判读coll集合中的元素是否都在该集合中
remove() 移除集合中的某个元素,如果移除相同内容的实体类,也需要重写equals()方法
removeAll(coll) 移出c本集合中oll集合中的所有元素 也需要重写equals()方法
retainAll() 求两个集合的交集 并保留他们一样的
toArray() 集合--->数组
Arrays.asList 数组--->集合
iterator() 迭代器
iterator迭代器的使用
@Test
public void test1(){
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(new String("Tome"));
coll.add(false);
//每次都会生成一个新的iterator实例
Iterator iterator = coll.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
调用rmove方法要在next()以后
List的面试题
public class TextDemo {
@Test
public void test1(){
List list = new ArrayList();
list.add(1);
list.add(2);
list.add(3);
}
private void updateList(List list){
list.remove(2);//删除第二个元素
list.remove(new Integer(2));//删除2这个元素
}
}
Set集合
|----Collection接口:单列集合。用来存储一个一个对象
|----Set接口:存储无序、不可重复的数据 --->
|---HashSet:作为Set接口的主要实现类;是线程不安全的;可以存储null值
|---LinkHashSet:作为HashSet的子类,遍历其内部数据可以按照添加的顺序去遍历
对于频繁的遍历操作,LinkedHashSet效率高于HashSet
|---TreeSet:可以按照添加对象指定的属性,进行排序。 底层是红黑树
1.向TreeSet中添加的数据,要求是相同类的对象。
2.两种排序方式:自然排序 和 定制排序
3.自然排序中,比较两个对象是否相同的标准为:compareTo()返回0,不再是equals().
4.定制排序中,比较两个对象是否相同的标准为:compare()返回0,不再是equals().
一、Set:存储无序不可重复的数据
//以HashSet进行说明:
1.无序性:不等于随机性。存储的数据在底层数据中并非按照数组索引的顺序添加,而是根据数据的哈希值确定的。
2.不可重复性:保证添加的元素按照equals()判断时,不能返回true,即相同的元素只能添加一个
二、添加元素的过程:以HashSet为例:
我们向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值,
此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置(即:索引位置),判断
数组此位置上是否已经有元素,
如果此位置上没有元素,则元素a添加成功。--->情况1
如果此位置上有其他元素b(或以链表形式存在的多个元素),则比较元素a和元素b的哈希值:
如果哈希值不相同,则元素a添加成功。--->情况2
如果哈希值相同,进而需要调用元素a所在类的equals()方法:
equals()方法返回true,元素a添加失败
equals()方法返回false,则元素a添加成功。 --->情况3
对于添加成功的情况2和请款3而言:元素a与已经存在指定索引位置上数据以链表的方式存储。
jdk7:元素a放到数组中,指向原来的元素
jdk8:原来的元素在数组中,指向元素a
总结:七上八下
HashSet底层:数组+链表的结构
三、LinkedHashSet的使用:作为HashSet的子类,在添加数据的同时,每个数据还维护了两个引用,记录此数据前一个数据和后一个数据。
//优点:对于频繁的遍历操作,LinkedHashSet效率高于HashSet
要求:向Set中添加数据,其所在类一定要重写hashCode()和equals()
重写的hashCode和equals()尽可能保持一致性。
重写两个方法的小技巧:对象中用作equals()方法比较的Field,都应该用来计算hashCode值。
Set经典面试题
@Test
public void test1(){
HashSet set = new HashSet();
Person p1 = new Person(1001,"AA");
Person p2 = new Person(1002,"BB");
set.add(p1);
set.add(p2);
System.out.println(set); //[Person{id=1002, name='BB'}, Person{id=1001, name='AA'}]
p1.setName("CC");
set.remove(p1);
System.out.println(set);//[Person{id=1002, name='BB'}, Person{id=1001, name='CC'}]
set.add(new Person(1001,"CC"));
System.out.println(set);//[Person{id=1002, name='BB'}, Person{id=1001, name='CC'}, Person{id=1001, name='CC'}]
set.add(new Person(1001,"AA"));
System.out.println(set);//[Person{id=1002, name='BB'}, Person{id=1001, name='CC'}, Person{id=1001, name='CC'}, Person{id=1001, name='AA'}]
}
Map集合
一、Map的原理
|----Map接口:双列集合,用来存储一对(key-value)数据
|----HashMap:作为Map的主要实现类;线程不安全,效率高;可以存储null的key和value
|----LinkedHashMap:保证在遍历map元素时,可以按照添加的顺序实现遍历
原因:在原有的HashMap底层节后上,添加了一对指针,指向前一个和后一个元素。
对于频繁的遍历操作,此类执行效率高于HashMap。
|----TreeMap:保证按照添加的key-value进行排序,实现排序遍历,此时考虑key的自然排序和定制排序
底层使用红黑树
|----Hashtable:古老的实现类;线程安全,效率低;不可以存储null的key和value
|----Properties:常用来处理配置文件。key和value都是String类型
HashMap的底层:数组+链表(jdk7及之前)
数组+链表+红黑树(jdk8)
面试题:
1、HashMap的底层实现原理?以jdk7为例说明:
HashMap map = new HashMap():
在实例化以后,底层创建了一个长度为16的一维数组,Entey[] table。
。。。。可能已经执行了多次put。。。。
map.put(key1,value1):
首先调用key1所在类的hashcode()计算key1的哈希值,此哈希值经过某种计算以后,得到在Entry数组中的存放位置
如果此位置上的数据为空,此时的key1-value1添加成功。--->情况1
如果此位置上的数据不为空,(意味着此位置上存在一个或多个数据(以链表的形式存在)),比较key1和已经存在的一个或多个数据
的哈希值:
如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1添加成功--->情况2
如果key1的哈希值和已经存在的数据(key2-value2)的哈希值相同,继续比较:调用key1所在类的equals()方法,比较:
如果equals()返回false:此时key1-value1添加成功--->情况3
如果equals()true:使用value1替换value2。
补充:关于情况2和情况3:此时key1-value1和原来的数据以链表的方式存储。
在不断地添加过程中,会涉及到扩容的问题,默认的扩容方式:扩容为原来容量的2倍,并将原有的数据复制过来。
jdk8相较于jdk7在底层原理方面的不同:
1、new HashMap():底层没有创建一个长度为16的数组
2、jdk8底层的数组是:Node[],而非Entry[]
3、首次调用put()方法时,底层创建长度为16的数组
4、jdk7的底层结构只有:数组+链表。 jdk8中底层结构:数组+链表+红黑树。 扩容临界值:16*0.75=12 0.75是HashMap的默认加载因子
当数组的某一个索引位置上的元素以链表形式存在的数据个数 >8 且当前数组的长度 >64 ,
此时此索引位置上的数据改为红黑树存储。
2、HashMap 和 Hashtable的异同?
3、CurrentHashMap 与 Hashtable的异同?
二、Map结构的理解:
Map中的key:无序的、不可重复,使用Set存储的所有key --->key所在的类要重写equals()和hashCode() (以hashmap为例)
Map中的value:无序,可重复的
一个键值对:key-value构成了一个Entry对象
Map中的entry:无序的、不可重复的
Map的常用方法:
添加:put(Object key,Object value)
删除:remove(Object key)
修改:put(Object key,Object value)
查询:get(Object key)
长度:size()
遍历:keySet()/values()/entrySet()
Collections工具类
Collections:是一个操作:Set。List,Map等集合的工具类
Collections类中提供了多个synchronizedXxx()方法,
改方法可使用将指定集合包装成线程同步的集合,
从而可以解决多线程并发访问集合时的线程安全问题
面试题:Collections和Collection的区别?
Collection是存储单列数据的的一个集合接口,Collections是操作Collection的工具类
泛型
泛型的使用
1、jdk 5.0新增特性
2、在集合中使用泛型
总结:
①集合接口或集合类在jdk5.0时都修改为带泛型的结构。
②在实例化集合类时,可以指明具体的泛型类型
③注意点:泛型的类型必须是类,不能是基本数据类型。需要用到基本数据类型的位置,拿包装类来替换
④如果实例化时,没有指明泛型的类型,默认类型为Object类型
3、如何自定义泛型结构:泛型类、泛型接口;泛型方法
1)关于自定义泛型类,泛型接口
如果子类在继承带泛型的父类时,指明了泛型类型。则实例化子类对象时,不需要指明泛型。
public class SuuubOrder extends Order<Integer>{
public class SubOrder<T> extends Order<T> {
2)泛型方法:在方法中出现了泛型的结构,泛型参数与类的泛型参数没有任何关系
换句话说:泛型方法所属的类,是不是泛型类都没有关系
泛型方法,可以声明为静态的。原因:泛型参数是在调用的时候确定的,而不是在实例化的时候确定的。
3)通配符的使用
通配符:?
类A是类B的父类,G<A>和G<B>是没有关系的,二者的共同父类是:G<?>
注意:1、静态方法不能有泛型
2、异常类不能声明为泛型类
3、泛型在new数组的时候要这样:T[] t = (T[]) new Object[10];
4、类A是类B的父类,G<A>和G<B>二者不具备父子关系,二者是并列关系。
5、类A是类B的父类,A<G>是B<G>的父类
泛型方法:
public <E> List<E> copy(E[] arr){
ArrayList<E> list = new ArrayList<>();
for (E e : arr) {
list.add(e);
}
return list;
}
File类
File类的使用
1、File类的一个对象,代表一个文件或一个文件目录(俗称:文件夹)
2、File类声明在java.io包下
3、File类中涉及到关于文件或文件目录的创建、删除、重命名、修改时间、大小等方法,
并未涉及到写入或读取文件内容的操作。如果需要读取或写入文件的内容,必须使用IO流来完成。
4、后续File类的对象,常会作为参数传递到流的构造器中,指明读取或写入的"终点"
1、如何创建File类的实例
File(String filePath)
File(String parentPath,String childPath)
File(File parentFile,String childPath)
2、
相对路径:相较于某个路径下,指明的路径。
绝对路径:包含盘符在内的文件或文件目录的路径。
3、路径分隔符
windows:\\
unix:/
实例化演示:
@Test
public void test1(){
//这三种只是在内存层面,并没有持久化
//构造器1
File file = new File("hello.txt");//相对于当前Model
File file1 = new File("F:\\kuangshencode\\JavaSE\\基础语法\\he.txt");
System.out.println(file);
System.out.println(file1);
//构造器2
File file2 = new File("F:\\kuangshencode\\","JavaSE");
System.out.println(file2);
//构造器3
File file3 = new File(file2,"hi. txt");
System.out.println(file3);
}
file中的常用方法1
mkdir()是上层目录不存在,不创建,而mkdirs()如果上层目录不存在,也一起创建
当使用delete()时候,如果文件夹里面有文件,就不会删除该文件夹
IO流
流分类有三种
一、流的分类
1、操作数据单位:字节流,字符流
2、数据的流向:输入流、输出流
3、流的角色:节点流、处理流
二、流的体系结构
抽象基类 节点流(或文件流) 缓冲流(处理流的一种)
InputStream FileInputStream(read(byte[] c)) BufferedInputStream(read(byte[] c))
OutoutStream FileOutoutStream(write(byte[] c,0,len)) BufferedOutoutStream(write(byte[] c,0,len))
Reader FileReader(read(char[] c)) BufferedReader(read(char[] c)/readLine())
Writer FileWriter(write(char[] c,0,len)) BufferedWriter(write(char[] c,0,len))
字符流
FileReader读入操作
@Test
public void test1(){
FileReader fr = null;
try {
//1、实例化File类的对象,指明要操作的文件
File file = new File("src\\com\\lr\\io\\hello.txt");
System.out.println(file.getAbsolutePath()); //如果是test块里边相较于当亲model ,如果是main方法里边相较于当前工程
//2、提供具体的流
fr = new FileReader(file);
//3、数据的读入
//方式一
// int data = fr.read();
// while (data!=-1){
// System.out.print((char) data);
// data = fr.read()
// }
//read()返回读入的一个字符。如果达到文件末尾,返回-1
//方式二
int data;
while ((data = fr.read())!=-1){
System.out.print((char) data);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//4.流的关闭操作
if (fr!=null){
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//字符读入加强版,当然也需要用try-catch-finally包起来
@Test
public void test2() throws IOException {
//1、File类的实例化
File file = new File("src\\com\\lr\\io\\hello.txt");
//2、FileReader流的实例化
FileReader fr = new FileReader(file);
//3、读入操作
//返回每次读如数据的个数。如果达到文件末尾返回-1
char[] cbuf = new char[5];
int len = fr.read(cbuf);
while (len!=-1){
//方式一
// for (int i = 0; i <len ; i++) {
// System.out.print(cbuf[i]);
// }
String s = new String(cbuf, 0, len);
System.out.print(s);
len = fr.read(cbuf);
}
//4、资源的关闭
fr.close();
}
FileWriter字符流写出操作
从内存中写出数据到磁盘文件中
说明:
1、输出操作,对应的File可以不存在,并不会报异常
2、如果File对应的硬盘中的文件不存在,在输出的过程中,会自动创建此文件。
如果File对应的硬盘中的文件存在:
如果流使用的构造器是:FileWriter(file,false) / FileWriter(file) 对原有文件的覆盖
如果流使用的构造器是:FileWriter(file,true):不会对原有文件进行覆盖,而是在原有文件后面追加内容。
@Test
public void test3() throws IOException {
//1、提供File类的对象,指明写出到的文件
File file = new File("src\\com\\lr\\io\\hello1.txt");
//2、提供FileWriter的对象,用于数据的写出
FileWriter fw = new FileWriter(file);
//3、写出的操作
fw.write("你好呀");
//4、流资源的关闭
fw.close();
}
字符流先读入再写出(当然也需要try-catch)
@Test
public void test4() throws IOException {
//1、创建读入写出的文件
File file = new File("src\\com\\lr\\io\\hello.txt");
File file1 = new File("src\\com\\lr\\io\\hello1.txt");
//2、创建读入和写出的流
FileReader fr = new FileReader(file);
FileWriter fw = new FileWriter(file1);
//3、读入和写出操作
char[] c = new char[5];
int read = fr.read(c);
while (read!=-1){
fw.write(c,0,read);
read = fr.read(c);
}
//4、关闭资源
fr.close();
fw.close();
}
字节流
字节流复制操作
结论:对于文本文件(.txt,.java,.c,.cpp),使用字符流处理
对于非文本文件(.jpg,mp3,mp4,.avi,.doc,.ppt),使用字节流处理
字节流可以以复制文本文件,只不过中途打开看容易出现乱码,不看的话打开还是完整的
@Test
public void test2() throws IOException {
File file = new File("src\\com\\lr\\io\\杰森.jpg");
File file1 = new File("src\\com\\lr\\io\\杰森1.jpg");
FileInputStream fileInputStream = new FileInputStream(file);
FileOutputStream fileOutputStream = new FileOutputStream(file1);
byte[] b = new byte[5];
int read = fileInputStream.read(b);
while (read!=-1){
fileOutputStream.write(b,0,read);
read = fileInputStream.read(b);
}
fileInputStream.close();
fileOutputStream.close();
}
缓冲流
使用缓冲流进行文件的复制
处理流之一:缓冲流的使用
1.缓冲流
BufferedInputStream
BufferedOutStream
BufferedReader
BufferedWritter
2、提供流的读取、写入的速度
提高读写速度的原因:内部提供了一个缓冲区
3、处理流,就是“套接”在已有的流的基础上
@Test
public void test() throws IOException {
//1、造文件
File file = new File("src\\com\\lr\\io\\杰森.jpg");
File file1 = new File("src\\com\\lr\\io\\杰森2.jpg");
//2、造节点流
FileInputStream fileInputStream = new FileInputStream(file);
FileOutputStream fileOutputStream = new FileOutputStream(file1);
//3、造缓冲流
BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
//4、复制操作
byte[] b= new byte[1024];
int read = bufferedInputStream.read(b);
while (read!=-1){
bufferedOutputStream.write(b,0,read);
read = bufferedInputStream.read(b);
}
//关闭资源,先关外边后关里边
bufferedInputStream.close();
bufferedOutputStream.close();
fileInputStream.close();
fileOutputStream.close();
}
流的转换
处理流之二:转换流的使用
1、转换流:属于字符流
InputStreamReader:将一个字节的输入流,转化为字符的输入流
OutputStreamWriter:将一个字符的输出流,转换为字节的输出流
2、作用:提供字节流与字符流之间的转换
3、解码 字节、字节数组---> 字符
编码 字符---->字节、字节数组
4、字符集
@Test
public void test() throws IOException {
FileInputStream fileInputStream = new FileInputStream("src\\com\\lr\\io\\hello.txt");
java.io.InputStreamReader inputStreamReader = new java.io.InputStreamReader(fileInputStream,"UTF-8");
char[] c = new char[1024];
int read = inputStreamReader.read(c);
while (read!=-1){
String s = new String(c,0,read);
System.out.print(s);
read = inputStreamReader.read(c);
}
inputStreamReader.close();
}
先读入,再写出
@Test
public void test1() throws IOException {
File file = new File("src\\com\\lr\\io\\hello.txt");
File file1 = new File("src\\com\\lr\\io\\hello10.txt");
FileInputStream fileInputStream = new FileInputStream(file);
FileOutputStream fileOutputStream = new FileOutputStream(file1);
java.io.InputStreamReader reader = new java.io.InputStreamReader(fileInputStream,"utf-8");
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream,"gbk");
char[] c = new char[1024];
int read = reader.read(c);
while (read!=-1){
outputStreamWriter.write(c,0,read);
read = reader.read(c);
}
reader.close();
outputStreamWriter.close();
}
对象流
序列化机制:允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久的保存到磁盘上,
或通过网络把这个二进制流传输到另一个网络节点,当其他程序获取了这个二进制流,就可以还原成Java对象
对象流的使用
1、ObjectInputStream 和 ObjectOutputStream
2、作用:
3、要想一个java对象时可序列化的,需要满足相应的要求。
/*
Person类需要满足如下的要求,方可序列化
1、需要实现接口:Serializable
2、当前类提供一个全局常量:serialVersionUID
3、除了当前Person类需要实现Serializable接口之外,还要保证其内部所有部分
也必须是可序列化的。(默认,基本数据类型也是可序列化的)
补充:不能序列化static和transient修饰的成员变量
*/
/*
序列化过程:将内存中的Java对象保存到磁盘中或通过网络传输出去
*/
@Test
public void test() throws IOException {
ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream("object.dat"));
o.writeObject(new String("我爱北京天门"));
o.flush();
o.writeObject(new Person("梁瑞",22));
o.flush();
o.close();
}
/*
反序列化过程:将磁盘文件中的对象还原为内存中的一个java对象
*/
@Test
public void test2() throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("object.dat"));
Object o = objectInputStream.readObject();
String str = (String)o;
Person person = (Person) objectInputStream.readObject();
System.out.println(person);
System.out.println(str);
objectInputStream.close();
}
网络编程
计算机网络:
计算机网络是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统。
网络编程的目的:
传播交流信息,数据交换,通信。
想要达到这个效果需要什么?
- 如何准确的定位网络上的一台计算机 192.168.99.100:端口,定位到这个计算机上的某个资源
- 找到了这个计算机,如何传输数据呢?
网络通信的要素
如何实现网络通信?
通信双方的地址:
- ip
- 端口号
- 192.168.99.100:5900
规则:网络通信的协议
TCP/IP参考模型:
小结:
- 网络编程中有两个主要的问题
- 如何准确的定位到网络上的一台或多台主机
- 找到主机之后如何进行通信
- 网络编程中的要素
- IP地址和端口号 IP
- 网络通信协议 UDP,TCP
IP
ip地址:InetAddress
- 唯一定位一台网络上的计算机
- 127.0.0.1:本机localhost
- ip地址的分类
- IPV4/IPV6
- IPV4 127.0.0.1 ,4个字节组成。0~255
- IPV6 fe80::2d79:f277:fcbd:68af%3,128位。8个无符号整数
- 公网(互联网)-私网(局域网)
- 192.168.xx.xx,专门给组织内部使用的
- ABCD类地址
- IPV4/IPV6
public static void main(String[] args) {
try {
//查询本机地址
InetAddress byName = InetAddress.getByName("127.0.0.1");
System.out.println(byName);
InetAddress byName2 = InetAddress.getByName("localhost");
System.out.println(byName2);
InetAddress byName3 = InetAddress.getLocalHost();
System.out.println(byName3);
//查询网站ip地址
InetAddress byName4 = InetAddress.getByName("www.baidu.com");
System.out.println(byName4);
//常用方法
System.out.println(byName4.getHostAddress());//获取ip
System.out.println(byName4.getHostName());//域名,或自己电脑的名字
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
端口
端口表示计算机上一个程序的进程
-
不同的进程有不同的端口号!用来区分软件的!
-
被规定0~65535
-
TCP,UDP 单个协议下,端口号不能冲突
-
端口号分类
-
共有端口0~1023
- HTTP:80
- HTTPS:443
- FTP:21
-
程序注册的端口:1024~49151,分配给用户或者程序
- Tomcat:8080
- MySQL:3306
- Oracle:1521
-
动态,私有 49152~65535
netstat -ano #查看所有的端口 netstat -ano|findstr "5900" #查看指定改的端口 tasklist|findstr "8696" #查看指定端进程
-
public static void main(String[] args) { InetSocketAddress inetSocketAddress = new InetSocketAddress("localhost", 8080); System.out.println(inetSocketAddress.getHostName());//localhost System.out.println(inetSocketAddress.getAddress());//localhost/127.0.0.1 System.out.println(inetSocketAddress.getPort());//8080 }
-
通信协议
协议:约定,就好比我们现在说的普通话。
网络通信协议:速率,传输代码率,代码结构,传输控制
问题:非常复杂
大事化小:分层!
TCP/IP协议簇 实际上是一组协议
重要:
- TCP:用户传输协议
- UDP:用户数据报协议
出名的协议:
- TCP:
- IP:网络互联协议
TCP和UDP对比
TCP:打电话
- 连接,稳定
-
三次握手,四次挥手
- 客户端、服务端
- 传输完成,释放连接,效率低
UDP:
- 不连接,不稳定
- 客户端、服务端:没有明确的界限
- 不管有没有准备好,都可以发给你
- DDOS:洪水攻击!(饱和攻击)
Tcp
客户端
-
连接服务器Socket
-
发送消息
-
//1.要知道服务器的地址,端口号 InetAddress ip = InetAddress.getByName("127.0.0.1"); int port = 9999; //2.创建一个socket连接 Socket socket = new Socket(ip, port); //3、发送消息 OutputStream os = socket.getOutputStream(); os.write("你好我是黑黑".getBytes());
服务器
-
建立服务端口ServeSocket
-
等待用户的连接通过accept
-
接收用户的消息
-
//1.我的有一个地址 ServerSocket serverSocket = new ServerSocket(9999); //2、等待客户端连接过来 Socket socket = serverSocket.accept(); //3、读取客户端消息 InputStream inputStream = socket.getInputStream(); byte[] b = new byte[1024]; int read = inputStream.read(b); while (read!=-1){ String msg = new String(b,0,read); System.out.print(msg); read = inputStream.read(); }
文件上传
服务器端
public static void main(String[] args) throws Exception {
//1、创建地址
ServerSocket serverSocket = new ServerSocket(9000);
//2、监听客户端连接
Socket socket = serverSocket.accept();
//3、获取输入流
InputStream ips = socket.getInputStream();
//3、文件输出
FileOutputStream fos = new FileOutputStream(new File("杰森1.jpg"));
byte[] b = new byte[1024];
int len = ips.read(b);
while (len!=-1){
fos.write(b,0,len);
len = ips.read(b);
}
//通知客户端,我已经接收完毕
OutputStream outputStream = socket.getOutputStream();
outputStream.write("我接受完毕了,你可以断开了".getBytes());
outputStream.close();
fos.close();
ips.close();
socket.close();
}
客户端
public static void main(String[] args) throws Exception {
//1、创建一个socket连接
Socket socket = new Socket(InetAddress.getByName("127.0.0.1"),9000);
//2、创建一个输出流
OutputStream os = socket.getOutputStream();
//3、读取文件
FileInputStream fis = new FileInputStream(new File("F:\\kuangshencode\\JavaSE\\基础语法\\杰森.jpg"));
//4、写出文件
byte[] b = new byte[1024];
int len = fis.read(b);
while (len!=-1){
os.write(b,0,len);
len = fis.read(b);
}
//通知服务器,我已经结束了
socket.shutdownOutput();//我已经传输完了
//确定服务器接收完毕,才能够断开连接
InputStream inputStream = socket.getInputStream();
byte[] b1 = new byte[1024];
int len1 = inputStream.read(b1);
while (len1!=-1){
String s = new String(b1,0,len1);
System.out.print(s);
len1 = inputStream.read(b);
}
//5、关闭资源
fis.close();
os.close();
socket.close();
}
Tomcat
服务端
- 自定义S
- Tomcat服务器 S
客户端
- 自定义 C
- 浏览器 B
UDP
发送端
public static void main(String[] args) throws IOException {
//1、建立一个socket
DatagramSocket socket = new DatagramSocket();
//2、建个包
String msg = "服务器,你好呀!";
//发送给谁
InetAddress localhost = InetAddress.getByName("localhost");
int port = 9090;
//数据,数据的长度起始,要发送给谁
DatagramPacket datagramPacket = new DatagramPacket(msg.getBytes(), 0, msg.getBytes().length, localhost, port);
//3、发送包
socket.send(datagramPacket);
//4、关闭连接
}
接收端
public static void main(String[] args) throws Exception {
//开方端口
DatagramSocket socket = new DatagramSocket(9090);
//接收数据
byte[] buffer = new byte[1024];
DatagramPacket packet = new DatagramPacket(buffer, 0, buffer.length);//接收
socket.receive(packet);//阻塞接收
System.out.println(packet.getAddress());
System.out.println(new String(packet.getData()));
//关闭资源
socket.close();
}
咨询
循环发送
public static void main(String[] args) throws Exception {
//创建连接对象
DatagramSocket socket = new DatagramSocket(8888);
while (true){
//ip和端口号
InetAddress localhost = InetAddress.getByName("localhost");
int port = 9999;
//从控制台输入
Scanner scanner = new Scanner(System.in);
String s = scanner.nextLine();
byte[] b = s.getBytes();
//创建包
DatagramPacket packet = new DatagramPacket(b,0,b.length,localhost,port);
//发送包
socket.send(packet);
if ("bye".equals(s)){
break;
}
}
socket.close();
}
循环接收
public static void main(String[] args) throws Exception {
//开方一个端口
DatagramSocket socket = new DatagramSocket(9999);
while (true){
//接收数据
byte[] b = new byte[1024];
DatagramPacket packet = new DatagramPacket(b,0,b.length);
socket.receive(packet);//阻塞接收
String str = new String(packet.getData());
System.out.println(str);
if ("bye".equals(str)){
break;
}
}
socket.close();
}
URL
统一资源定位符:定位互联网上的某一个资源
DNS域名解析 www.baidu.com xxx.x.x.x
协议:// ip 地址:端口/项目名/资源
public static void main(String[] args) throws MalformedURLException {
URL url = new URL("http://localhost:8080/helloworld/index.jsp?username=heihei&passwprd=123");
System.out.println(url.getProtocol());//协议 http
System.out.println(url.getHost());//主机ip localhost
System.out.println(url.getPort());//端口 8080
System.out.println(url.getPath());//文件 /helloworld/index.jsp
System.out.println(url.getFile());//全路径 /helloworld/index.jsp?username=heihei&passwprd=123
System.out.println(url.getQuery());//参数 username=heihei&passwprd=123
}
使用URL进行文件下载
public static void main(String[] args) throws Exception {
//1、下载地址
URL url = new URL("https://mail-activity.nos-jd.163yun.com/e522756d-2028-4e5b-be3c-04cddbb29658");
//2、连接到这个资源HTTP
HttpURLConnection urlConnection = (HttpURLConnection)url.openConnection();
InputStream inputStream = urlConnection.getInputStream();
FileOutputStream ops = new FileOutputStream("aaa");
byte[] b = new byte[1024];
int len = inputStream.read(b);
while (len!=-1){
ops.write(b,0,len);
len = inputStream.read(b);
}
ops.close();
inputStream.close();
}
多线程
process(进程)和Thread(线程)
- 说起进程,就不得不说下程序。程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。
- 而进程则是执行程序的一次执行过程,它是一个动态的概念。是系统资源分配的单位。
- 通常在一个进程中可以包含若干个线程,当然一个进程中,至少有一个线程,不然没有存在的意义。线程是cpu调度和执行的单位。
线程的三种实现方式:
- 继承Thread类
- 实现Runnable接口
- 实现Callable接口
一、继承Thread类,实现多线程(不推荐)避免单继承的局限性
注意:
- 我们不能直接调用run()方法开启线程
- 不可以让已经start()的线程去执行,一个thread对象的start()只能执行一次
//创建线程方式一:继承Thread类,重写run()方法,调用strat开启线程
//线程开启不一定立即执行,是由CPU调度执行
public class TestThread1 extends Thread{
@Override
public void run() {
//run方法线程体
for (int i = 0; i <20 ; i++) {
System.out.println("我在看代码-----"+i);
}
}
public static void main(String[] args) {
//创建一个线程对象
TestThread1 testThread1 = new TestThread1();
//调用start()方法开启线程
testThread1.start();
//main线程,主线程
for (int i = 0; i <20 ; i++) {
System.out.println("我在学习多线程-----"+i);
}
}
}
使用多线程同步下载图片
//练习Thread,实现多线程同步下载图片
public class TestThread2 extends Thread{
private String url;
private String name;
public TestThread2(String url, String name) {
this.url = url;
this.name = name;
}
//下载图片线程的执行体
@Override
public void run() {
WebDownloader webDownloader = new WebDownloader();
webDownloader.downloader(url,name);
System.out.println("下载了文件名为L"+name);
}
public static void main(String[] args) {
TestThread2 thread2 = new TestThread2("http://i0.hdslb.com/bfs/article/02db465212d3c374a43c60fa2625cc1caeaab796.png","a.jpg");
TestThread2 thread3 = new TestThread2("http://i0.hdslb.com/bfs/article/02db465212d3c374a43c60fa2625cc1caeaab796.png","b.jpg");
TestThread2 thread4 = new TestThread2("http://i0.hdslb.com/bfs/article/02db465212d3c374a43c60fa2625cc1caeaab796.png","c.jpg");
thread2.start();
thread3.start();
thread4.start();
}
}
//下载器
class WebDownloader{
//下载方法
public void downloader(String url,String name){
try {
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常,downloader出现问题");
}
}
}
二、实现Runnable接口实现多线程(推荐)
//创建线程方式2:实现Runnable接口,重写run()方法,执行线程需要丢入Runnable接口的实现类。调用strat()方法
public class TestThread3 implements Runnable{
@Override
public void run() {
for (int i = 0; i <20 ; i++) {
System.out.println("我在看代码-----"+i);
}
//run方法线程体
for (int i = 0; i <20 ; i++) {
System.out.println("我在看代码-----"+i);
}
}
public static void main(String[] args) {
//创建Runnable接口的实现类对象
TestThread3 thread3 = new TestThread3();
//创建线程对象,通过线程对象来开启我们的线程,代理
new Thread(thread3).start();
for (int i = 0; i <20 ; i++) {
System.out.println("我在学习多线程-----"+i);
}
}
}
多个线程操作同一个数据会产生线程不安全问题
//多个线程同时操作同一个对象
//买火车票的例子
//发现问题:多个线程操作同一资源的情况下,线程不安全,数据紊乱
public class TestThread4 implements Runnable{
//票数
private int ticketNums = 10;
@Override
public void run() {
while (true){
if (ticketNums<=0){
break;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"--->拿到了第几张"+ticketNums--+"票");
}
}
public static void main(String[] args) {
TestThread4 testThread4 = new TestThread4();
new Thread(testThread4,"小明").start();
new Thread(testThread4,"老师").start();
new Thread(testThread4,"黄牛").start();
}
}
模拟龟兔赛跑
//模拟龟兔赛跑
public class TestThread5 implements Runnable{
//胜利者
private static String winner;
@Override
public void run() {
for (int i = 0; i <=100 ; i++) {
//模拟兔子休息
if (Thread.currentThread().getName().equals("兔子")&&i%10==0){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (gameOver(i)){
break;
}
System.out.println(Thread.currentThread().getName()+"--->跑了"+i+"步");
}
}
//判断是否完成比赛
private boolean gameOver(int steps){
//判断是否有胜利者
if (winner!=null){//已经存在胜利者
return true;
}else if (steps>=100){
winner = Thread.currentThread().getName();
System.out.println("winner is"+winner);
return true;
}else {
return false;
}
}
public static void main(String[] args) {
TestThread5 testThread5 = new TestThread5();
new Thread(testThread5,"乌龟").start();
new Thread(testThread5,"兔子").start();
}
}