Java笔记
(8条消息) 狂神说Java–Java学习笔记(合集)_fllow_wind的博客-CSDN博客_狂神说java笔记
一、Java的简单介绍
1.1 初识Java
1.2 Java的三大版本
- JavaSE: 标准版,桌面程序,控制台开发…
- JavaME: 嵌入式开发 (手机,小家电…),已经凉了
- JavaEE: E企业级开发 (Web端,服务端开发…),JavaSE为基础(Java最好的产品)
1.3 JDK JRE JVM
- JDK:Java Development Kit (Java开发者工具,包括 JRE,JVM)
- JRE:Java Runtime Environment (Java运行时环境)
- JVM:Java Virtual Machine (Java虚拟机,跨平台核心)
1.4 Java的运行机制
- 编译型
- 解释型
当一个外国佬要看三体的时候,他有两者选择,第一:他可以买三体被完全翻译的书(类比编译型)第二:他可以找一个翻译者让翻译者一句一句翻译给他听(类比解释型)
Java两者都有(详情百度)
二 Java的基础语法
2.1 注释
(学会调整注释的颜色:setting->Editor->Color Scheme->Java->Comments)
书写注释是一种良好的习惯
- 单行注释//
- 多行注释/**/
- 文本注释/** * */
2.2 关键字和标识符
标识符:简单来说,凡是可以由程序员自己来命名的单词都是标识符。eg: 类名、方法名、变量名、接口名、常量名…
关键字:Java 关键字是编程语言里事先定义的,有特殊意义的单词, Java 中所有的关键字都是小写的英语单词
- 所有标识符都应该以 字母、$(美元符)、_(下划线) 开头
- 首字母之后可以是 字母、$、_ 或数字任何字符组合
- 关键字不能作为变量名或方法名
- 标识符大小写敏感
2.3 数据类型
java是强语言类型
java的数据类型可分成两种:基本类型 和 引用类型(类 接口 数组)
字节:
- 位(bit):是计算机内部数据存储的最小单位,11001100是一个八位二进制数
- 字节(byte, B):是计算机中 数据处理 的基本单位,习惯上用大写B表示
- 1B(byte) = 8bit,1字节等于8位 1B=8b 1024B = 1KB, 1024KB = 1M, 1024M = 1G
- 字符:指计算机中使用的字母,数字,字和符号
拓张与面试题:
//整数扩展: 二进制0b 八进制0 十进制 十六进制0x
int i = 10;//输出10
int i2 = 010; //八进制 输出8
int i3 = 0x10; //十六进制 输出16
//浮点数扩展:
//面试题:银行业务字母怎么表示钱? BigDecimal(数学工具类)
//float double是有问题的,最好避免使用浮点数进行比较
float f = 0.1f; //0.1
double d = 1.0/10; //0.1
System.out.println(f==d); //false
//浮点数 位有限,舍入误差,大约
//最好避免使用浮点数进行比较
float f1 = 23131313131f;
float f2 = f1+1;
System.out.println(f1==f2); //true
//字符扩展:所有字符本质还是数字
char c1 = 'a';
char c2 = '中';
System.out.println(c1); //a
System.out.println((int)c1);//强制类型转换,97
System.out.println(c2); //中
System.out.println((int)c2);//强制类型转换,20013
//编码 Unicode表(97=a,65=A)2字节 0-65536
//U000~UFFFF 十六进制(u0061=a,相当于十进制的97)
System.out.println('\u0061'); //a '\'是转义字符
//布尔值扩展
boolean flag = true;
if(flag==true){} //新手
if(flag){} //老手这样写 Less is More(代码要精简易读)
2.4 类型转换
由于java是强类型的语言所以进行运算时往往需要进行类型的转换
强制转换:容量从高到低
自动转换:容量从低到高
小数的优先级大于整数
//强制转换 (类型)变量名 高--低
//自动转换 低--高
int i = 128;
byte b = (byte)i; //强制转换 内存溢出 -128~127
double d =i; //自动转换
System.out.println(i); //128
System.out.println(b); //-128
System.out.println(d); //128.0
/*
注意点:
1.不能对布尔值进行转换
2.不能把对象类型转换为不相干的类型
3.在把高容器转换到低容量的时候,强制转换
4.可能存在内存溢出,或者精度问题
* */
System.out.println((int)23.7); //23 丢失精度
char c = 'a';
int n = c+1;
System.out.println(n); //98
System.out.println((char)n); //b
//当操作数比较大时,注意溢出问题
//JDK7新特性,数字之间可以用下划线分割
int money = 10_0000_0000; //10亿,下划线不会被打印出来
System.out.println(money); //1000000000
int years = 20;
int total = money*years; //数据大,溢出
System.out.println(total); //-1474836480
long total2 = money*years; //默认是int,转换前就有溢出问题
System.out.println(total2); //-1474836480
long total3 = money*(long)years; //先把一个数转Long
System.out.println(total3); //20000000000
2.5 变量、常量、作用域
变量的命名规范:
- 类成员变量:首字母小写和驼峰原则:monthSalary 除了第一个单词之外,后面的单词首字母都要大写
- 局部变量:首字母小写和驼峰原则
- 方法名:首字母小写和驼峰原则:run(); runRun();
- 常量:字母全大写+下划线:MAX_VALUE
- 类名:首字母大写和驼峰原则;GoodMan
public class demo01 {
//int a,b,c;尽量不要这样写,建议把他们分开写,程序的可读性
//int a;
//int b;
//int c;
//变量的作用域
//实例变量:从属于对象;如果不进行初始化,其值为这个类型的默认值:0、0.0;布尔型默认是false,除了基本类型其他的都是null
String name;
int age;
//类变量static
static double salary = 2500;
//main方法
public static void main(String[] args){
//局部变量,必须声明和初始化值,无法在add方法中使用
int i = 10;
System.out.println(i);//当不初始化值的时候会报错,只是写int i;时报错
demo01 demo01 = new demo01();//快捷键alt+enter即可补全
System.out.println(demo01.age);//输出0
System.out.println(demo01.name);//输出null
//类变量 static 此时就不需要用new了
System.out.println(salary);
}
//其他方法
public void add(){
}
//常量,修饰符不存在前后顺序,常量一般都用大写字母表示
static final double PI = 3.14;//也可以写成final static
}
变量作用域
- 类变量(static)
- 实例变量
- 局部变量
public class Variable{
static int allClicks = 0; //类变量
String str = "hello world"; //实例变量
public void method(){
int i=0; //局部变量
}
}
常量
- 常量:初始化后不能再改变的值,不会变动的值。
- 可以理解为一种特殊的变量,其值被设定后,在程序运行过程不允许被更改。
//常量一般用大写字符
final 常量名=值;
final double PI=3.14;
//修饰符 不存在先后顺序,static可以写final后面
static final doube PI=3.14; //类变量,该类下的全局范围
2.6 运算符
- 算数运算符
- 赋值运算符
- 关系运算符
- 逻辑运算符
- 位运算符
- 条件运算符
- 拓展运算符
int a=10;
int b=20;
System.out.println(a/b); //0
System.out.println((double)a/b); //0.5
long c=12300000000;
System.out.println(a+b); //int
System.out.println(a+c); //long 自动转换式子中容量大的数据类型
很多运算,会用到一些工具类来操作:如幂运算在Java中2^3是不支持的,可以使用math类来操作
// ++自增 --自减 单目运算符
int a = 3;
int b = a++; //b=a,a=a+1 先赋值 即b=3 a=4
int c = ++a; //a=a+1,c=a 先自增 即a=5 c=5
System.out.println(a); //5
System.out.println(b); //3
System.out.println(c); //5
//幂运算 2^3 2*2*2=8
double pow = Math.pow(2,3); // (底数,指数)double型
System.out.println(pow); //8.0
public class demo01 {
public static void main(String[] args){
//短路运算
int c = 5;
boolean d = (c<4) && (c++<4);
System.out.println(d);//输出false
System.out.println(c);//输出5,因为&&在运算过程中如果发现前面的为假的时候就不会考虑到后面的情况,即在发现c<4为假时就不会在考虑c++<4了
}
}
public class demo01 {
public static void main(String[] args){
/*
A = 0011 1100;
B = 0000 1101
-------------
A&B = 0000 1100;
A|B = 0011 1101;
A^B = 0011 0001;
~B = 1111 0010;
面试题:2*8如何算才最快
2*2*2*2=16;
<< 右移运算符 相当于*2
>> 左移运算符 相当于/2
左右移动都应该是相对于小数点来说的
所以右移3位即可
0000 0010 2
0001 0000 16
*/
System.out.println(2<<3);//输出16
}
}
public class demo01 {
public static void main(String[] args){
//字符串连接符,当输出一方有字符串时,即认为是字符串的拼接
int a = 10;
int b = 20;
System.out.println(a+b+"");//输出30,此时先运算+号再进行字符串的拼接
System.out.println(""+a+b);//输出1020,此时只进行字符串的拼接
}
}
2.7 包机制
重点:(8条消息) Java进阶——包机制概述_脑袋不灵光的小白羊的博客-CSDN博客
包的本质其实就是一个文件夹
一般用公司的域名倒置作为包名
package com.hong.www;//定义包的路径
import com.hong.www.demo01;//导入包
2.8 JavaDoc
(8条消息) Javadoc生成的详细操作教程_何学长在奔跑的博客-CSDN博客_javadoc怎么生成
javadoc命令是用来生成自己的api文档的
参数信息:
- @author作者名
- @version版本号
- @since 指明需要最早使用的jdk版本
- @param参数名
- @return返回值情况
- @throws异常抛出情况
package com.hong.www;
import com.hong.www.demo01;
/**
* @author JJ
* @version 1.0
* @since 1.8
*/
public class demo01 {
String name ;
/**
*
* @param name
* @return
* @throws Exception
*/
public String test(String name) throws Exception{
return name;
}
}
idea生成javadoc包:
三 Java的流程控制
3.1 用户交互Scanner
java给我们提供了一个工具类,我们可以获取用户的输入:java.util.Scanner 是Java5 的新特征,我们可以通过Scnner 类来获取用户的输入。通过Scanner类的next()与nextLine()方法获取输入的字符串,在读取前我们一般需要使用hasNext() 与 hasNextLine()判断是否还有输入的数据
基本语法:
Scanner s = new Scanner(System.in)
- next():
- 对输入有效字符之前遇到的空白,next() 方法会自动将其去掉
- 只有输入有效字符后才将其后面输入的空白作为分隔符或或者结束符
- next() 不能得到带有空格的字符串
- nextLine()
- 以enter为结束符,也就是说nextLine()方法返回的是输入回车之前所有的字符
- 可以获取到空格
package com.hong.scanner;
import java.util.Scanner;
public class Demo01 {
public static void main(String[] args){
//创建一个扫描器对象,用于接收键盘数据
Scanner scanner = new Scanner(System.in);
System.out.println("使用next方式接收:");
//判断用户有没有输入字符串
if(scanner.hasNext()){
//使用next方式接收
String str = scanner.next();//程序会等待用户输入完毕
//String str = scanner.nextLine();输入hello world时,next输出的是hello 而nextLine输出的则是hello world
System.out.println("输出的内容为:"+str);
}
scanner.close();//凡是属于IO流的类如果不关闭会一直占用资源
}
}
//从键盘接收数据
Scanner scanner = new Scanner(System.in);
System.out.println("请输入数据:");
String str = scanner.nextLine();
System.out.println("输入的内容为:"+str);
scanner.close();
System.out.println("请输入整数:");
if(scanner.hasNextInt()){
int i=scanner.nextInt();
System.out.println("输入的整数为:"+i);
}else {
System.out.println("输入的不是整数数据");
}
3.2 顺序结构
- if
- switch()…case…break…default
反编译:目前不知道有什么用,在P37视频里面
3.3 选择结构
3.4 循环结构
- while
- do…while() 即使不满足条件也让程序执行一次
- for(初始化;布尔值;更新) (偷懒操作:100.for+回车键即可快捷变成for(int i=0; i<100;i++))
System.out.print();//输出完以后不会换行
System.out.println();//输出完以后会换行
int[] numbers = {10,20,30,40,50};//定义了一个数组
//遍历数组元素
for(int x:numbers){
System.out.println(x);
3.5 break & continue
- break退出循环
- continue退出本次循环
3.6 练习
四 Java的方法
4.1 何为方法
方法是语句的集合,他们在一起执行一个功能。
4.2 方法的定义及调用
方法包含一个方法头和一个方法体。
- 修饰符:可选,定义了方法的访问类型,告诉编译器如何调用该方法。
- 返回值类型:方法可能会返回值。returnValueType是方法返回值的数据类型。有些方法没有返回值,则returnValueType为关键字void。
- 方法名:是方法的实际名称,方法名与参数表共同构成方法签名。
- 参数类型:像一个占位符。方法被调用时,传递值给参数,该值称为实参或变量。参数列表是指方法的参数类型、顺序和参数个数。参数是可选的,方法可以不包含任何参数。
- 形式参数:在方法被调用时用于接收外界输入的数据。
- 实参:调用方法时实际传给方法的数据。
- 方法体:方法体包含具体的语句,定义该方法的功能。
修饰符 返回值类型 方法名 ( 参数类型 参数名 ){
……
方法体;
……
return返回值;
}
4.3 方法的重载
重载就是在一个类中有相同的函数名,当形参不同的函数
方法的重载规则:
- 方法名必须相同
- 参数列表必须不同(个数不同,类型不同,参数的顺序不同)
- 方法的返回类型可以不同也可以相同
- 仅仅返回类型不同不足以构成方法的重载
实现理论:
方法名称相同时,编译器会根据调用参数的个数,参数的类型登等去逐个匹配,以选择对应的方法,如果匹配失败,则报错
4.4 命令行传参
有时候你希望运行一个程序时再传递给他消息,这就要靠传递命令参数main()函数去实现
package com.hong.method;
public class Demo01 {
public static void main(String args[]){
for (int i = 0; i < args.length; i++) {
System.out.println("args["+ i +"]:"+args);
}
}
}
4.5 可变参数
- 一个方法中只能指定一个可变参数,它必须是方法的最后一个参数。
public class Demo01 {
public static void main(String args[]){
Demo01 demo01 = new Demo01();
demo01.test(1,2,3,5);
}
public void test(int... i){
System.out.println(i[0]);
System.out.println(i[1]);
System.out.println(i[2]);
}
4.6 递归
递归结构包括两个部分:
- 递归头:什么时候不调用滋自身方法,如果没有头,将陷入死循环
- 递归体:什么时候需要调用自身方法
五 数组
5.1 数组概述
5.2 数组声明创建
//数组的声明:
dataType[] arrayRefVar;//首选方法
dataType arrayRefVar[];//早期为了让C和C++工作者方便学习Java而设计的
//创建数组:
dataType[] arrayRefVar = new dataType[arraySize];
//获取数组的长度:
arrays.length
数组的初始化:
- 静态初始化
int[] a = {1,2,3};
Man[] mans = {new Man(1,1),new Man(2,2)};
- 动态初始化
int[] a = new int[2];
a[0] = 1;
a[1] = 2;
- 数组的默认初始化
数组是引用类型,他的元素相当于实例变量,因此数组一经分配空间,其中的每个元素也被按照实例变量同样的方式被隐式初始化
数组的长度一旦被创建就是确定的,不可变的,如果越界,则报错:ArrayIndexOutofBounds
5.3 数组使用
package com.hong.array;
public class Demo01 {
public static void main(String[] args){
int[] arrays = {1,2,3,4,5};
for (int array : arrays) {//for-each
System.out.println(array);
}
int[] reverse = reverse(arrays);
printArray(reverse);
}
//打印数组元素,数组作为方法入参
public static void printArray(int[] arrays){
for (int i = 0; i < arrays.length; i++) {
System.out.print(arrays[i]+"");
}
}
//反转数组,数组作为返回值
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;
}
}
5.4 多维数组
可以理解为坐标(线、平面、立体……)
int a[][] = new int[2][5];
5.5 Arrays类
数组的工具类:java.util.Arrays
public class Demo01 {
public static void main(String[] args){
int[] a = {1,23,4,78,34,66,10};
//直接输出
System.out.println(a);//[I@16b98e56
//打印数组元素Arrays.toString
System.out.println(Arrays.toString(a));//[1, 23, 4, 78, 34, 66, 10]
//对数组进行排序,升序
Arrays.sort(a);
System.out.println(Arrays.toString(a));//[1, 4, 10, 23, 34, 66, 78]
//数组填充
Arrays.fill(a,10);
System.out.println(Arrays.toString(a));//[10, 10, 10, 10, 10, 10, 10]
}
八大排序:
(8条消息) 八大排序详解-超详细_你温柔的慈悲的博客-CSDN博客_几大排序
(8条消息) 超详细的八大排序算法的各项比较以及各自的特点_m0_37962600的博客-CSDN博客_八大排序
5.6 稀疏数组
//创建一个二维数组 11*11 0:没有棋子,1:黑棋 2:白棋
int[][] array1 = new int[11][11];
array1[1][2] = 1;
array1[2][3] = 2;
//输出原始的数组
System.out.println("原始的数组:");
for (int[] array : array1) {
for (int i : array) {
System.out.print(i+"\t");
}
System.out.println();
}
//转换为稀疏数组保存
//1.有效值的个数
int sum = 0; //有效值总数
for (int i = 0; i < 11; i++) {
for (int j = 0; j < 11; j++) {
if(array1[i][j]!=0){
sum++;
}
}
}
//2.创建一个稀疏数组
int[][] array2 = new int[sum+1][3];
array2[0][0] = 11;
array2[0][1] = 11;
array2[0][2] = sum;
//3.遍历二维数组,将有效值存放到稀疏数组
int count = 0;
for (int i = 0; i < array1.length; i++) {
for (int j = 0; j < array1[i].length; j++) {
if(array1[i][j]!=0){
count++;
array2[count][0] = i;
array2[count][1] = j;
array2[count][2] = array1[i][j];
}
}
}
//4.输出稀疏数组
System.out.println("稀疏数组:");
for (int i = 0; i < array2.length; i++) {
for (int j = 0; j < array2[i].length; j++) {
System.out.print(array2[i][j]+"\t");
}
System.out.println();
}
/* 结果:
输出原始的数组
0 0 0 0 0 0 0 0 0 0 0
0 0 1 0 0 0 0 0 0 0 0
0 0 0 2 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
稀疏数组
11 11 2
1 2 1
2 3 2
*/
六 面向对象
6.1 初识面向对象
面向过程&面向对象
面向对象编程(Object-Oriented Programming,OOP)的本质就是:以类的方式组织代码,以对象的组织(封装)数据
三大特征:
- 封装
- 继承
- 多态
6.2 方法回顾与加深
方法的定义:
- 修饰符
- 返回值类型
- break 和 return的区别
- 方法名
- 参数列表
- 抛出异常
方法的调用:
- 静态方法:static
- 非静态方法
- 形参和实参
- 值传递和引用传递
- this关键字
6.3 对象的创建分析
类是一种抽象的数据结构,它是对某一事物整体描述或定义,但是并不能代表某一具体事物
对象是抽象概念的具体实例
package com.oop.Deme02;
//学生类
public class Student {
//属性:字段
String name;
int age;
//方法
public void study(){
System.out.println(this.name+"在学习");
}
}
package com.oop.Deme02;
//一个项目应该只存在一个main方法
public class Application {
public static void main(String[] args){
//类:抽象的,实例化
//类实例化后会返回自己的一个对象
//student对象就是一个一个Student类的具体实例
Student xiaoxiao = new Student();
xiaoxiao.name = "小小";
xiaoxiao.age = 5;
System.out.println(xiaoxiao.name);
System.out.println(xiaoxiao.age);
}
}
类中的构造器也称为构造方法,是在进行创建对象的时候必须要调用的,并且构造器有以下两个特点:
- 必须和类的名字相同
- 必须没有返回类型,也不能写void
package com.oop.Deme02;
public class Person {
//一个类即使声明都不写,它也会存在一个方法
//显示的定义构造器
String name;
//2.实例化初始值
//1.使用new关键字,本质是在调用构造器
//默认构造器
public Person(){
this.name = "007";
}
//有参构造:一旦定义了有参构造,无参构造就必须显示定义,(即如果需要用到无参构造,那么就得自己写无参构造,不能idea生成)
public Person(String name){
this.name = name;
}
//alt + insert 可以快捷构造器
}
/*
package com.oop.Deme02;
//一个项目应该只存在一个main方法
public class Application {
public static void main(String[] args){
//new 实例化了一个对象
Person person = new Person();
System.out.println(person.name);
}
}
*/
package com.oop.Deme02;
public class Pet {
public String name;
public int age;
public void shout(){
System.out.println("叫了一声");
}
}
package com.oop.Deme02;
//一个项目应该只存在一个main方法
public class Application {
public static void main(String[] args){
Pet dog = new Pet();
dog.name = "旺财";
dog.age = 1;
dog.shout();
System.out.println(dog.name);
System.out.println(dog.age);
Pet cat = new Pet();
}
}
6.4 面向对象三大特征
封装:高内聚,低耦合。高内聚就是类的内部数据操作细节由自己完成,不允许外部干涉;低耦合就是仅暴露少量的方法给外部使用。属性私有:get 和 set
package com.oop;
import com.oop.demo04.Student;
public class Application {
public static void main(String[] args){
Student s1 = new Student();
s1.setName("jj");
System.out.println(s1.getName());
}
}
package com.oop.demo04;
public class Student {
//private存在就无法使用s1.name 调用了
//属性私有
private String name;
private int id;
private char sex;
//提供一些可以操作这个属性的方法
//提供一些public的get set方法
//get 获得这个数据
public String getName(){
return this.name;
}
//set 给这个属性设置值
public void setName(String name){
this.name = name;
}
//快捷键Alt+Insert
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public char getSex() {
return sex;
}
public void setSex(char sex) {
this.sex = sex;
}
}
继承:Java中只有单继承,无多继承。extends 的意思是“扩展”,子类是父类的扩展。继承是类和类之间的一种关系,除此自外还有依赖、组合、聚合等等。子类也叫派生类,父类也叫基类。在java中所有的方法都直接或者间接继承Object类
-
继承的本质是对某一批类的抽象,从而实现对世界更好地建模。
-
extends的意思是”扩展“。子类是父类的扩展,使用关键字extends来表示。
-
Java中类只有单继承,没有多继承!一个类只能继承一个父类。
-
继承是类与类之间的一种关系,此外还有依赖、组合、聚合等。
-
继承关系的两个类,一个为子类(派生类),一个为**父类(基类)**子类继承父类。
-
子类和父类之间,从意义上讲应该具有”is a“的关系。
-
子类继承了父类,就会拥有父类的全部方法,而private私有属性及方法无法继承。
-
在Java中,所有类,都默认直接或间接继承Object类 (Ctrl+H 可以查看类关系)
-
被final修饰的类,无法被继承(断子绝孙)。
重写:需要有继承关系才可以,子类重写父类的方法,子类的方法和父类的一定要相同,方法体不同
- 方法名必须相同
- 参数列表必须相同
- 修饰符:范围可以扩大但不能缩小
- 抛出的异常:范围可以缩小但不能扩大:ClassNotFoundException–>Exception(大)
为什么需要重写:父类的方法子类;不一定需要或者不一定满足
public class B {
public static void test(){ //静态方法
System.out.println("B==>test()");
}
}
public class A extends B{ //继承
public static void test(){
System.out.println("A==>test()");
}
}
public class Application {
public static void main(String[] args) {
//方法的调用之和左边定义的类型有关
A a = new A();
a.test(); //打印 A==>test()
//父类的引用指向了子类,但静态方法没有被重写
B b = new A();
b.test(); //打印 B==>test()
}
}
修改A.java, B.java
public class B {
public void test(){ //非静态方法
System.out.println("B==>test()");
}
}
public class A extends B{
@Override //重写了B的方法
public void test() {
System.out.println("A==>test()");
}
}
//父类的引用指向了子类
B b = new A(); //子类重写了父类的方法,执行子类的方法
b.test(); //打印变成了 A==>test()
/*
静态方法是类的方法,非静态方法是对象的方法
有static时,b调用了B类的方法,因为b是b类定义的
没有static时,b调用的是对象的方法,而b是A类new出来的对象,调用A的方法
*/
多态:多态是方法的多态,属性是没有多态的
多态的存在条件:
- 有继承关系
- 子类重写父类的方法
- 父类引用指向子类对象
instanceof:引用类型比较,判断一个对象是什么类型
System.out.println(x instanceof y)//如果x 和y 没有父子关系那么编译不通过,当x是y 的子类时输出true
public static void main(String[] args) {
// Object > String
// Objest > Person > Student
// Objest > Person > Teacher
Object object = new Student();
// X instanceof Y,X引用指向的对象是不是Y的子类
System.out.println(object instanceof Student); //true
System.out.println(object instanceof Person); //true
System.out.println(object instanceof Teacher); //false
System.out.println(object instanceof Object); //true
System.out.println(object instanceof String); //false
//类型之间的转化:父-子(高-低),低可以转换为高
Person obj = new Syudent(); //只能用Person方法(重写了用子类重写过的方法)
(Syudent)obj.go(); //强转之后可以用Student方法(Student->go())
}
static:
- 静态变量可以直接用类名访问,也称类变量。
- 静态变量(或方法)对于类,所有对象(实例)所共享。
- 静态区代码 加载类时一起被初始化,最早执行且只执行一次(第一次new)。
package com.oop.demo08;
import static java.lang.Math.random;//就可以简单写成System.out.println(randaom());
public class Student {
private static int age;//静态变量
private double score;//非静态变量
public void run(){}
public static void go(){}
{
//匿名代码块,可以赋值
}
static{
//静态代码块
}
public static void main(String[] args) {
Student s1 = new Student();
System.out.println(Student.age);
System.out.println(s1.age);
//System.out.println(Student.score);编译器报错
System.out.println(s1.score);
Student.go();
go();
new Student().run();
}
}
super & this
- super()调用父类的构造方法,必须在构造方法的第一个
- super必须只能出现在子类的方法或构造方法中
- super() 和 **this()**不能同时调用构造方法,因为this也必须写在第一行
- 子类如果没有显式的调用父类的构造方法时,idea就会隐式的调用父类的无参构造即super();
- super与this的区别:super代表父类对象的引用,只能在继承条件下使用;this调用自身对象,没有继承也可以使用。
6.5 抽象类和接口
抽象类(abstract):
- 不能new这个抽象类,只能靠继承的子类失去实现它
- 抽象类中可以写普通方法
- 但抽象方法只能在抽象类中写
- 抽象的抽象
//abstract 抽象类 类只能单继承(接口可以多继承)
public abstract class Action {
//约束~有人帮我们实现~
//抽象方法只有方法名,没有方法的实现
public abstract void doSth();
//1.不能new抽象类,只能靠子类去实现它,仅作为一个约束
//2.抽象方法只能出现在抽象类中,抽象类可以有普通方法
//3.抽象类有构造器,可以派生子类
//4.抽象类的意义:约束,提高开发效率。但是类只能单继承,所以有局限 用的不多
}
接口:只有自己的规范,自己无法写方法,声明类的关键字是class,声明接口的关键字是interface,接口的本质是契约,接口可以多继承,接口中定义的方法都是抽象的public abstract
一个类可以实现接口implements
//interface接口,接口都要有继承类
//实现类(implements 可以继承多个接口)
//多继承,利用接口实现多继承
public interface UserService {
//定义的属性都是常量,默认修饰 public static final
public static final int AGE = 99; //一般不用
//所有的定义的方法都是抽象的 默认public abstract
public abstract void run();
void add();
void query();
void delete();
}
6.6 内部类及OOP实战
内部类:就是在一个类的内部再定义一个类,qs:A类中定义B类,那么相对于A类来说B类就是内部类
内部类有:
-
成员内部类 可以操作外部类的私有属性及方法
-
静态内部类 static修饰,不能访问外部类私有属性
-
局部内部类 外部类的方法里定义的类
-
匿名内部类 没有名字初始化类(new Apple().eat()😉
七 异常机制
- 检查性异常:最具代表性的就是用户错误或者问题引起的异常,这是程序员无法预知的
- 运行时异常:是可能被程序员避免的异常,与检查性异常相反,运行时异常可以在编译时被忽略
- 错误:错误不是异常,而是脱离程序员控制问题
java把异常当作对象来处理,并定义一个基类:**java.lang.Throwable **作为所有异常的超类,在Java API中已经定义了许多异常,这些异常分成两类,错误Error 和 异常Exception
7.1 什么是异常
7.2 异常体系结构
7.3 Java异常处理机制
抛出异常
捕获异常
异常处理的五个关键字:try,catch,finally,throw,throws
package com.hong.exception;
public class demo01 {
public static void main(String[] args) {
int a = 0;
int b = 4;
//假设需要捕获多个异常:异常要从小打到排序
try{//try监控区域
System.out.println(b/a);
}catch(Error e){//catch(想要捕获的异常类型)
System.out.println("Error");
}catch(Exception e){
System.out.println("Exception");
}catch(Throwable e){
System.out.println("Throwable");
}finally{//处理善后工作
System.out.println("finally");
}
}
}
//快捷键Ctrl+Alt+T
package com.hong.exception;
public class demo01 {
public static void main(String[] args){
new demo01().test(0,4);
}
public void test(int a,int b) throws ArithmeticException{
if(a==0){
throw new ArithmeticException();//主动抛出异常,一般在方法中使用
}
}
}
7.4 处理异常
7.5 自定义异常
用户定义异常,只需要继承Exception即可
package com.hong.exception;
public class demo02 extends Exception{
private int number;
public demo02(int a){
this.number = a;
}
@Override
public String toString() {
return "demo02{" +
"number=" + number +
'}';
}
}
package com.hong.exception;
public class Test {
static void test(int a) throws demo02{
System.out.println("传递的参数是:"+a);
if(a>10){
throw new demo02(a);//抛出
}
System.out.println("OK");
}
public static void main(String[] args) {
try{
test(11);
} catch (com.hong.exception.demo02 e) {
System.out.println("demo02=>"+e);
}
}
}
//传递的参数是:11
//demo02=>demo02{number=11}
7.6 总结
八 集合
(4条消息) 黑马程序员全套Java教程_Java基础教程_集合进阶之Collection(二十四)_丶槛外的博客-CSDN博客
这个博主是分开的,在他的空间中找笔记
- 我们知道集合最主要的作用是用来存取数据的。
(1)单列数据通过Collection集合存储,Collection集合也被称为单列集合;双列数据通过Map集合实现,Map集合也被称为双列集合。
(2)单列集合中数据存储:List集合存储元素是可重复的,Set集合存储元素不可以重复。
(3)但是这些集合都只是接口,并不能直接创建对象并使用,所以下面还有具体的实现类。
8.1 Collection
Collection集合概述:
- 是单例集合的顶层接口,它代表一组对象,这些对象也称为Collection的元素
- JDK 不提供此接口的任何直接实现,它提供更加具体的子接口(如Set和List)的实现
创建Collection集合的对象:
- 多态的方式
- 具体的实现类ArrayList
package com.collection;
import java.util.ArrayList;
import java.util.Collection;
public class Demo01 {
public static void main(String[] args) {
//创建Collection集合对象
Collection<String> s = new ArrayList<String>();
//添加元素: boolean add(E e)
s.add("hello");
s.add("world");
s.add("java");
//输出集合对象
System.out.println(s);//[hello, world, java]
//ArrayList中重写了toString方法
}
}
Collection常用的方法:
- boolean add(E e) 添加元素,添加成功返回true
- boolean remove (Object o)从集合中移除指定的元素
- void clear()清空集合中的所有元素
- boolean contains(Object o)判断集合中是否有指定的元素
- boolean isEmpty()判断集合是否为空
- int size()集合的长度,即集合中元素的个数
package com.collection;
import java.util.ArrayList;
import java.util.Collection;
public class demo02 {
public static void main(String[] args) {
Collection<String> c = new ArrayList<String>();
c.add("hello");
c.add("world");
c.add("java");
System.out.println(c.remove("jjj"));//返回false
//c.clear();返回[]
System.out.println(c.contains("a"));//返回false
System.out.println(c.isEmpty());//输出false
System.out.println(c.size());//输出3
}
}
Collection集合的遍历
Iterator:迭代器,集合的专用遍历方式。
(1)Iterator iterator():返回此集合中元素的迭代器,通过集合的Iterator()方法得到;
(2)迭代器是通过集合的Iterator方法得到的,所以我们说它是依赖于集合而存在的(ArrayList的内部类Itr实现了Iterator接口)。
Iterator:常用的方法有
- E next():返回迭代中的下一个元素
- boolean hasNext():如果迭代具有更多的元素,则返回true
package com.collection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class IteratorTest {
public static void main(String[] args) {
Collection<String> c = new ArrayList<String>();
c.add("hello");
c.add("world");
c.add("java");
//Iterator<E> iterator();返回此集合中元素的迭代器,通过集合的iterator()方法获得
Iterator<String> it = c.iterator();
//E next()返回迭代的下一个元素
System.out.println(it.next());//hello
System.out.println(it.next());//world
System.out.println(it.next());//java
System.out.println(it.next());//NoSuchElementException:表示被请求的元素不存在
if(it.hasNext()){System.out.println(it.next());}
if(it.hasNext()){System.out.println(it.next());}
if(it.hasNext()){System.out.println(it.next());}
if(it.hasNext()){System.out.println(it.next());}//由于it.hasNext()为false所以不执行输出
}
}
案例:Collection集合存储学生对象并遍历
需求:创建一个存储学生对象的集合,存储三个学生,使用程序实现在控制台遍历该集合
思路:
- 定义学生类
- 创建Collection集合对象
- 创建学生对象
- 把学生添加到集合中
- 遍历集合
package com.collection.studentTest;
public class Student {
private String name;
private int age;
public Student(){};
public Student(String name,int age){
this.name = name;
this.age = age;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package com.collection.studentTest;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class CollectionDemo {
public static void main(String[] args) {
//创建Collection集合对象
Collection<Student> c = new ArrayList<Student>();
//创建学生对象
Student s1 = new Student("JJ",20);
Student s2 = new Student("KK",15);
Student s3 = new Student("LL",19);
//把学生添加到集合中
c.add(s1);
c.add(s2);
c.add(s3);
//遍历集合
Iterator<Student> it = c.iterator();
while (it.hasNext()) {
Student s = it.next();
System.out.println(s.getName()+","+s.getAge());
}
}
}
8.2 List
8.2.1 LIst集合特点:
- List集合概述:
(1)有序集合(也称为序列),用户可以精确控制列表中每个元素的插入位置。用户可以通过整数索引访问元素,并搜索列表中的元素;
(2)与Set集合不同,列表通常允许重复的元素。 - List的特点:
- 有序:存储和取出的顺序一致
- 可重复:存储的元素可以重复
package com.jihe.myList;
import java.util.ArrayList;
import java.util.List;
public class demo01 {
public static void main(String[] args) {
//创建集合对象
List<String> list = new ArrayList<String>();
//添加元素
list.add("hello");
list.add("world");
list.add("java");
list.add("hello");
//输出集合对象
System.out.println(list);//[hello, world, java, hello]
}
}
8.2.2 List集合特有的方法:
- void add(int index ,E element 在此集合中指定的位置中插入指定的元素
- E remove(int index) 删除指定索引处的元素,返回被删除的元素
- E set(int index ,E element) 修改指定索引处的元素,返回被修改的元素
- E get(int index ) 返回指定的索引处的元素
package com.jihe.myList;
import java.util.ArrayList;
import java.util.List;
public class demo02 {
public static void main(String[] args) {
//创建集合对象
List<String> list = new ArrayList<String>();
//添加元素
list.add("hello");
list.add("world");
list.add("java");
list.add(1,"c++");//[hello, c++, world, java]
//list.add(11,"python");//报错:IndexOutOfBoundsException
System.out.println(list.remove(2));//world
System.out.println(list.set(2,"javaee"));//删除java,输出java
System.out.println(list.get(2));//javaee
//输出集合对象
System.out.println(list);//[hello, world, java, hello]
//用for循环改进遍历
for(int i = 0; i < list.size();i++){
String s = list.get(i);
System.out.println(s);
}
}
}
8.2.3 List集合存储学生对象并遍历
需求:创建一个存储学生对象的集合,存储三个学生,使用程序实现在控制台遍历该集合
思路:
- 定义学生类
- 创建List集合对象
- 创建学生对象
- 把学生添加到集合中
- 遍历集合
和上面的基本一致这里就不在展示了
8.2.4 并发修改异常产生原因:
- 有一个List集合,集合中有三个元素liubei,guanyu,zhangfei,遍历集合,看看有没有“zhangfei”这个元素,如果有我们就添加一个“pangfeng”元素,我们可以这样写:
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("liubei");
list.add("guanyu");
list.add("zhangfei");
Iterator<String> it = list.iterator();
while (it.hasNext()){
if (it.next() == "zhangfei"){//ConcurrentModificationException
list.add("panfeng");
}
}
}
然而却发现程序报ConcurrentModificationException的运行时异常,此即为并发修改异常。查看API文档的解释是:当不允许这样的修改时,可以通过检测到对象的并发修改来抛出此异常。那么为什么会产生这个异常呢?
通过at itheima6.ListDemo.main(ListDemo.java:22)我们可以知道异常是在next()方法出现的问题。
通过at java.util.ArrayListI t r . n e x t ( A r r a y L i s t . j a v a : 851 ) 我 们 可 以 知 道 n e x t ( ) 为 为 j a v a . u t i l 包 下 A r r a y L i s t 类 里 面 的 内 部 类 I t r 类 的 方 法 。 通 过 a t j a v a . u t i l . A r r a y L i s t Itr.next(ArrayList.java:851)我们可以知道next()为为java.util包下ArrayList类里面的内部类Itr类的方法。 通过at java.util.ArrayListItr.next(ArrayList.java:851)我们可以知道next()为为java.util包下ArrayList类里面的内部类Itr类的方法。通过atjava.util.ArrayListItr.checkForComodification(ArrayList.java:901)我们最终定位到next()内调用的checkForComodification(),此为并发修改异常产生的原因。
- 并发修改异常的源码分析:
在mian()的第一行“List list = new ArrayList<>();”可以知道程序首先定义了一个List集合,并且后面还调用了List接口的add()和Iterator(),因为List为接口,这两个方法并没有直接在List接口中实现。我们最终创建的是ArrayList类的对象,而ArrayList类实现了List接口,所以在List类中没有实现的两个方法也是在ArrayList类中实现的。在iterator()内,还new了Itr类的对象,通过上文控制台输出的错误信息我们也知道异常是在内部类Itr的next()调用其方法checkForComodification()时产生,因此我们将内部类Itr的相关源码的也拿过来。
public interface List<E> {
boolean add(E e);
Iterator<E> iterator();
}
public class ArrayList<E> extends AbstractList<E> implements List<E>{
public boolean add(E e) {
modCount++;
elementData[size++] = e;
return true;
}
public Iterator<E> iterator() {
return new Itr();
}
private class Itr implements Iterator<E> {
int expectedModCount = modCount;//问题就出现在这里,由于先add的使modCount++,而没有进行expectedModCount = modCount;就直接if (modCount != expectedModCount)因此就报错
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
}
我们定位到checkForComodification(),发现其对modCount和expectedModCount两个值进行了比较,其中modCount为我们对集合实际修改的次数,expectedModCount为预期修改集合的次数。如果这两个值不相等就会抛出并发修改异常。在内部类开头将实际修改次数modCount赋值给了预期修改次数expectedModCount,而modCount值来自ArrayList类继承的父类AbstractList。
public abstract class AbstractList<E> {
protected transient int modCount = 0;
}
可以知道,modCount和expectedModCount两个值一开始都为0,那么我们什么时候改变某个值,导致出现异常呢?我们看到ArrayList类的add方法,发现其在第一行modCount++。综上,我们创建好迭代器it,在遍历到zhangfei并add(“panfeng”)后,再回到while循环,此时modCount发生了改变而expectedModCount却没有改变,这就是异常产生的根本原因。
迭代器在遍历的过程中,通过集合对象修改了集合的长度,造成了迭代器获元素的中判断修改值不一致
解决方法:使用for循环配合get()进行代码的编写(因为get()并没有对modCount和expectedModCount两个值进行修改)
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("liubei");
list.add("guanyu");
list.add("zhangfei");
for (int i = 0; i < list.size(); i++){
if (list.get(i) == "zhangfei"){
list.add("panfeng");
}
System.out.println(list.get(i));
}
}
8.2.5 ListIterator
ListIterator::列表迭代器
- 通过List集合的 listlterator() 方法获得,所以说它是List集合特有的迭代器
- 允许程序员沿任一方向遍历列表得列表迭代器,在迭代期间修改列表,并获取列表迭代器得当前位置
ListIterator中得常用方法:
- E next():返回迭代中的下一个元素
- boolean hasNext():如果迭代中有更多的元素,则放回true
- E pervious():返回列表中的上一个元素
- boolean hasPervious():如果此列表迭代器在相反的方向遍历列表时具有更多元素,则放回true
- void add(E e):将指定得元素插入到列表中
package com.jihe.myList;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
public class demo04 {
public static void main(String[] args) {
//创建集合对象
List<String> list = new ArrayList<String>();
//添加元素
list.add("hello");
list.add("world");
list.add("java");
//通过List集合的ListIterator()方法获取
ListIterator<String> it = list.listIterator();
//正向遍历,理解即可
while(it.hasNext()){
String s = it.next();
System.out.println(s);
}
//逆向遍历。也是理解即可
while(it.hasPrevious()){
String s = it.previous();
System.out.println(s);
}
while(it.hasNext()){
String s = it.next();
if(s.equals("world")){
it.add("javaee");
}
}//注意区别
System.out.println(list);
}
}
lt.add(“lidian”)不报错的原因:方法在执行完毕之后,重新将modCount值赋值给了expectedModCount。
//这是一部分源码
public void add(E e) {
checkForComodification();
try {
int i = cursor;
ArrayList.this.add(i, e);
cursor = i + 1;
lastRet = -1;
expectedModCount = modCount;//关键就在这里,add后马上进行 expectedModCount = modCount
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
8.2.6 增强for循环
增强for:简化数组和Collection集合的遍历
- 实现Iterable接口的类允许其对象成为增强型for语句的目标
- 它是JDK5之后出现的,其内部原理是一个Iterator迭代器
格式:
for(元素数据类型 变量名:数组或者Collection集合){
//在此处使用变量名即可,该变量就是元素
}
package com.jihe.myList.listTest;
import java.util.ArrayList;
import java.util.List;
public class demo05 {
public static void main(String[] args) {
String[] s = {"hello","world","java"};
for(String st : s){
System.out.println(st);
}
List<String> list = new ArrayList<String>();
list.add("hello");
list.add("world");
list.add("java");
for(String s1 : list){
System.out.println(s1);
}
}
}
8.2.7 三种遍历方式
package com.jihe.myList.listTest;
import com.jihe.myList.listTest.Student;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class application {
public static void main(String[] args) {
List<Student> list = new ArrayList<>();
Student s1 = new Student("JJ",13);
Student s2 = new Student("aa",149);
Student s3 = new Student("ss",16);
list.add(s1);
list.add(s2);
list.add(s3);
//迭代器遍历
Iterator<Student> it = list.iterator();
while(it.hasNext()){
Student s =it.next();
System.out.println(s.getName()+","+s.getAge());
}
System.out.println("------------");
//普通for,带索引的遍历方式
for(int i = 0;i < list.size();i++){
Student s =list.get(i);
System.out.println(s.getName()+","+s.getAge());
}
System.out.println("------------");
//增强for方式
for(Student s : list){
System.out.println(s.getName()+","+s.getAge());
}
}
}
8.2.8 数据结构
数据结构是计算机存储、组织数据的方式,是指相互之间存在一种或多种特定关系的数据元素的集合
8.2.9 List集合子类特点
List集合常用的子类特点:ArrayList ,LinkedList
- ArrayList:底层数据结构是数组,查询快,增删慢(常用)
- LinkedList:底层数据结构是链表,查询慢,增删快
package com.jihe.myList;
import java.util.ArrayList;
import java.util.LinkedList;
public class demo06 {
public static void main(String[] args) {
ArrayList<String> array = new ArrayList<String>();
array.add("Hello");
array.add("World");
array.add("Java");
for(String s : array){
System.out.println(s);
}
LinkedList<String> linkedList = new LinkedList<String>();
linkedList.add("Hello");
linkedList.add("World");
linkedList.add("Java");
for(String s : linkedList){
System.out.println(s);
}
}
}
8.2.10 Linkedlist 集合特有功能
- public void addFirst(E e) 在该列表头插入指定的元素
- public void addLast(E e) 在指定的元素追加到此列表的末尾
- public E getFirst() 返回此列表中的第一个元素
- public E getLast() 返回此列表中的最后一个元素
- public E removeFirst() 此列表中删除并返回第一个元素
- public E removeLast() 此列表中删除并返回最后一个元素
package com.jihe.myList;
import java.util.LinkedList;
public class demo07 {
public static void main(String[] args) {
LinkedList<String> s = new LinkedList<String>();
s.add("Hello");
s.add("World");
s.add("Java");
s.addFirst("Javaee");
s.addLast("Javase");
System.out.println(s.getFirst());
System.out.println(s.getLast());
s.removeFirst();
s.removeLast();
}
}
8.3 Set
8.3.1 Set 集合的特点
- Set集合不包含重复的元素
- 没有带索引的方法,所以不能使用普通的for循环进行遍历
package com.jihe.set;
import java.util.HashSet;
import java.util.Set;
public class demo01 {
public static void main(String[] args) {
//创建集合对象
//HashSet堆集合的顺序不做任何保证
Set<String> set = new HashSet<String>();
//添加元素
set.add("hello");
set.add("world");
set.add("java");
//不包含重复元素
set.add("java");
//遍历
for(String s : set){
System.out.println(s);
}
//world
//java
//hello
}
}
8.3.2 哈希值
哈希值:是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值
Object类中有一个方法可以获取对象的哈希值
- public int hashCode():返回对象的哈希码
package com.jihe.set.demo02;
public class TestHash {
public static void main(String[] args) {
//创建学生对象
Student s1 = new Student("JJ",18);
//同一个对象多次调用hashCode()方法返回的哈希值是相同的
System.out.println(s1.hashCode());//2129789493
System.out.println(s1.hashCode());//2129789493
//默认情况下,不同对象的哈希值是不同的
//通过方法的重写,可以实现不同对象的哈希值是相同的
Student s2 = new Student("JJ",18);
System.out.println(s2.hashCode());//668386784
System.out.println("hello".hashCode());//99162322
System.out.println("hello".hashCode());//99162322
System.out.println("java".hashCode());//;3254818
//有些特例
System.out.println("重地".hashCode());//1179395
System.out.println("通话".hashCode());//1179395
System.out.println("手机".hashCode());//806479
}
}
8.3.3 HashSet集合概述和特点
特点:
- 底层数据结构是哈希表
- 对集合的迭代顺序不坐任何保证,也即是说不保证存储和取出的元素顺序是一致的
- 没有带索引的方法,所以不能使用普通的for循环遍历
- 由于是Set集合,所以是不包含重复元素的集合
package com.jihe.set;
import java.util.HashSet;
public class demo03 {
public static void main(String[] args) {
HashSet<String> hs = new HashSet<>();
hs.add("hello");
hs.add("world");
hs.add("java");
hs.add("hello");
for(String s : hs){
System.out.println(s);
}
//world
//java
//hello
}
}
8.3.4 HashSet 集合保证元素唯一性的源码分析
HashSet<String> hs = new HashSet<>();
hs.add("liubei");
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
//1、当我们调用HashSet的add方法添加元素“liubei”时,add方法将“liubei”传给put()为形参key,
public V put(K key, V value) {
//2、put()又将hash(key)的返回值连同key本身又传给putVal()作为形参,其中hash(key)即添加元素的哈希值作为形参hash,key作为形参key。
return putVal(hash(key), key, value, false, true);
}
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {
//3、哈希表的底层为数组,所以tab是一个元素为结点的数组,此为哈希表结构的一种实现。
Node<K,V>[] tab; Node<K,V> p; int n, i;
//4、如果哈希表未初始化,就对其进行初始化
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//5、根据对象的哈希值计算对象的存储位置,如果该位置没有元素,就存储一个元素
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
/*
首先比较哈希值,存入的元素和前面的元素比较哈希值
如果哈希值相同,会继续向下执行,把元素添加到集合
如果哈希值不同,会调用对象的equals()比较
如果返回false,会继续向下执行,把元素添加到集合
如果返回true,说明元素重复,不存储
*/
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
HashSet集合添加一个元素的过程:
HashCode集合存储元素:要保证元素唯一性,需要重写hashCode()和equals()
8.3.5 哈希表
哈希表:
(1)JDK8之前,底层采用数组+链表实现,可以说是一个元素为链表的数组;
(2)JDK8以后,在长度比较长的时候,底层实现了优化。
(3)哈希表在存储数据元素保证元素唯一性的方法:HashSet的默认初始容量为16,即链表数组的长度为16。所以我们要将某个哈希值的元素存储时,将此哈希值对16取余数,每个元素都得到0-15的余数,放到对应索引的链表里面。其中放进链表前,首先要将元素的哈希值与已经存储了的所有哈希值进行比较,如果哈希值不相同,就可以存储进链表内;如果哈希值相同,就进一步比较相同两个哈希值对应的具体内容,如果具体内容也相同,则不存储该元素,反之存储该元素进入链表。
8.3.6 HashSet集合存储学生对象的案例
需求:创建一个存储学生对象的集合,存储多个学生对象,是使用程序实现在控制台上遍历集合
要求:学生对象的成员变量值相同,我们就认为是同一个对象
思路:
- 定义学生类
- 创建HashSet集合对象
- 创建学生对象
- 把学生添加到集合中
- 遍历
- 在学生类中重写两个方法:hashSet()和equals()自动生成就好
按理说HashSet中不能存储重复元素,而上述代码成功存储并且能在控制台输出,这不符合学生对象的成员变量值相同就认为是同一个对象的需求,而这是为什么呢?我们如何保证集合元素的唯一性呢?
我们知道HashSet底层数据结构为哈希表,而哈希表又依赖于HashCode()和equals()方法,所以我们的学生类要重写HashCode和equals方法。我们在Student类中Alt+Insert快捷生成即可。
package com.jihe.set.demo04;
public class Student {
private String name;
private int age;
public Student(){}
public Student(String name,int age){
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
//Generate->equals()和hashCode()->default->next
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
if (age != student.age) return false;
return name != null ? name.equals(student.name) : student.name == null;
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
return result;
}
}
package com.jihe.set.demo04;
import java.util.HashSet;
public class Test {
public static void main(String[] args) {
HashSet<Student> hs = new HashSet<Student>();
Student s1 = new Student("JJ",12);
Student s4 = new Student("JJ",12);
Student s3 = new Student("GG",16);
Student s2 = new Student("KK",18);
hs.add(s1);
hs.add(s2);
hs.add(s3);
hs.add(s4);
for(Student s : hs){
System.out.println(s.getName()+","+s.getAge());
}
}
}
8.3.6 LinkedHashSet集合概述和特点
特点:
- 哈希表和链表实现的Set接口,具有可预测的迭代顺序
- 由链表保证元素有序,也就是说元素的存储和取出的顺序是一致的
- 由哈希表保证元素的唯一性,也就是说没有重复的元素
8.3.7 TreeSet
特点:
-
元素有序,这里的有序不是指存储和取出的顺序,而是按照一定的规则进行排序,具体的排序方式取决于构造方法
TreeSet():根据其元素的自然排序进行排序(无参构造,从小到大排序)
TreeSet(Comparator comparator):根据指定的比较器进行排序
-
没有带索引的方法,所以不能使用普通的for循环遍历
-
由于是Set集合,所以不含重复的元素
package com.jihe.set;
import java.util.TreeSet;
public class demo05 {
public static void main(String[] args) {
TreeSet<Integer> ts = new TreeSet<Integer>();//不能用int,集合只能存储引用类型,基本类型换成包装类型即可
ts.add(10);
ts.add(30);
ts.add(60);
ts.add(50);
ts.add(20);
ts.add(20);
for(Integer i : ts){
System.out.println(i);
}
//10
//20
//30
//50
//60
}
}
8.3.8 自然排序Comparable的使用
需求:存储学生对象并遍历,创建TreeSet集合时使用无参构造方法。要求按照年龄从小到大排序,年龄相同时,按照姓名字母顺序排序。
注意点:
- 用TreeSet集合存储自定义对象,无参构造方法的使用是自然排序对元素进行排序的
- 自然排序,就是让元素所属的类实现Comparable接口,重写compareTo(T o)方法
- 重写方法时,一定要注意排序的规则必须按照主要条件和次要条件来写
package com.jihe.set.demo06;
public class Student implements Comparable<Student>{
private String name;
private int age;
public Student(){}
public Student(String name,int age){
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public int compareTo(Student s) {
//return 0;返回0时,说明都相等,即元素是重复的
//return 1;从小到大排序
//return -1;从大到小排序
int num = this.age - s.age;//从小到大排序
//int num = s.age - this.age;从大到小
int num2 = num==0?this.name.compareTo(s.name):num;//年龄相同比较名字
return num2;
}
}
package com.jihe.set.demo06;
import java.util.TreeSet;
public class Test {
public static void main(String[] args) {
TreeSet<Student> ts = new TreeSet<>();
Student s1 = new Student("JJ",18);
Student s2 = new Student("SS",28);
Student s3 = new Student("KK",26);
Student s4 = new Student("PP",18);
ts.add(s1);
ts.add(s2);
ts.add(s3);
ts.add(s4);
for(Student s : ts){
System.out.println(s.getName() + "," + s.getAge());
}
}
}
8.3.9 比较器排序Comparable
package com.jihe.set.demo07;
public class Student {
private String name;
private int age;
public Student(){}
public Student(String name,int age){
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
package com.jihe.set.demo07;
import com.jihe.set.demo07.Student;
import java.util.Comparator;
import java.util.TreeSet;
public class Test {
public static void main(String[] args) {
TreeSet<Student> ts = new TreeSet<>(new Comparator<Student>() {//匿名内部类
@Override
public int compare(Student s1, Student s2) {
int num = s1.getAge() - s2.getAge();//从小到大排序
//int num = this.age - s.age;
int num2 = num==0? s1.getName() .compareTo(s2.getName()):num;//年龄相同比较名字
return num2;
}
});
Student s1 = new Student("JJ",18);
Student s2 = new Student("SS",28);
Student s3 = new Student("KK",26);
Student s4 = new Student("PP",18);
Student s5 = new Student("PP",18);
ts.add(s1);
ts.add(s2);
ts.add(s3);
ts.add(s4);
ts.add(s5);
for(Student s : ts){
System.out.println(s.getName() + "," + s.getAge());
}
}
}
结论:
- 用TreeSet集合存储自定义对象,带参构造方法使用的时比较器排序对元素进行排序的
- 比较器排序,就是让集合的构造方法接收Comparator的实现类对象,重写compare(T o1,T o2)
8.3.10 成绩排序的案例
- 需求:用TreeSet集合存储多个学生信息(姓名,语文成绩,数学成),并遍历该集合。要求按照总分从高到低出现。
package com.jihe.set.demo08;
public class Student {
private String name;
private int math;
private int chinese;
public Student(String name,int math,int chinese){
this.math = math;
this.chinese = chinese;
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getMath() {
return math;
}
public void setMath(int math) {
this.math = math;
}
public int getChinese() {
return chinese;
}
public void setChinese(int chinese) {
this.chinese = chinese;
}
public int getSum(){
return this.math+this.chinese;
}
}
package com.jihe.set.demo08;
import java.util.Comparator;
import java.util.TreeSet;
public class Test {
public static void main(String[] args) {
TreeSet<Student> ts = new TreeSet<>(new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
//主要条件
int num1 = s2.getSum()-s1.getSum();
//次要条件
int num2 = num1==0?s1.getChinese()- s2.getChinese():num1;
int num3 = num2==0?s1.getName().compareTo(s2.getName()):num2;
return num3;
}
});
Student s1 = new Student("JJ",99,98);
Student s2 = new Student("GJ",56,89);
Student s3 = new Student("HJ",89,65);
Student s4 = new Student("TY",89,98);
Student s5 = new Student("DD",89,98);
ts.add(s1);
ts.add(s2);
ts.add(s3);
ts.add(s4);
ts.add(s5);
for(Student s : ts){
System.out.println(s.getName()+","+s.getChinese()+","+s.getMath()+","+s.getSum());
}
}
}
8.4 泛型
8.4.1 泛型概念
泛型类的定义格式:
-
格式:修饰符 class 类名 <类型>{ }
-
public class Generic { }
此处的T可以随便写为任意的标识,常见的如:T、E、K、V等形式的参数都表示泛型
它的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
package com.jihe.deneric.demo01;
public class Student {
private Integer age;
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
package com.jihe.deneric.demo01;
public class Teacher {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package com.jihe.deneric.demo01;
public class Generic<T> {
private T t;
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
}
package com.jihe.deneric.demo01;
public class Test {
public static void main(String[] args) {
Student s = new Student();
s.setAge(10);
System.out.println(s.getAge());
Teacher t = new Teacher();
t.setName("JJ");
System.out.println(t.getName());
Generic<Integer> g1 = new Generic<Integer>();
g1.setT(12);
System.out.println(g1.getT());
Generic<String> g2 = new Generic<>();
g2.setT("KK");
System.out.println(g2.getT());
}
}
//10
//JJ
//12
//KK
8.4.2 泛型方法
package com.jihe.deneric.demo02;
public class Generic {
public <T> void show(T t){
System.out.println(t);
}
}
package com.jihe.deneric.demo02;
public class Test {
public static void main(String[] args) {
Generic g = new Generic();//简化
g.show("JJ");
g.show(5);
g.show(true);
}
}
/*
public static void main(String[] args) {
Generic<String> g1 = new Generic<>();
g1.show("liubei");
Generic<Integer> g2 = new Generic<>();
g2.show(30);
Generic<Boolean> g3 = new Generic<>();
g3.show(true);
}
*/
8.4.3 泛型接口
格式:
- 修饰符 interface 接口名 <类型> { }
- 范例:public interface Generic { }
package com.jihe.deneric.demo03;
public interface Generic<T> {
void show(T t);
}
package com.jihe.deneric.demo03;
public class GenericImpl<T> implements Generic<T>{
@Override
public void show(T t) {
System.out.println(t);
}
}
package com.jihe.deneric.demo03;
public class Test {
public static void main(String[] args) {
Generic<String> g0 = new GenericImpl<>();
g0.show("JJ");
Generic<Integer> g1 = new GenericImpl<Integer>();
g1.show(2);
}
}
8.4.4 类型通配符
为了表示各种泛型List的父类,可以使用类型通配符
- 类型通配符:<?>
- List<?> : 表示元素类型未知的List,它的元素可以匹配到任何的类型
- 这种类型通配符的List仅仅表示它是各种泛型List的父类,并不能把元素添加到其中
如果我们不希望List<?>是任何泛型List的父类,希望它代表某一类泛型List的父类,可以使用类型通配符的上限
- 类型通配符的上限:<? extends 类型>
- List<? extends Number>:它表示的类型是Number或者其子类
除了可以指定类型通配符的上限,我们也可以指定类型通配符的下限
- 类型通配符的下限:<? super 类型>
- List <? super Number>:它表示的类型是Number或者其父类型
public static void main(String[] args) {
//首先我们要知道Integer的父类为Number,Number的父类为Object
//类型通配符:<?>
List<?> list1 = new ArrayList<Object>();
List<?> list2 = new ArrayList<Number>();
List<?> list3 = new ArrayList<Integer>();
//类型通配符上限:<? extends 类型>
//List<? extends Number> list4 = new ArrayList<Object>();//报错
List<? extends Number> list5 = new ArrayList<Number>();
List<? extends Number> list6 = new ArrayList<Integer>();
//类型通配符下限:<? super 类型>
List<? super Number> list7 = new ArrayList<Object>();
List<? super Number> list8 = new ArrayList<Number>();
//List<? super Number> list9 = new ArrayList<Integer>();//报错
}
8.4.5 可变参数
可变参数又称为参数个数可变,用作方法的形参出现,那么这个方法参数个数就是可变的
- 格式:修饰符 返回值类型 方法名 (数据类型… 变量名){}
可变参数的注意事项:
- 这里的变量名其实是一个数组
- 如果一个方法有多个参数,包含可变参数,那么可变参数要发在最后
package com.jihe.deneric.demo05;
public class Test {
public static void main(String[] args) {
System.out.println(sum(10,20));
System.out.println(sum(10,20,89,13));
System.out.println(sum(10,20,45,89));
System.out.println(sum(10,20,89,78,12,45,56));
}
public static int sum(int b,int... a){
int sum = b;
for(int i : a){
sum += i;
}
return sum;
}
}
8.4.5 可变参数的使用
Arrays工具中由一个静态方法:
public static List asList(T… a)
Arrays工具类中有一个静态方法:
(1)public static List asList(T…a):返回由指定数组支持的固定大小的列表;
(2)返回的集合不能做增删操作,可以做修改操作。
List接口中有一个静态方法(JDK9以后):
(1)public static List of(E…elements):返回包含任意数量元素的不可变列表;
(2)返回的集合不能做增删改操作。
Set接口中有一个静态方法(JDK9以后):
(1)public static Set of(E…elements):返回一个包含任意数量元素的不可变集合;
(2)返回的集合不能做增删操作,没有修改的方法。
public static void main(String[] args) {
List<String> list = Arrays.asList("liubei","guanyu","zhangfei");
//list.add("zhugeliang");//UnsupportedOperationException
//list.remove("liubei");//UnsupportedOperationException
list.set(1,"zhugeliang");
System.out.println(list);//[liubei, zhugeliang, zhangfei]
//List<String> list2 = list.of("liubei","guanyu","zhangfei");
//list2.add("zhugeliang");//UnsupportedOperationException
//list2.remove("liubei");//UnsupportedOperationException
//list2.set(1,"zhugeliang");//UnsupportedOperationException
//System.out.println(list2);
//Set<String> set = Set.of("liubei","guanyu","zhangfei");
//set.add("zhugeliang");//UnsupportedOperationException
//set.remove("liubei");//UnsupportedOperationException
//System.out.println(set);
}
8.5 Map
8.5.1 Map集合的概述和使用
- Interface Map <K,V> K:键的类型 V:值的类型
- 将键映射到值的对象;不能包含重复的元素;每个键可以映射到最多一个值
- 举例:学生姓名和学号
创建Map集合的对象
- 多态的方式
- 具体的实现类HashMap
package com.map.demo01;
import java.util.HashMap;
import java.util.Map;
public class Test {
public static void main(String[] args) {
Map<Integer,String> map = new HashMap<Integer,String>();
//V put(K key , V value)将指定的值与该映射中的指定键相关联,当key相同时,相当于修改数据
map.put(12,"JJ");
map.put(13,"DD");
map.put(14,"HH");
map.put(12,"JJ");
System.out.println(map);//重写了toString方法
//{12=JJ, 13=DD, 14=HH}
}
}
8.5.2 Map集合的基本功能
方法名 | 说明 |
---|---|
V put (K key , V value) | 添加元素 |
V remove (Object key) | 根据键删除键值对元素 |
void clear() | 移除所有的键值对元素 |
boolean containKey (Object key ) | 判断集合是否包含指定的键 |
boolean containValue (Object value) | 判断集合是否包含指定的值 |
boolean isEmpty() | 判断集合是否为空 |
int size() | 集合的长度,也就是集合中键值对的个数 |
package com.map.demo02;
import java.util.HashMap;
import java.util.Map;
public class Test {
public static void main(String[] args) {
Map<String,String> map = new HashMap<String,String>();
map.put("01","JJ");
map.put("02","KK");
map.put("03","LL");
System.out.println(map.remove("01"));//JJ
//map.clear();
System.out.println(map.containsKey("01"));//false
System.out.println(map.containsValue("KK"));//true
System.out.println(map.isEmpty());//false
System.out.println(map.size());//2
System.out.println(map);//{02=KK, 03=LL}
}
}
8.5.3 Map集合的获取功能
方法名 | 说明 |
---|---|
V get (Object key) | 根据键获取值 |
Set keySet() | 获取所有键的集合 |
Collection values | 获取所有值的集合 |
Set <Map.Entry<K,V>>entrySet() | 获取所有键值对对象的集合 |
package com.map.demo03;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class Test {
public static void main(String[] args) {
Map<String,String> map = new HashMap<String,String>();
map.put("01","JJ");
map.put("02","KK");
map.put("03","LL");
System.out.println(map.get("01"));//JJ
System.out.println(map.get("04"));//null
Set<String> keySet = map.keySet();
for(String s : keySet){
System.out.println(s);
}
//01
//02
//03
Collection<String> values = map.values();
for(String l : values){
System.out.println(l);
}
//JJ
//KK
//LL
}
}
8.5.4 Map集合的遍历
方法一;
- 获取所有键的集合,用keySet()方法实现
- 遍历键的集合,获取到每一个键,用增强for实现
- 根据键去找值,用get(Object key )去实现
package com.map.demo04;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class Test {
public static void main(String[] args) {
Map<String,String> map = new HashMap<String,String>();
map.put("jj","JJ");
map.put("kk","KK");
map.put("ll","LL");
Set<String> keySet = map.keySet();
for(String s : keySet){
String value = map.get(s);
System.out.println(s + "," + value);
}
//jj,JJ
//kk,KK
//ll,LL
}
}
方法二:
- 获取所有键值对对象集合:Set <Map.Entry<K,V>>entrySet()
- 遍历键值对对象集合,得到每一个键值对对象:用增强for实现,得到每一个Map.Entry
- 根据键值对对象获取键和值
package com.map.demo04;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class Test02 {
public static void main(String[] args) {
Map<String,String> map = new HashMap<String,String>();
map.put("jj","JJ");
map.put("kk","KK");
map.put("ll","LL");
Set<Map.Entry<String,String>> entrySet = map.entrySet();
for(Map.Entry<String,String> me : entrySet){
String key = me.getKey();
String value = me.getValue();
System.out.println(key + "," +value);
}
//jj,JJ
//kk,KK
//ll,LL
}
}
8.5.5 HashMap集合案例
需求:创建一个HashMap集合,键是学生类(Student),值是居住地(String)。存储多个键值对元素,并遍历。要求保证键的唯一性:如果学生对象的成员变量值相同,就认为是同一个对象
思路:
- 定义学生类
- 创建HashMap集合对象
- 创建学生对象
- 把学生添加到集合中
- 遍历集合
- 学生类中重写方法:hashCode();equals();
package com.map.demo05;
public class Student {
private String name;
private int age;
public Student(){}
public Student(String name,int age){
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
if (age != student.age) return false;
return name != null ? name.equals(student.name) : student.name == null;
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
return result;
}
}
package com.map.demo05;
import java.util.HashMap;
import java.util.Set;
public class Test {
public static void main(String[] args) {
HashMap<Student,String> hm = new HashMap<Student,String>();
Student s1 = new Student("JJ",18);
Student s2 = new Student("DD",89);
Student s3 = new Student("HH",12);
Student s4 = new Student("LL",66);
Student s5 = new Student("LL",66);
hm.put(s1,"广州");
hm.put(s2,"郑州");
hm.put(s3,"江西");
hm.put(s4,"迪拜");
hm.put(s5,"巴黎");
Set<Student> keySet = hm.keySet();
for(Student key : keySet){
String value = hm.get(key);
System.out.println(key.getName()+","+key.getAge()+","+value);
}
}
}
8.5.6 ArrayList集合存储HashMap元素并遍历
创建一个ArrayList集合,存储三个元素,每一个元素都是HashMap,每一个HashMap的键和值都是String,并遍历。
思路:
(1)创建ArrayList集合;
(2)创建HashMap集合,并添加键值对元素;
(3)把HashMap作为元素添加到ArrayList集合;
(4)遍历ArrayList集合。
package com.map.demo06;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Set;
public class Test {
public static void main(String[] args) {
ArrayList<HashMap<String,String>> array = new ArrayList<HashMap<String,String>>();
HashMap<String,String> hm1 = new HashMap<>();
hm1.put("孙策","大乔");
hm1.put("周瑜","小乔");
array.add(hm1);
HashMap<String,String> hm2 = new HashMap<>();
hm2.put("郭靖","黄蓉");
hm2.put("杨过","小龙女");
array.add(hm2);
HashMap<String,String> hm3 = new HashMap<>();
hm3.put("令狐冲","任盈盈");
hm3.put("林平之","岳灵珊");
array.add(hm3);
for(HashMap<String,String> hm : array){
Set<String> keySet = hm.keySet();
for(String key : keySet){
String value = hm.get(key);
System.out.println(key + "," + value);
}
}
//孙策,大乔
//周瑜,小乔
//杨过,小龙女
//郭靖,黄蓉
//令狐冲,任盈盈
//林平之,岳灵珊
}
}
8.5.7 HashMap集合存储ArrayList元素并遍历
创建一个HashMap集合,存储三个键值对元素,每一个键值对元素的键是String,值是ArrayList,每一个ArrayList的元素是String,并遍历。
思路:
(1)创建HashMap集合;
(2)创建ArrayList集合,并添加元素;
(3)把ArrayList作为元素添加到HashMap集合;
(4)遍历HashMap集合。
package com.map.demo07;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Set;
public class Test {
public static void main(String[] args) {
HashMap<String, ArrayList<String>> hm = new HashMap<String,ArrayList<String>>();
ArrayList<String> sgyy = new ArrayList<String>();
sgyy.add("诸葛亮");
sgyy.add("赵云");
hm.put("三国演义",sgyy);
ArrayList<String> xyj = new ArrayList<String>();
xyj.add("孙悟空");
xyj.add("唐僧");
hm.put("西游记",xyj);
ArrayList<String> shz = new ArrayList<String>();
shz.add("武松");
shz.add("鲁智深");
hm.put("水浒传",shz);
Set<String> keySet = hm.keySet();
for(String key : keySet){
System.out.println(key);
ArrayList<String> value = hm.get(key);
for(String s : value){
System.out.println("\t"+s);
}
}
//水浒传
// 武松
// 鲁智深
//三国演义
// 诸葛亮
// 赵云
//西游记
// 孙悟空
// 唐僧
}
}
8.5.8 统计字符串中每个字符出现的次数
需求:键盘录入一个字符串,要求统计字符串中每个字符串出现的次数。
举例:键盘录入“aababcabcdabcde”,在控制台输出:“a(5)b(4)c(3)d(2)e(1)”。
分析:
(1)我们可以把结果分成几个部分来看:a(5),b(4),c(3),d(2),e(1);
(2)每一个部分可以看成是:字符和对应的次数组成;
(3)这样的数据,我们就可以通过HashMap集合来存储,键是字符,值是字符出现的次数;
(4)要注意的是,键是字符,类型应该是Character;值是字符出现的次数,类型应该是Integer。
思路:
(1)键盘录入一个字符串;
(2)创建HashMap集合,键是Character,值是Integer;
(3)遍历字符串,得到每一个字符;
(4)拿得到的每一个字符作为键到HashMap集合中去找对应的值,看其返回值。如果返回值是null,说明该字符在HashMap集合中不存在,就把该字符作为键,1作为值存储。如果返回值不是null,说明该字符在HashMap集合中存在,把该值加1,然后重新存储该字符串和对应的值;
(5)遍历HashMap集合,得到键和值,按照要求进行拼接;
(6)输出结果。
package com.map.demo08;
import java.util.HashMap;
import java.util.Scanner;
import java.util.Set;
import java.util.TreeMap;
public class Test {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入一个字符串:");
String line = sc.nextLine();
//HashMap<Character,Integer> hm = new HashMap<Character,Integer>();无序排列
TreeMap<Character,Integer> hm = new TreeMap<Character,Integer>();//有序排列
for(int i = 0; i < line.length(); i++){
char key = line.charAt(i);
Integer value = hm.get(key);
if(value == null){
hm.put(key,1);
}else{
value++;
hm.put(key,value);
}
}
StringBuilder sb = new StringBuilder();
Set<Character> keySet = hm.keySet();
for(Character key : keySet){
Integer value = hm.get(key);
sb.append(key).append("(").append(value).append(")");
}
String result = sb.toString();
System.out.println(result);
//jjjkklloo
//j(3)k(2)l(2)o(2)
}
}
8.6 Collections
8.6.1 Collections集合概述和使用
概述:Collections是针对集合操作的工具类
Collections类常用的方法:
- public static <T extends Comparable<? super T>> void sort (List list) :将指定的列表按升序排序
- public static void reverse(List<?> list):反转指定列表中元素的顺序
- public static void shuffle(List<?> lsit):使用默认的随机源随机排列指定的列表
package com.jihe.collections.demo01;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
public class Test {
public static void main(String[] args) {
List<Integer > list = new ArrayList<>();
list.add(10);
list.add(60);
list.add(40);
list.add(50);
list.add(30);
list.add(20);
Collections.sort(list);
System.out.println(list);
//[10, 20, 30, 40, 50, 60]
Collections.reverse(list);
System.out.println(list);
//[60, 50, 40, 30, 20, 10]
Collections.shuffle(list);
System.out.println(list);
//[40, 60, 50, 20, 30, 10]
}
}
8.6.2 ArrayList存储学生对象并排序
要求:按照年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序
思路:
(1)定义学生类;
(2)创建ArrayList集合;
(3)创建学生对象;
(4)把学生对象添加到集合;
(5)使用Collections对ArrayList集合排序;
(6)遍历集合。
package com.jihe.collections.demo02;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
public class Test {
public static void main(String[] args) {
ArrayList<Student> array = new ArrayList<Student>();
Student s1 = new Student("JJ",18);
Student s2 = new Student("KK",89);
Student s3 = new Student("HH",45);
Student s4 = new Student("DD",12);
Student s5 = new Student("PP",12);
array.add(s1);
array.add(s2);
array.add(s3);
array.add(s4);
array.add(s5);
Collections.sort(array, new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
int num = s1.getAge() - s2.getAge();
int num1 = num == 0? s1.getName() .compareTo(s2.getName()) : num;
return num1;
}
});
for(Student s : array){
System.out.println(s.getName()+ "," + s.getAge());
}
//DD,12
//PP,12
//JJ,18
//HH,45
//KK,89
}
}
8.6.3 斗地主
需求:通过程序实现斗地主中的洗牌,发牌和看牌
思路:
- 创建一个牌盒,也就是定义一个集合对象,用ArrayList集合实现
- 往牌盒里面装牌
- 洗牌,也就是把牌打散,用Collections的shuffle()方法实现
- 发牌,也就是遍历集合,给三个玩家发牌
- 看牌,也就是三个玩家分别遍历自己的牌
package com.jihe.collections.demo03;
import java.util.ArrayList;
import java.util.Collections;
public class Test01 {
public static void main(String[] args) {
//牌盒
ArrayList<String> array = new ArrayList<String>();
//♦2,♦3,♦4.....♦A,小王,大王
//定义花色数组
String[] colors = {"♦","♣","♥","♠"};
//定义点数数组
String[] numbers = {"2","3","4","5","6","7","8","9","J","Q","K","A"};
//拼接:♦2,♦3,♦4.....♦A,小王,大王
for(String color : colors){
for(String number : numbers){
array.add(color+number);
}
}
array.add("小王");
array.add("大王");
//洗牌
Collections.shuffle(array);
//发牌
ArrayList<String> user0 = new ArrayList<String>();
ArrayList<String> user1 = new ArrayList<String>();
ArrayList<String> user2 = new ArrayList<String>();
ArrayList<String> dipai = new ArrayList<String>();
for (int i = 0; i < array.size(); i++) {
String poker = array.get(i);
if(i >= array.size()-3){
dipai.add(poker);
}else if(i % 3 == 0){
user0.add(poker);
}else if(i % 3 == 1){
user1.add(poker);
}else if(i % 3 == 2){
user2.add(poker);
}
}
//看牌
lookPoker("林青霞",user0);
lookPoker("柳岩",user1);
lookPoker("风清扬",user2);
lookPoker("底牌",dipai);
//林青霞的牌是:♣3 ♥3 ♠2 ♣6 ♥4 大王 ♥J ♠6 ♥7 ♣2 ♣9 ♠4 ♥Q ♦A ♠7 ♣J
//柳岩的牌是:♠5 ♦7 ♦8 ♦K ♥A ♠9 ♦4 ♥K ♦3 ♠Q ♦9 ♥6 小王 ♣4 ♦J ♥8
//风清扬的牌是:♠A ♥5 ♦5 ♣A ♠K ♣5 ♠3 ♣Q ♦6 ♣8 ♦2 ♥9 ♠8 ♣K ♠J
//底牌的牌是:♦Q ♥2 ♣7
}
//看牌的方法
public static void lookPoker(String name,ArrayList<String> array){
System.out.print(name+"的牌是:");
for(String poker : array){
System.out.print(poker+" ");
}
System.out.println();
}
}
升级版:
思路:
(1)创建HashMap,键是编号,值是牌;
(2)创建ArrayList,存储编号;
(3)创建花色数组和点数数组;
(4)从0开始往HashMap里面存储编号,并存储对应的牌,同时往ArrayList里面存储编号;
(5)洗牌(洗的是编号),用Collections的shuffle()实现;
(6)发牌(发的也是编号,为了保证编号是排序的,创建TreeSet集合接收);
(7)定义方法看牌(遍历TreeSet集合,获取编号,到HashMap集合找对应的牌);
(8)调用方法看牌。
package com.jihe.collections.demo03;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.TreeSet;
public class Test02 {
public static void main(String[] args) {
//创建HashMap,键是编号,值是牌
HashMap<Integer,String> hm = new HashMap<Integer,String>();
//创建ArrayList,储存编号
ArrayList<Integer> array = new ArrayList<>();
//定义花色数组
String[] colors = {"♦","♣","♥","♠"};
//定义点数数组
String[] numbers = {"3","4","5","6","7","8","9","J","Q","K","A","2"};
//从0开始往HashMap里面储存编号,并储存对应的牌,同时往ArrayList里面储存编号
int index = 0;
for(String color : colors){
for(String number : numbers){
hm.put(index,color+number);
array.add(index);
index++;
}
}
hm.put(index,"小王");
array.add(index);
index++;
hm.put(index,"大王");
array.add(index);
//洗牌,洗的是编号,用Collections的shuffle方法来
Collections.shuffle(array);
//发牌,发的也是编号为了保证编号是排序的,创建TreeSet集合接收
TreeSet<Integer> user0 = new TreeSet<>();
TreeSet<Integer> user1 = new TreeSet<>();
TreeSet<Integer> user2 = new TreeSet<>();
TreeSet<Integer> dipai = new TreeSet<>();
for (int i = 0; i < array.size(); i++) {
int poker = array.get(i);
if(i >= array.size()-3){
dipai.add(poker);
}else if(i % 3 == 0){
user0.add(poker);
}else if(i % 3 == 1){
user1.add(poker);
}else if(i % 3 == 2){
user2.add(poker);
}
}
//调用看牌方法
lookPoker("林青霞",user0,hm);
lookPoker("理鱼",user1,hm);
lookPoker("飞行器",user2,hm);
lookPoker("底牌",dipai,hm);
//林青霞的牌是:♦4 ♦5 ♦7 ♦A ♣4 ♣6 ♣Q ♣A ♥J ♥K ♠4 ♠7 ♠8 ♠9 ♠Q ♠2
//理鱼的牌是:♦3 ♦J ♦K ♣3 ♣7 ♣J ♣K ♥3 ♥4 ♥6 ♥9 ♥A ♠6 ♠K ♠A 大王
//飞行器的牌是:♦6 ♦8 ♦9 ♦Q ♦2 ♣8 ♣2 ♥5 ♥8 ♥Q ♥2 ♠3 ♠5 ♠J 小王
//底牌的牌是:♣5 ♣9 ♥7
}
public static void lookPoker(String name,TreeSet<Integer> ts,HashMap<Integer,String> hm){
System.out.print(name + "的牌是:");
for(Integer key : ts){
String poker = hm.get(key);
System.out.print(poker+" ");
}
System.out.println();
}
}
九 IO流
9.1 File
9.1.1 File类概述和构造方法
File:它是文件和目录路径名的抽象表示
- 文件和目录是可以通过File封装成对象的
- 对于File而言,其封装的并不是一个真正的文件,仅仅是一个路径而已,它是可以存在的,也可以是不存在的,将来是要通过具体的操作把这个路径的内容转换成具体存在的
方法名 | 说明 |
---|---|
File(String pathname) | 通过将给定的路径名字符串转换成抽象路径名来创建新的File实例 |
File(String parent, String child) | 从父路径名字符串和子路径名字符串创建新的File实例 |
File(File parent, String child) | 从父抽象路径名和子路径名字符串创建新的File实例 |
package com.io.file.demo01;
import java.io.File;
public class Test {
public static void main(String[] args) {
File f1 = new File("E:\\it\\java.txt");
System.out.println(f1);//E:\it\java.txt,重写了toString的方法
File f2 = new File("E:\\it","java.txt");
System.out.println(f2);//E:\it\java.txt
File f3 = new File("E:\\it");
File f4 = new File(f3,"java.txt");
System.out.println(f4);//E:\it\java.txt
}
}
9.1.2 File类创建功能
方法名 | 说明 |
---|---|
public boolean creatNewFile() | 当具有该名称的文件不不存在时,创建一个由该抽象路径命名的新空文件 |
public boolean mkdir() | 创建由此抽象路径命名的目录 |
public boolean mkdirs() | 创建由此抽象路径名命名的目录,包括任何必须但不存在的父目录 |
package com.io.file.demo02;
import java.io.File;
import java.io.IOException;
public class Test {
public static void main(String[] args) throws IOException {
//在 D:\Users\JJ\Desktop\笔记\测试01 目录下创建一个文件java.txt
File f1 = new File("D:\\Users\\JJ\\Desktop\\笔记\\测试01\\java.txt");
System.out.println(f1.createNewFile());//文件不存在就创建文件,并返回true,如果文件存在就返回false
System.out.println("-----------");
//在 D:\Users\JJ\Desktop\笔记\测试01 目录下创建一个目录即文件夹JavaSE
File f2 = new File("D:\\Users\\JJ\\Desktop\\笔记\\测试01\\JavaSE");
System.out.println(f2.mkdir());
System.out.println("-----------");
//在 D:\Users\JJ\Desktop\笔记\测试01 目录下创建一个多级目录JavaWEB\\HTML
File f3 = new File("D:\\Users\\JJ\\Desktop\\笔记\\测试01\\JavaWEB\\HTML");
System.out.println(f3.mkdirs());//不能使用mkdir,mkdir()不可以创建多级目录
System.out.println("----------");
//在 D:\Users\JJ\Desktop\笔记\测试01 目录下创建一个文件javase.txt
File f4 = new File("D:\\Users\\JJ\\Desktop\\笔记\\测试01\\java.txt");
System.out.println(f4.mkdir());//文件不存在就创建文件,并返回true
System.out.println("-----------");
//注意调用的方法是否正确
}
}
9.1.3 File类判断和获取功能
方法名 | 说明 |
---|---|
public boolean isDirectory() | 测试此抽象路径名表示的File是否为目录 |
public boolean isFile() | 测试此抽象路径名表示的FIle是否为文件 |
public boolean exists() | 测试此抽象路径名表示的FIle是否存在 |
public String getAbsolutePath() | 返回此抽象路径名的绝对路径字符串 |
pubilc String getPath() | 将此抽象路径名的绝对转换成路径名字符串 |
public String getName() | 返回此抽象路径名表示的文件或目录的名称 |
public String[] list() | 返回此抽象路径名表示的目录中的文件和目录的名称字符串数组 |
public File[] listFiles() | 返回此抽象路径名表示的目录中的文件和目录的File对象数组 |
9.1.4 File类的删除功能
方法名 | 说明 |
---|---|
public boolean delete() | 删除由此抽象路径名表示的文件或者目录 |
绝对路径和相对路径区别:
- 绝对路径:完整的路径名,不需要其他任何的信息就可以定位到它代表的文件。例如:D:\Users\JJ\Desktop\笔记\测试01\java.txt
- 相对路径:必须使用取其他路径名的信息进行解释,例如:基础语法\java01.txt,此时就借助了基础语法的路径
package com.io.file.demo04;
import java.io.File;
import java.io.IOException;
public class Test {
public static void main(String[] args) throws IOException {
//在当前的模块目录下创建java01.txt文件
File f1 = new File("基础语法\\java01.txt");
System.out.println(f1.createNewFile());
//删除当前模块下的java.txt文件
System.out.println(f1.delete());
//在当前模块下创建it01目录
File f2 = new File("基础语法\\it01");
System.out.println(f2.mkdir());
//删除当前模块目录下的it01目录
System.out.println(f2.delete());
//在当前模块下创建it01目录,并且在it01目录下创建java02.txt文件
File f3 = new File("基础语法\\it02");
System.out.println(f3.mkdir());
File f4 = new File("基础语法\\it02\\java02.txt");
System.out.println(f4.createNewFile());
System.out.println(f4.delete());
System.out.println(f3.delete());
}
}
删除目录时应该要注意:如果一个目录有内容(目录,文件),不能直接删除,应该删除目录中的其他内容,最后才能删除目录中的内容
9.1.5 递归
遍历目录:
需求:给定一个路径,请通过递归完成遍历该目录下的所有内容,并把所有文件的绝对路径输出到控制台上
思路:
-
根据一个给定的路径创建一个File对象
-
定义一个方法,用于获取给定的目录下的所有内容,参数为第一步创建的File对象
-
获取给定的File目录下的所有的文件或者目录的File数组
-
遍历该File数组,得到每一个FIle对象
-
判断该File对象是否时目录
是:递归调用
不是:获取绝对路径输出到控制台上
-
调用方法
package com.io.file.demo05;
import java.io.File;
public class Test {
public static void main(String[] args) {
File srcFile = new File("D:\\Users\\JJ\\Desktop\\笔记\\测试01");
//调用方法
getAllFilePath(srcFile);
}
public static void getAllFilePath(File srcFile){
//获取给定的File目录下的所有的文件或者目录的File数组
File[] fileArray = srcFile.listFiles();
if(fileArray != null){
for (File file : fileArray){
if(file.isDirectory()){
getAllFilePath(file);
}else{
System.out.println(file.getAbsolutePath());
}
}
}
}
}
9.2 字节流
9.2.1 IO流概述和分类
IO流概述:
- IO:输入/输出(Input/Output)
- 流:是一种抽象概念,是对数据传输的总称,也就是说数据在设备之间的传输称为流,流的本质是数据传输
- IO流就是用来处理设备之间数据传输问题的,常见的应用有:文件复制、文件上传、文件下载
IO流分类:
-
按数据的流向
输入流:读数据
输出流:写数据
-
按数据类型分
字节流:字节输入流、字节输出流
字符流:字符输入流、字符输出流
一般来说,我们说IO流的分类是按数据类型来分的
那么我们什么情况下要使用这两种流呢?
如果数据通过Window自带的记事本软件打开,我们还可以读懂里面的内容,就用字符流,否则就使用字节流。如果不清楚,那么就用字节流(字节流是万能的流)
9.2.2 字节流写数据
字节流抽象类:
- InputStream:这个抽象类是表示字节输入流所有类的超类
- OutputStream:这个抽象类是表示字节输出流的所有类的超类
- 子类名特点:子类名称都是以其父类名作为子类名的后缀
FileOutoutStream:文件输出流用于将数据写入到File
- FileOutputStream(String name):创建文件输出流以指定的名称写入文件
package com.io.file.demo06;
import java.io.FileOutputStream;
import java.io.IOException;
public class Test {
public static void main(String[] args) throws IOException {
//创建字节流输出对象
//FileOutputStream(String name) 创建文件输出流以指定的名称写入文件
FileOutputStream fos = new FileOutputStream("基础语法\\fos.txt");
/*
做了三件事:
调用系统功能创建文件
创建了字节输出对象
让字节输出流对象指向创建好的文件
*/
//void write(int b) 将指定的字节写入到此文件输出流
fos.write(97);
//关闭此文件输出流并释放与此输出流相关联的任何系统资源
fos.close();
}
}
使用字节输出流写数据的步骤:
- 创建字节输出流对象(调用了系统功能创建了文件,创建字节输出流对象,让字节流对象指向文件)
- 调用字节输出流对象的写数据方法
- 释放资源(关闭此文件输出流并释放与此输出流相关联的任何系统资源)
9.2.3 字节流写数据的3种方式
方法名 | 说明 |
---|---|
void write(int b) | 将指定的字节流写入此文件,一次写一个字节数据 |
void write(byte[] b) | 将b.length字节从指定的字节数组写入此文件,一次写一个字节数组的数据 |
void write(byte[] b,int off,int len) | 将len字节从指定的字节数组开始,从偏移量off开始写入此此文件输入流,一次写一个字节数组的部分数据 |
package com.io.byteStream.demo02;
import java.io.FileOutputStream;
import java.io.IOException;
public class Test {
public static void main(String[] args) throws IOException {
//创建文件输出流以指定的名称写入到文件
FileOutputStream fos = new FileOutputStream("基础语法\\fos.txt");
//等价于:FileOutputStream fos = new FileOutputStream(new File("基础语法\\fos.txt"));
//等价于:File file = new File("基础语法\\fos.txt");
//FileOutputStream fos = new FileOutputStream(file);
fos.write(97);
fos.write(98);
fos.write(99);
fos.write(100);
//byte[] getBytes():返回字符串对应的字节数组
byte[] bys = "ghkj".getBytes();
fos.write(bys);
byte[] bys01 = "jjkkkll".getBytes();
fos.write(bys01,1,3);
}
}
9.2.4 字节流写数据的两个小问题
字节流写数据如何实现换行?
写数据后,加换行符:
windows:\r\n
linux:\n
mac:\r
字节流写数据如何实现追加 写入呢?
public FileOutputStream(String name,boolean apppend),创建文件输出流以指定的名称写入文件,如果第二个参数为true,则字节写入文件的末尾而不是开头
package com.io.byteStream.demo03;
import java.io.FileOutputStream;
import java.io.IOException;
public class Test {
public static void main(String[] args) throws IOException {
//FileOutputStream fos = new FileOutputStream("基础语法\\fos.txt");
FileOutputStream fos = new FileOutputStream("基础语法\\fos.txt",true);
for (int i = 0; i < 5; i++) {
fos.write("hello".getBytes());
fos.write("\r\n".getBytes());
}
fos.close();
}
}
9.2.5 字节流写数据加入异常处理
finally:
(1)在异常处理时提供finally块来执行所有清除操作,比如IO流中的释放资源;
(2)特点:被finally控制的语句一定会执行,除非JVM退出
package com.io.byteStream.demo04;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class Test {
public static void main(String[] args) {
FileOutputStream fos = null;
try {
fos = new FileOutputStream("基础语法\\fos.txt");
fos.write("JJ".getBytes());
} catch (IOException e) {
e.printStackTrace();
}finally {
if(fos!=null){//不进行判断的话,如果fos是空的,即路径不存在,那么就会报空指针异常的错误
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
9.2.6 字节流读数据
FileInputStream:从文件系统中的文件获取字节流
- FileInputStream(String name):通过打开与实际文件的链接来创建一个FileInputStream,该文件由文件系统中的路径名name命名
使用字节输入读数据的步骤:
- 创建字节输入流对象
- 调用字节输入流对象的读数据方法
- 释放资源
package com.io.byteStream.demo05;
import java.io.FileInputStream;
import java.io.IOException;
public class Test {
public static void main(String[] args) throws IOException {
FileInputStream fos = new FileInputStream("基础语法\\fos.txt");
int by;
//int read():从该输入流读取一个字节的数据
while((by=fos.read())!=-1){//文件到达末尾返回值是-1
System.out.print((char)by);//不要使用println,因为要保持和被读文件一样的行数
}
fos.close();
}
}
9.2.7 复制文本文件
思路:
- 根据数据源创建字节输入流对象
- 根据目的地创建字节输出流对象
- 读写数据,复制文本文件(一次读取一个字节,一次写入一个字节)
- 释放资源
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("E:\\itcast\\窗里窗外.txt");
FileOutputStream fos = new FileOutputStream("窗里窗外.txt",true);
int by;
while ((by = fis.read()) != -1){
fos.write(by);
}
fis.close();
fos.close();
}
9.2.8 字节流读数据(一此读一个字节数组数据)
使用字节输入流读数据的步骤
- 创建字节输入流对象
- 调用字节输入流对象的读数据方法
- 释放数据
package com.io.byteStream.demo07;
import java.io.FileInputStream;
import java.io.IOException;
public class Test {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("基础语法\\fos.txt");
byte[] bys = new byte[1024];//1024及其整数倍
int len;
while((len=fis.read(bys))!= -1){//fis.read(bys)从fis中读取数据并且返回该数据的长度,获取杯读取数据的长度,读完的时候为-1
System.out.println(new String(bys,0,len));
}
fis.close();
}
}
9.2.8 字节流复制照片
package com.io.byteStream.demo08;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class Test {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("D:\\Users\\JJ\\Pictures\\Saved Pictures\\计组1.jpg");
FileOutputStream fos = new FileOutputStream("基础语法\\picture.jpg");
byte[] bys = new byte[1024];//1024及其整数倍
int len;
while((len=fis.read(bys))!= -1){
fos.write(bys,0,len);
}
fos.close();
fis.close();
}
}
9.2.9 字节缓冲流
字节缓冲流:
- BufferOutputStream:该类实现缓冲输出流,通过设置这样的输出流,应用程序可以向底层输出流写入字节,而不必为写入的每个字节导致系统调用
- BufferedInputStream:创建BufferedInputStream将创建一个内部缓冲区数组,当从流中读取或跳过字节时,内存缓冲区将根据需要从输入流中重新填充,一次很多个字节
构造方法:
- 字节缓冲输出流:BufferedOutputStream(OutputStream out)
- 字节缓冲输入流:BufferedInputStream(InputStream in)
为什么构造方法需要的是字节流,而不是具体的文件或者路径呢?
- 字节缓冲流仅仅提供缓冲区,而真正读写数据还是得依靠基本的字节流对象进行操作
package com.io.byteStream.demo09;
import java.io.*;
public class Test {
public static void main(String[] args) throws IOException {
//FileOutputStream fos = new FileOutputStream("基础语法\\fos.txt");
//BufferedOutputStream bos = new BufferedOutputStream(fos);
//简写为:
//BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("基础语法\\fos.txt"));
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("基础语法\\fos.txt"));
byte[] bys = new byte[1024];
int len;
while((len=bis.read(bys))!=-1){
System.out.println(new String(bys,0,len));
}
}
}
9.2.10 字节流复制视频
package com.io.byteStream.demo10;
import com.oop.demo06.B;
import java.io.*;
public class Test {
public static void main(String[] args) throws IOException {
//记录开始时间
long startTime = System.currentTimeMillis();
method01();
//记录结束时间
long endTime = System.currentTimeMillis();
System.out.println("共耗时:"+(endTime-startTime) + "毫秒");
}
//基本字节流一次读写一个字节
public static void method01() throws IOException {
FileInputStream fis = new FileInputStream("");
FileOutputStream fos = new FileOutputStream("");
int by;
while((by=fis.read())!=-1){
fos.write(by);
}
fos.close();
fis.close();
}
//基本字节流一次读写一个字节数组
public static void method02() throws IOException {
FileInputStream fis = new FileInputStream("");
FileOutputStream fos = new FileOutputStream("");
byte[] bys = new byte[1024];
int len;
while((len=fis.read(bys))!=-1){
fos.write(bys,0,len);
}
fos.close();
fis.close();
}
//字节缓冲流一次读写一个字节
public static void method03() throws IOException{
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(""));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(""));
int by;
while((by=bis.read())!=-1){
bos.write(by);
}
bos.close();
bis.close();
}
//字节缓冲流一次读写一个数组
public static void method04() throws IOException{
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(""));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(""));
byte[] bys = new byte[1024];
int len;
while((len=bis.read(bys))!=-1){
bos.write(bys,0,len);
}
bos.close();
bis.close();
}
}
9.3 字符流
9.3.1 为什么会出现字符流
由于字节操作中文的时候不是特别方便,所以Java就提供字符流
- 字符流 = 字节流 + 编码表
用字节流复制文本文 件时,也有中文,但是为什么没有问题,原因时最底层操作会自动进行字节拼接成中文,如何识别中文呢?
- 汉字在存储的时候,无论选择的是哪种存储,第一个字节都是负数
package com.io.characterStream.demo01;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
public class Test {
public static void main(String[] args) throws IOException {
/*
一个汉字的存储:
如果是CBK编码,占用两个字节
如果是UTF-8存储,则占用三个字节
*/
String s0 = "abc";
String s1 = "中国";
byte[] bys0 = s1.getBytes();
byte[] bys1 = s1.getBytes("UTF-8");
byte[] bys2 = s1.getBytes("GBK");
System.out.println(s0);//abc
System.out.println(s1);//中国
System.out.println(Arrays.toString(bys0));//[-28, -72, -83, -27, -101, -67]前三个数字代表中,后面三个代表国
System.out.println(Arrays.toString(bys1));//[-28, -72, -83, -27, -101, -67]
System.out.println(Arrays.toString(bys2));//[-42, -48, -71, -6]前两个代表中
}
}
9.3.2 编码表
9.3.3 符串中的编码解码问问题
编码:
- byte[] getBytes():使用平台的默认字符集将该String编码为一系列字节,将结果存储到新的字节数组中
- byte[] getBytes(String charsetName);使用指定的字符集将该String编码为一系列字节,将结果存储到新的字节数组中
解码:
- String (byte[] bytes):使用平台默认的字符集解码指定的字节数组来构造新的String
- String (byte[] bytes,String charsetName):通过指定的字符集解码指定的字节数组来构造新的String
package com.io.characterStream.demo02;
import java.io.IOException;
public class Test {
public static void main(String[] args) throws IOException {
String s = "中国";
byte[] bys0 = s.getBytes();
byte[] bys1 = s.getBytes("UTF-8");
byte[] bys2 = s.getBytes("GBK");
String ss1 = new String(bys0);
String ss2 = new String(bys1);
String ss3 = new String(bys2);
String ss4 = new String(bys2, "GBK");
System.out.println(ss1);//中国 idea平台默认的是UTF-8
System.out.println(ss2);//中国
System.out.println(ss3);//�й�
System.out.println(ss4);//中国
}
}
9.3.4 字符流中的编码解码问题
字符流的抽象基类:
- Reader:字符输入流的抽象类
- Writer:字符输出流的抽象类
字符流中和编码解码问题的相关的两个类:
- InputStreamReader
- OutputStreamWriter
package com.io.characterStream.demo03;
import java.io.*;
public class Test {
public static void main(String[] args) throws IOException {
//OutputStreamWriter(OutputStream out)创建一个使用默认字符编码的OutputStreamWriter
//OutputStreamWriter(OutputStream out,String charsetName)创建一个使用名字字符集的OutputStreamWriter
// OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("基础语法\\fos.txt"));idea默认也是UTF-8
// OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("基础语法\\fos.txt"),"UTF-8");
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("基础语法\\fos.txt"),"GBK");
osw.write("中国");
osw.close();
// InputStreamReader(InputStream in)创建一个使用默认字符编码的InputStreamReader
// InputStreamReader(InputStream in,String charsetName)创建一个使用名字字符集的InputStreamReader
InputStreamReader isr = new InputStreamReader(new FileInputStream("基础语法\\fos.txt"),"GBK");
int ch;
while((ch= isr.read())!=-1){
System.out.print((char)ch);
}
}
}
9.3.5 字符流写数据的五种方式
方法名 | 说明 |
---|---|
void write(int c) | 写一个字符 |
void write(char[] cbuf) | 写一个字符数组 |
void write(char[] cbuf,int off,int len) | 写入字符数组的一部分 |
void write(String str) | 写一个字符串 |
void write(String str,int off,int len) | 写一个字符串一部分 |
package com.io.characterStream.demo04;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
public class Test {
public static void main(String[] args) throws IOException {
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("基础语法\\osw.txt"));
//void write(int c)
osw.write(97);
osw.flush();//刷新流
osw.write(98);
//void write(char cbuf)
char[] ch = {'a','b','c','d','e'};
osw.write(ch);
osw.write(ch,1,3);
osw.write("fjhhkj");
osw.write("yaudgdaqwadig",2,8);
osw.close();//先刷新 再关闭
// osw.write(99);Exception in thread "main" java.io.IOException: Stream closed 缓冲流已经关闭了
}
}
9.3.6 字符流读数据的2种方法
方法名 | 说明 |
---|---|
int read() | 一次读一个字符数据 |
int read(char[] cbuf) | 一次读一个字符数组数据 |
package com.io.characterStream.demo05;
import com.sun.org.apache.xpath.internal.operations.String;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
public class Test {
public static void main(String[] args) throws IOException {
InputStreamReader isr = new InputStreamReader(new FileInputStream("基础语法\\osw.txt"));
int ch;
while((ch=isr.read())!=-1){
System.out.print((char)ch );
}
char[] chs = new char[1024];
int len;
while((len=isr.read(chs))!=-1){
System.out.print(new String(chs,0,len));
}
isr.close();
}
}
9.3.7 复制Java文件
package com.io.characterStream.demo06;
import java.io.*;
public class Test {
public static void main(String[] args) throws IOException {
InputStreamReader isr = new InputStreamReader(new FileInputStream(""));
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(""));
char[] ch = new char[1024];
int len;
while((len=isr.read(ch))!=-1){
osw.write(ch,0,len);
}
osw.close();
isr.close();
}
}
9.3.8 复制Java文件进阶版
(1)转换流的名字比较长(比如InputStreamReader和OutputStreamWriter),而我们常见的操作都是按照本地默认编码实现的,所以为了简化书写,转换流提供了对应的子类;
(2)FileReader:用于读取字符文件的便捷类,构造方法为FileReader(String fileName);
(3)FileWriter:用于写入字符文件的便捷类,构造方法为FileWriter(String fileName);
- 注意:如果在字符流中涉及到编码和解码的问题,我们还是要使用转换流inputStreamReader和OutputStreamWriter。
package com.io.characterStream.demo07;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class Test {
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader("");
FileWriter fw = new FileWriter("");
char[] ch = new char[1024];
int len;
while((len=fr.read(ch))!=-1){
fw.write(ch,0,len);
}
fr.close();
fw.close();
}
}
9.3.9 字符缓冲流
字符缓冲流:
(1)BufferedWriter:将文本写入字符输出流,缓冲字符,以提供单个字符,数组和字符串的高效写入,可以指定缓冲区大小,或者可以接收默认大小。默认值足够大,可用于大多数用途;
(2)BufferedReader:从字符输入流读取文本,缓冲字符,以提供字符,数组和行的高效读取,可以指定缓冲区大小,或者可以使用默认大小。默认值足够大,可用于大多数用途。
构造方法:
(1)BufferedWriter(Writer out);
(2)BufferedReader(Reader in)。
package com.io.characterStream.demo08;
import java.io.*;
public class Test {
public static void main(String[] args) throws IOException {
BufferedWriter bw = new BufferedWriter(new FileWriter("基础语法\\bw.txt"));
bw.write("hello\r\n");
bw.write("world\r\n");
bw.close();
BufferedReader br = new BufferedReader(new FileReader("基础语法\\bw.txt"));
char[] ch =new char[1024];
int len;
while((len=br.read(ch))!=-1){
System.out.println(new String(ch,0,len));
}
br.close();
}
}
9.3.10 字符缓冲流特有的功能
BufferedWrite:
- void newLine():写一行行分隔符,行分隔符字符串由系统属性定义
BufferedReader:
- public String readLine();读一行文字,结果包含行的内容的字符串,不包括任何行终止符,如果流的结尾已经到达,则为nill
package com.io.characterStream.demo09;
import java.io.*;
public class Test {
public static void main(String[] args) throws IOException {
BufferedWriter bw = new BufferedWriter(new FileWriter("基础语法\\bw,.txt"));
for (int i = 0; i < 10; i++) {
bw.write("hello"+i);
bw.newLine();//bw.write("\r\n")
bw.flush();
}
bw.close();
BufferedReader br = new BufferedReader(new FileReader("基础语法\\bw,.txt"));
String line;
while((line=br.readLine())!=null){
System.out.println(line);
}
br.close();
}
}
9.3.1 IO流小结
- 字节流
小结1:字节流可以复制任意文件数据,有4种方式,一般采用字节缓冲流一次写一个字节数组的方式。
- 字符流:
小结2:字符流只能复制文本数据,有5种方式,一般采用字符缓冲流的特有功能。
(1)字节流(可以读写任意文件数据):
字节输入流InputStream->文件字节输入流FileInputStream, 字节缓冲流BufferedInputStream(文件字符输入流和文件字符输出流都可以使用一次读取一个字符和一次读取一个字符数组的方法,共计四种读取方式)
字节输出流OutputStream->文件字节输出流FileOutputStream, 字节缓冲流BufferedOutputStream
(2)
字符流(只能复制文本数据):
字符输入流Reader->(InputStreamReader->FileReader(特殊方法String readLine())), 字符缓冲流BufferedReader
字符输出流Writer->(OutputStreamWriter->FileWriter(特殊方法void newLine(), void write(String line))),字符缓冲流 BufferedWriter
9.3.12 集合到文件
需求:把ArrayList集合汇总的字符串数据写入到文本文件。要求:每一个字符串作为文件中的一行数据。
思路:
(1)创建ArrayList集合;
(2)往集合中存储字符串元素;
(3)创建字符缓冲输出流对象;
(4)遍历集合,得到每一个字符串数据;
(5)调用字符串冲输出流对象的方法写数据;
(6)释放资源。
public static void main(String[] args) throws IOException {
ArrayList<String> arrayList = new ArrayList<>();
arrayList.add("liubei");
arrayList.add("guanyu");
arrayList.add("zhangfei");
BufferedWriter bw = new BufferedWriter(new FileWriter("a.txt"));
for (String s : arrayList){
bw.write(s);
bw.newLine();
bw.flush();
}
bw.close();
}
9.3.13 文件到集合
需求:把文本文件中的数据读取到集合中,并遍历集合。要求文件中每一行数据是一个集合元素。
思路:
(1)创建字符缓冲输入流对象;
(2)创建ArrayList集合对象;
(3)调用字符换成输入流对象的方法读数据;
(4)把读到的字符串数据存储到集合中;
(5)释放资源;
(6)遍历集合。
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new FileReader("a.txt"));
ArrayList<String> arrayList = new ArrayList<>();
String line;
while ((line = br.readLine()) != null){
arrayList.add(line);
}
br.close();
for (String s : arrayList){
System.out.println(s);
}
}
9.3.14 点名器
需求:有一个文件里面存储了班级同学的姓名,每一个姓名占一行,要求通过程序实现随机点名器。
思路(1-5步同上一案例文件到集合):
(1)创建字符缓冲输入流对象;
(2)创建ArrayList集合对象;
(3)调用字符换成输入流对象的方法读数据;
(4)把读到的字符串数据存储到集合中;
(5)释放资源;
(6)使用Random产生一个随机数,随机数的范围在[0,集合的长度);
(7)把第6步产生的随机数作为索引到ArrayList集合中获取值;
(8)把第7步得到的随机数据输出在控制台。
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new FileReader("a.txt"));
ArrayList<String> arrayList = new ArrayList<>();
String line;
while ((line = br.readLine()) != null){
arrayList.add(line);
}
br.close();
Random r = new Random();
int index = r.nextInt(arrayList.size());
System.out.println(arrayList.get(index));
}
(7条消息) 黑马程序员全套Java教程_Java基础教程_IO流之字符流(三十二)_丶槛外的博客-CSDN博客
(7条消息) 黑马程序员全套Java教程_Java基础教程_IO流之特殊操作流(三十三)_丶槛外的博客-CSDN博客
(上面这两个都没看视频直接看文字解说即可)
10.多线程
多线程之实现多线程(三十四):(7条消息) 黑马程序员全套Java教程_Java基础教程_多线程之实现多线程(三十四)_丶槛外的博客-CSDN博客
多线程之线程同步(三十五):(7条消息) 黑马程序员全套Java教程_Java基础教程_多线程之线程同步(三十五)_丶槛外的博客-CSDN博客
多线程之生产者消费者(三十六):(7条消息) 黑马程序员全套Java教程_Java基础教程_多线程之生产者消费者(三十六)_丶槛外的博客-CSDN博客
多线程之反射(三十七):(7条消息) 黑马程序员全套Java教程_Java基础教程_多线程之反射(三十七)_丶槛外的博客-CSDN博客
多线程之模块化(p408-410)(三十八):(7条消息) 黑马程序员全套Java教程_Java基础教程_多线程之模块化(p408-410)(三十八)_丶槛外的博客-CSDN博客
rite:
- void newLine():写一行行分隔符,行分隔符字符串由系统属性定义
BufferedReader:
- public String readLine();读一行文字,结果包含行的内容的字符串,不包括任何行终止符,如果流的结尾已经到达,则为nill
package com.io.characterStream.demo09;
import java.io.*;
public class Test {
public static void main(String[] args) throws IOException {
BufferedWriter bw = new BufferedWriter(new FileWriter("基础语法\\bw,.txt"));
for (int i = 0; i < 10; i++) {
bw.write("hello"+i);
bw.newLine();//bw.write("\r\n")
bw.flush();
}
bw.close();
BufferedReader br = new BufferedReader(new FileReader("基础语法\\bw,.txt"));
String line;
while((line=br.readLine())!=null){
System.out.println(line);
}
br.close();
}
}
9.3.1 IO流小结
- 字节流
[外链图片转存中…(img-N6rG0sTz-1662381586145)]
[外链图片转存中…(img-O5FA1Dhy-1662381586146)]
小结1:字节流可以复制任意文件数据,有4种方式,一般采用字节缓冲流一次写一个字节数组的方式。
- 字符流:
[外链图片转存中…(img-4QDdbefr-1662381586146)]
[外链图片转存中…(img-ZgSVVfVg-1662381586147)]
小结2:字符流只能复制文本数据,有5种方式,一般采用字符缓冲流的特有功能。
(1)字节流(可以读写任意文件数据):
字节输入流InputStream->文件字节输入流FileInputStream, 字节缓冲流BufferedInputStream(文件字符输入流和文件字符输出流都可以使用一次读取一个字符和一次读取一个字符数组的方法,共计四种读取方式)
字节输出流OutputStream->文件字节输出流FileOutputStream, 字节缓冲流BufferedOutputStream
(2)
字符流(只能复制文本数据):
字符输入流Reader->(InputStreamReader->FileReader(特殊方法String readLine())), 字符缓冲流BufferedReader
字符输出流Writer->(OutputStreamWriter->FileWriter(特殊方法void newLine(), void write(String line))),字符缓冲流 BufferedWriter
9.3.12 集合到文件
需求:把ArrayList集合汇总的字符串数据写入到文本文件。要求:每一个字符串作为文件中的一行数据。
思路:
(1)创建ArrayList集合;
(2)往集合中存储字符串元素;
(3)创建字符缓冲输出流对象;
(4)遍历集合,得到每一个字符串数据;
(5)调用字符串冲输出流对象的方法写数据;
(6)释放资源。
public static void main(String[] args) throws IOException {
ArrayList<String> arrayList = new ArrayList<>();
arrayList.add("liubei");
arrayList.add("guanyu");
arrayList.add("zhangfei");
BufferedWriter bw = new BufferedWriter(new FileWriter("a.txt"));
for (String s : arrayList){
bw.write(s);
bw.newLine();
bw.flush();
}
bw.close();
}
9.3.13 文件到集合
需求:把文本文件中的数据读取到集合中,并遍历集合。要求文件中每一行数据是一个集合元素。
思路:
(1)创建字符缓冲输入流对象;
(2)创建ArrayList集合对象;
(3)调用字符换成输入流对象的方法读数据;
(4)把读到的字符串数据存储到集合中;
(5)释放资源;
(6)遍历集合。
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new FileReader("a.txt"));
ArrayList<String> arrayList = new ArrayList<>();
String line;
while ((line = br.readLine()) != null){
arrayList.add(line);
}
br.close();
for (String s : arrayList){
System.out.println(s);
}
}
9.3.14 点名器
需求:有一个文件里面存储了班级同学的姓名,每一个姓名占一行,要求通过程序实现随机点名器。
思路(1-5步同上一案例文件到集合):
(1)创建字符缓冲输入流对象;
(2)创建ArrayList集合对象;
(3)调用字符换成输入流对象的方法读数据;
(4)把读到的字符串数据存储到集合中;
(5)释放资源;
(6)使用Random产生一个随机数,随机数的范围在[0,集合的长度);
(7)把第6步产生的随机数作为索引到ArrayList集合中获取值;
(8)把第7步得到的随机数据输出在控制台。
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new FileReader("a.txt"));
ArrayList<String> arrayList = new ArrayList<>();
String line;
while ((line = br.readLine()) != null){
arrayList.add(line);
}
br.close();
Random r = new Random();
int index = r.nextInt(arrayList.size());
System.out.println(arrayList.get(index));
}
(7条消息) 黑马程序员全套Java教程_Java基础教程_IO流之字符流(三十二)_丶槛外的博客-CSDN博客
(7条消息) 黑马程序员全套Java教程_Java基础教程_IO流之特殊操作流(三十三)_丶槛外的博客-CSDN博客
(上面这两个都没看视频直接看文字解说即可)
10.多线程
多线程之实现多线程(三十四):(7条消息) 黑马程序员全套Java教程_Java基础教程_多线程之实现多线程(三十四)_丶槛外的博客-CSDN博客
多线程之线程同步(三十五):(7条消息) 黑马程序员全套Java教程_Java基础教程_多线程之线程同步(三十五)_丶槛外的博客-CSDN博客
多线程之生产者消费者(三十六):(7条消息) 黑马程序员全套Java教程_Java基础教程_多线程之生产者消费者(三十六)_丶槛外的博客-CSDN博客
多线程之反射(三十七):(7条消息) 黑马程序员全套Java教程_Java基础教程_多线程之反射(三十七)_丶槛外的博客-CSDN博客
多线程之模块化(p408-410)(三十八):(7条消息) 黑马程序员全套Java教程_Java基础教程_多线程之模块化(p408-410)(三十八)_丶槛外的博客-CSDN博客