跳到主要内容
JavaSE 核心知识点总结:语法、OOP、集合、IO 及线程 | 极客日志
Java java 算法
JavaSE 核心知识点总结:语法、OOP、集合、IO 及线程 系统讲解 JavaSE 核心知识,包括基础语法(数据类型、转换、运算符、循环、方法、数组)、面向对象(类、对象、构造方法、重载、this、static、代码块、权限修饰符、封装、继承、多态、重写、final、抽象类、接口)、API 常用类(Object、Arrays、String、StringBuffer、Math、Random、Date)、集合框架(Collection、List、Set、Map 体系及遍历)、IO 流(File、字节流、字符流、节点流、处理流、序列化)、异常处理(概述、体系、常见异常、try-catch-finally)、网络编程(模型、TCP/UDP、Socket)及多线程(进程线程区别、创建方式、生命周期、同步锁)。通过代码示例与理论结合,帮助读者掌握 Java 开发基础。
墨染流年 发布于 2026/3/30 更新于 2026/5/23 28 浏览一、基础语法
1. 数据类型
分类:
基本数据类型:
int, short, long, byte(整数型)
float, double(浮点型)
char(字符型)
boolean(布尔型)
注意: Java 中浮点数字面量默认是 double 类型,使用 float 类型需加 f / F。
引用数据类型:
String:修饰字符串
class:修饰类
Interface:修饰接口
2. 基本数据类型转换
分类:
默认转换:小容量类型转为大容量类型
强制转换:大容量类型转为小容量类型(存在溢出或精度降低)
注意: 容量不是所占字节大小;如 4 个字节的 float 类型,容量大于 8 个字节的 long 类型。
容量大小排行: byte/short/char ----> int ----> long ----> float ----> double(小--->大)
3. 运算符
算术运算符
注意 i++ 和 ++i 区别:
赋值运算符
比较运算符
& 无论左边是真是假,右边表达式都运算
&& 当左边表达式为真,右边表达式才运算;若左边为假,那么右边不参与运算
(|| 和 | 同理,|| 表示左边为真,右边不参与运算)
实际开发中推荐使用&& ||
4. 循环语句 while 循环,do/while 循环,for 循环
5. 定义方法
所有方法都要定义到类里
Java 中的方法类似其他语言中的函数,对完成某个功能的代码进行封装并为其命名,最终可以重复调用
方法定义:
例如:public static void menu( ){ ... }
public:访问权限
static:静态
void:返回值类型
menu:方法名 (自定义)
( ):参数列表
{ ... }:方法体
6. 数组
数组是一组相同数据类型的集合,是一个容器。
数组中可以存储基本数据类型,也可以存储引用数据类型。
数组本身是引用数据类型,是一个对象。
数组创建时必须指明长度,且长度不能改变。
数组中每个元素空间是连续的。
访问数组中的元素通过下标访问,即索引;
下标从 0 开始,是 int 类型;数组的最大索引=数组长度 -1
int [] a = new int [5 ];
System.out.println(Arrays.toString(a));
**前言:**面向过程和面向对象都是语言设计思想,面向过程 POP 是具体的步骤,是早期的编程语言设计思想,结构简单,扩展能力差,后期维护难度较大;面向对象 OOP,面向对象设计程序时,从宏观上分析程序有哪些功能,然后对功能进行分类,把不同的功能封装在不同的类中,是一种宏观的设计,但到具体实现,仍然离不开面向过程。(二者的区别与关系)
二、面向对象
1. 类和对象 什么是类?
具有相同特征(同一类)事物的抽象描述,如人类,车类,学生类等。
变量:事物属性的描述 (名词)
方法:事物的行为(可以做的事情 动词)
构造方法:初始化对象
块:一段没有名称的代码块
内部类:即在类体中声明的类
第一步----发现类
第二步----定义类的成员变量
第三步----定义类的成员方法
第四步----使用类创建对象
public class Student {
String name ;
int age;
String gender;
public void study () {
System.out.println("学生要好好学习" );
}
public void excise () {
System.out.println("学生要多运动" );
}
public static void main (String[] args) {
Student student = new Student ();
student.study();
student.name = "小王" ;
}
}
什么是对象?
对象是类的实例,以类为模版,在内存中创建出一个实际存在的实例。
我们使用 new 关键字来创建一个新的对象。
Student student = new Student ();
2. 构造方法 **分类:**构造方法分为 无参构造方法 和 有参构造方法。
在一个类中可以有多个构造方法 ( 构造方法可以重载 ).
方法名与类名相同,没有返回值,且不需要 void 修饰。
如果一个类没有定义构造方法,Java 会提供一个默认的无参构造方法 ; 如果一个类定义了构造方法,Java 将不会提供默认构造方法。
构造方法在使用 new 关键字实例化对象时自动调用。
构造方法的作用是初始化对象的状态,可以在构造方法中为对象的实例变量赋初值。
public class Student {
String name ;
int age;
String gender;
public Student () {
name = "小张" ;
age = 18 ;
gender = "男" ;
System.out.println(name+age+gender);
}
public Student (String name,int age,String gender) {
this .name=name;
this .age=age;
this .gender=gender;
System.out.println(name+age+gender);
}
public static void main (String[] args) {
Student student = new Student ();
Student student1 = new Student ("小魏" ,20 ,"女" );
}
}
3. 方法的重载
在一个类中有多个名称相同的方法,如何在调用时区分同名的方法?
通过方法的参数的个数,类型,顺序;
方法的重载与返回值类型无关
public class Student {
String name ;
int age;
String gender;
public Student (String name,int age,String gender) { }
public Student (int age,String name,String gender) {
public Student (String name,int age) {
}
4. this 关键字
在一个类的内部可以使用 this 关键字来引用成员变量名,区分成员变量和局部变量。(作用)
this 在类中表示当前正在访问的对象
public Person (String name,int age,String gender) {
this .name = name;
this .age = age;
this .gender = gender;
}
5. static 关键字
static ---静态,可以修饰类中的成员变量,成员方法,代码块,内部类(不能修饰构造方法)
修饰成员变量
静态成员变量也称类变量,在内存中只有一份,所有对象可以共享,一般情况下,将类中所有 对象都相同的属性设置为静态的。
修饰成员方法
修饰的成员方法也称为类方法,可以直接使用类名访问,在静态的方法中只能访问静态的成员 变量,非静态的方法中可以使用静态的成员变量。
随着类的加载而加载
优先于对象存在
静态成员被所有对象共享
可以直接使用类名访问
静态的方法中,只能使用静态的成员变量,因为他们都是随着类的加载而加载的;
一旦方法中使用了非静态的成员变量,那么此方法就不能定义为静态的;但非静态的方法中可以 使用静态的成员变量。
6. 代码块
实例代码块:在每次创建对象时执行 { 实例代码块内容 }
静态代码块:在类被加载时自动执行 static { 静态代码块内容 }
运行类中的 main 方法
访问类中的静态成员变量,静态成员方法
创建类的对象
public class Demo {
{
System.out.println("1-创建对象时,不需要显示的调用,会自动执行" );
}
static {
System.out.println("2-在类被加载时,会自动执行" );
}
public static void main (String[] args) {
new Demo ();
new Demo ();
}
}
7. 访问权限修饰符
public :公共权限--------修饰的成员,在任何地方都可以访问到。
protected :受保护权限------在本类,同包的其他类中可以访问到,及不同包的子类。
default :默认权限--------在本类,同包的其他类中可以访问到。
private :私有权限 -------只能在本类访问到。
(protected 和默认 (default) 的区别就是,protected 可以在不同包的子类中访问)
8. 面向对象的三大特征
封装
**概念:**将类中的某些信息,使用不同的访问权限修饰符隐藏起来,不让外界直接访问操作,而是通过 类中向外提供的特定方法访问,方便加入控制语句,主动权在类手中。
封装案例 1:将类中成员变量私有化
public class Student {
private String name;
private int age ;
}
此时在其他类中不能任意访问成员变量,只能通过类中提供的特殊的方法进行访问。
public String getName () {
return name;
}
public void setName (String name) {
if (name.length()>1 &&name.length()<5 ){
this .name = name;
}
}
public int getAge () {
return age;
}
public void setAge (int age) {
if (age<100 &&age>0 ){
this .age = age;
}
}
public static void main (String[] args) {
Student s1 = new Student ();
s1.setName("小王" );
s1.setAge(-1 );
System.out.println(s1.getName());
System.out.println(s1.getAge());
}
单例模式:让一个类在一个程序中只能创建一个对象,将类的构造方法私有化,外界不能随便用。
public class Window {
static Window window = new Window ();
private Window () { }
public static Window getWindow () {
return window;
}
public static void main (String[] args) {
System.out.println(Window.getWindow());
System.out.println(Window.getWindow());
System.out.println(Window.getWindow());
}
}
可以看到,此例中创建的每个 window 的哈希值都相同于 Window 类中第一次创建的 window
**作用:**可以有效的避免创建多个对象,达到在一个程序中只创建一个对象的作用。
继承
**概念:**将同一类事物中共性的属性和行为进行抽取,定义为一个类中(基类),其他类可以继 承基类,就可以拥有基类中的功能,但不能使用基类中私有的内容。
**作用:**实现代码的复用性,以及可以在子类扩展自己特有的功能,而不影响其他类。(优点)
使用条件:只要符合 is-a 关系(什么是什么关系,比如苹果是水果),就可以使用继承,一个类 只能 直接 继承一个类;而间接的可以继承多个类,称为继承的传递性 ,例如 b 继承 a,然后 c 继承 b,则 c 也继承了 a.
继承的基本语法:
在创建类的时候在类名后使用extends 关键字 继承别的类,子类继承父类后,拥有了父类的成员 变量和成员方法,但不能访问私有成员。
public class Cat extends Animal {
**注意:**当一个类没有显示继承其他类的时候默认继承 object 类,Object 类是 java 类体系中最大的 类,Object 类之上再也没有别的类。
多态 **概念:**用父类的引用变量指向子类对象,多态也称向上转型,将子类类型转为父类类型。
**作用:**用父类类型表示任意的子类类型对象,利于程序扩展。
编译期 --- 类型是父类类型
运行期 --- 类型是具体的子类类型
口诀:编译看左边,运行看右边(若是静态方法则都看左边(父类))
多态的存在意味着可以使用父类类型的引用来调用子类对象中重写的方法。
public class Animal {
public void makeSound () {
System.out.println("Animal is making a sound" );
}
}
public class Dog extends Animal {
@Override
public void makeSound () {
System.out.println("Dog is barking" );
}
public static void main (String[] args) {
Animal dog = new Dog ();
dog.makeSound();
}
}
在上面的例子中,animal 引用的是一个 Dog 对象,但是调用的是 Dog 类中重写的 makeSound()方 法。这就是多态的效果!
9. 方法的重写 **概念:**当父类中方法的实现不能满足子类需求时,可以在子类中对父类的方法进行重写 ( 覆盖),这 样调用时,就会调用子类中重写的方法。
子类重写的方法结构与父类方法的结果必须一致(方法名,参数列表,返回值类型必须一致)
子类重写的方法使用的访问权限不能小于父类被重写方法的访问权。
构造方法,静态方法不能重写,成员变量不存在重写。
使用 @Override 注解标签
了解:
@ Override 是 Java 中的一个注解标签,定义在重写的方法上面,表示此方法是从父类重写而来,也可以不用添加,不过建议保留,因为编译器可以进行语法验证,并且阅读代码时可以明确的知道此方法是重写的。
10. final 关键字
final 可以修饰类,方法,参数,成员变量(常量)
final 修饰的类不能被继承,所以不能修饰抽象类,例如 Java 中 String 类就是 final 修饰
final 修饰的方法不能被重写
final 修饰方法的参数,参数值在方法中不能被改变
final 修饰的成员变量值不能改变,因此称为常量
final static int A = 10 ;
final int COUNT ;
public demo1 () {
COUNT = 10 ;
}
public demo1 (int COUNT) {
this .COUNT=COUNT;
}
(常量一般建议全部使用大写字母 多个单词间用下划线连接)
11. 抽象类
**概念:**抽象方法是一种特殊的方法,只有声明,没有具体的实现,必须用 abstract 关键字修饰,没有方法体。
**作用:**在一些体系结构的顶端,只需要某些定义功能而没有必要实现,因为不同子类中的实现 都不同,这个时候就可以将这个方法声明为抽象方法。
例如定义一个 eat 的抽象方法:
public abstract class Animal {
public abstract void eat () ;
}
abstract class Animal {
abstract void sound () ;
void sleep () {
System.out.println("睡觉" );
}
}
子类继承抽象类时,必须实现所有的抽象方法,否则子类也必须声明为抽象类
class Dog extends Animal {
void sound () {
System.out.println("汪汪" );
}
}
抽象类可以有构造方法,但是不能被实例化。子类可以通过使用"super"关键字来调用抽象类的 构造方法和方法。
12. 接口
接口可以看做是一种特殊的抽象类,里面可以包含抽象方法,但不能被创建对象;接口可以被 类实现,实现类必须实现接口中定义的所有方法;接口和抽象类十分的类似,接口内部的方法也 是默认抽象的,不可在内部实例化的,只能由接口的调用者去实现。
接口通过关键字 interface 定义;类通过implements 关键字来实现接口。
public interface MyInterface {
int num = 10 ;
void eat () ;
public static void test () {
}
default void test1 () {
}
}
**注意:**类实现接口后,要么重写接口中所有抽象方法,要么声明为抽象类。
public class MyClass implements A ,B{
}
public interface A extends B ,C{
}
三、API 常用类
Object 类
Object 类是所有 Java 类的祖先(根基类),每个类都使用 Object 作为超类(父类),所有对象(包括数组)都继承实现这个类的方法。
如果在类的声明中未使用 extends 关键字指明其基类,则默认基类为 Object 类
public class Person {...} 等价于:public class Person extends Object {...}
1. toString 方法
Object 类中定义有 public String toString() 方法,其返回值是 String 类型,描述当前对象的有关息;当需要通过输出语句输出一个对象时,如 System.out.println(person),将自动调用该对象类的 toString() 方法,如果该类中没有重写 toString(),那么默认调用 Object 类中 toString(),默认输出对象 hashCode 值。我们可以根据需要在用户自定义类型中重写 toString() 方法。
public class Person {
private String name ;
private int age;
public Person () { }
public Person (String name, int age) {
this .name = name;
this .age = age;
}
@Override
public String toString () {
return "Person{" + "name='" + name + '\'' + ", age=" + age + '}' ;
}
}
public static void main (String[] args) {
Person p1=new Person ("小魏" ,18 );
System.out.println(p1);
}
当我们重写 toString 方法后就可以把对象信息通过字符串形式输出了。
如果不重写的话会默认调用 Object 类中 toString(),把对象在内存的哈希值返回 (以 16 进制返回)。
快速生成 toString 重写方法:
右键选择 Generate,点击 toString() 即可。
2. equals 方法
boolean equals(Object obj) 判断两个对象是否相等
Object 类中的 equals默认 比较的是两个对象的地址是否相等,我们就可以使用 == 代替它,在其他类中,一般都重写了 equals(),把它改造为比较对象中的内容是否相等
public class Test_2 {
public static void main (String[] args) {
Person p1 = new Person ("小魏" ,16 );
Person p2 = new Person ("小魏" ,16 );
System.out.println(p1==p2);
System.out.println(p1.equals(p2));
}
}
(Object 中的 equals 方法默认使用==比较,比较的是对象地址,这点需要注意)
在 Person 类中对 equals 方法进行重写,使其比较的是对象中的内容是否相等
@Override
public boolean equals (Object obj) {
if (obj instanceof Person){
Person other = (Person)obj;
return name.equals(other.name) && age ==other.age;
}
return false ;
}
"==" 比较等号两边是否相等
当==用于基本类型比较时,比较的是变量值是否相等。
当==用于引用类型比较时,比较的是对象的地址是否相等。
(JDK 提供的一些类,如 String,Date 等,重写了 Object 的 equals 方法,调用这些类的 equals 方法,x.equals (y),当 x 和 y 所引用的对象是同一类对象且属性内容相等返回 true 否则返回 false)
Arrays 类
1. equals( ) 方法
比较两个数组内容是否相等,返回值为 boolean 类型。
public class Null {
public static void main (String[] args) {
int [] a={1 ,2 ,3 ,4 };
int [] b={1 ,2 ,3 ,4 };
System.out.println(Arrays.equals(a,b));
}
}
2. copyOf( ) 方法
数组复制,将原数组内容复制到一个指定长度新数组中。
public class Null {
public static void main (String[] args) {
int [] c=new int [3 ];
c[0 ]=0 ;
c[1 ]=1 ;
c[2 ]=2 ;
int []d=Arrays.copyOf(c,6 );
System.out.println(Arrays.toString(d));
}
}
3. fill( ) 方法。 public class Null {
public static void main (String[] args) {
int []e ={1 ,2 ,3 ,4 ,5 };
Arrays.fill(e,0 );
System.out.println(Arrays.toString(e));
int []f = new int [10 ];
Arrays.fill(f,6 );
System.out.println(Arrays.toString(f));
}
}
4. sort( ) 方法 public class Null {
public static void main (String[] args) {
int [] a = {5 ,4 ,3 ,2 ,1 };
Arrays.sort(a);
System.out.println(Arrays.toString(a));
int [] b ={6 ,5 ,4 ,3 ,2 ,1 };
Arrays.sort(b,1 ,4 );
System.out.println(Arrays.toString(b));
}
}
5. binarySearch( )方法 public class Null {
public static void main (String[] args) {
int [] b ={5 ,4 ,6 ,8 ,2 ,1 ,7 };
Arrays.sort(b);
int index = Arrays.binarySearch(b,6 );
System.out.println(index);
}
}
6. toString( ) 方法 public class Null {
public static void main (String[] args) {
int [] a={1 ,2 ,3 ,4 };
System.out.println(a);
System.out.println(Arrays.toString(a));;
}
}
String 类
1. 获取功能的常用方法
int length( ) 获取字符串长度
char charAt( ) 获取指定位置上的字符
int indexOf( ) 获取字符首次出现的位置,也可以从指定位置开始查找
lastIndexOf( ) 从后往前找
String substring( ) 从指定位置开始截取一个字符串副本
public static void main (String[] args) {
String s1 = "abcdabcd" ;
System.out.println(s1.length());
System.out.println(s1.charAt(4 ));
System.out.println(s1.indexOf("a" ));
System.out.println(s1.indexOf("a" ,4 ));
System.out.println(s1.lastIndexOf("a" ));
String s2=s1.substring(2 ,6 );
System.out.println(s2);
}
2. 转换功能的常用方法
String toUpperCase( ) 将字符串内容全部转换为大写
String toLowerCase( ) 将字符串内容全部转换为小写
String concat(String str) 拼接指定字符串内容到原字符串末尾
String [ ] split(分割符) 通过分割符将字符串分割并以数组形式返回
public static void main (String[] args) {
String s1="WoShiChinese" ;
System.out.println(s1.toUpperCase());
System.out.println(s1.toLowerCase());
String s2 = "我爱中国" ;
System.out.println(s2.concat(s1));
String s3 = "张三:中国人;" +"Tom:英国人;" +"梅西:阿根廷人;" ;
String[] strings = s3.split(";" );
System.out.println(Arrays.toString(strings));
}
3. 替换功能的常用方法
String replace(char old,char new) 单个字符替换
String replace(String old,String new) 整个字符串替换
replaceAll(String regex, String replacement) 替换字符串中所有数字
replaceFirst(String regex, String replacement) 替换字符串中第一个数字
public static void main (String[] args) {
String s1 = " ab5cd7bda" ;
System.out.println(s1.replace("a" ,"A" ));
System.out.println(s1.replaceAll("\\d" ,"G" ));
System.out.println(s1.replaceFirst("\\d" ,"R" ));
}
4. 去除字符串两端空格
String trim( ) 去除字符串两端空格(字符串中间的空格不能去除)
public static void main (String[] args) {
String s2 = " abcEF 13aD1 " ;
System.out.println(s2);
System.out.println(s2.length());
System.out.println(s2.trim());
System.out.println(s2.trim().length());
}
StringBuffer 类
String 可以直接创建字符串对象,而 StringBuffer 不能,需要新创建一个字符串对象。
String s1 = "abc" ;
StringBuffer s2 = new StringBuffer ("abc" );
添加功能
public StringBuffer append(String str) 在原字符串末尾添加字符串
public static void main (String[] args) {
StringBuffer s1 = new StringBuffer ("abc" );
s1.append("def" );
System.out.println(s1);
}
public StringBuffer insert(int offset,String str) 向指定位置插入字符串
public static void main (String[] args) {
StringBuffer s1 = new StringBuffer ("abc" );
s1.append("def" );
System.out.println(s1);
s1.insert(1 ,"ABC" );
System.out.println(s1);
}
删除功能
public StringBuffer deleteCharAt(int index) 删除单个字符
public StringBuffer delete(int start,int end) 删除指定区间的元素,不包含结尾
public static void main (String[] args) {
StringBuffer s1 = new StringBuffer ("abcdef" );
s1.deleteCharAt(2 );
System.out.println(s1);
StringBuffer s2 = new StringBuffer ("ABCDEF" );
s2.delete(1 ,3 );
System.out.println(s2);
}
替换功能
public StringBuffer replace(int start,int end,String str)
StringBuffer s1 = new StringBuffer ("abcdef" );
s1.replace(0 ,3 ,"ABC" );
System.out.println(s1);
反转功能
public StringBuffer reverse( )
StringBuffer s1 = new StringBuffer ("abcdef" );
s1.reverse();
System.out.println(s1);
截取功能
从 StringBuffer 中截取一个副本,返回给一个新的 String 对象,StringBuffer 对象不变
public static void main (String[] args) {
StringBuffer s1 = new StringBuffer ("abcdef" );
String s2=s1.substring(1 ,4 );
System.out.println(s1);
System.out.println(s2);
}
StringBuffer 和 String 的区别
String 修饰的字符串是一个值不能改变的字符串,用 String 声明的字符串对象值一旦给定就不能改变了,每次拼接都会创建新的字符串对象,耗时且占用空间。
StringBuffer 是内容可以改变的字符串,值可以改变且不需要创建新对象,在多任务执行时是安全的,适合单线程。
Math 类 介绍:
java.lang.Math 提供了一系列静态方法用于科学计算;其方法的参数和返回值类型一般为 double 型。
abs 绝对值
sqrt 平方根
pow(double a, double b) a 的 b 次幂
max(double a, double b) min(double a, double b) 比较两数的大小
random( ) 返回 0.0 到 1.0 的随机数
long round(double a) double 型的数据 a 转换为 long 型(四舍五入)
public static void main (String[] args) {
System.out.println(Math.abs(-3 ));
System.out.println(Math.sqrt(25 ));
System.out.println(Math.pow(2 ,5 ));
System.out.println(Math.round(9.35 ));
System.out.println(Math.floor(4.9 ));
System.out.println(Math.ceil(1.1 ));
System.out.println(Math.random());
}
Random 类
不能直接调用,使用 Random 类之前需要创建新的对象。
**介绍:**此类用于产生随机数
常用方法有:
import java.util.Random;
public static void main (String[] args) {
Random random = new Random ();
System.out.println(random.nextBoolean());
System.out.println(random.nextInt());
System.out.println(random.nextLong());
System.out.println(random.nextInt(35 )+1 );
byte [] bytes = new byte [5 ];
random.nextBytes(bytes);
System.out.println(Arrays.toString(bytes));
}
Date 类
不能直接调用,使用 Date 类之前需要创建新的对象。
Date date = new Date ();
System.out.println(date);
2. 获取自 1970 1.1 0:0:0 到程序运行时刻的毫秒值
Date date = new Date ();
System.out.println(date.getTime());
Date date1 = new Date ();
System.out.println(date1.getTime()-date.getTime());
测试 String 字符串和 StringBuffer 字符串循环拼接 10 万次,程序运行耗时。
测试 String 字符串拼接速度。
public static void main (String[] args) {
Date date = new Date ();
String s1 = "w" ;
String;
for (int i = 0 ; i < 100000 ; i++) {
s3 = s3.concat(s1);
}
Date date1 = new Date ();
System.out.println("用时" +(date1.getTime()-date.getTime())+"毫秒" );
}
public static void main (String[] args) {
Date date = new Date ();
StringBuffer s2 = new StringBuffer ("w" );
StringBuffer s4 = new StringBuffer ("" );
for (int i = 0 ; i < 100000 ; i++) {
s4 = s4.append(s2);
}
Date date1 = new Date ();
System.out.println("用时" +(date1.getTime()-date.getTime())+"毫秒" );
}
在前文中我们了解了二者的区别,通过这次测试我们也验证了之前的结论:用 String 声明的字符串对象值一旦给定就不能改变了,每次拼接都会创建新的字符串对象,耗时且占用空间;而 StringBuffer 是内容可以改变的字符串,值改变的同时不需要创建新对象,所有拼接起来速度更快。
四、集合
在开发实践中,我们需要一些能够动态增长长度的容器来保存我们的数据,java 中为了解决数据存储单一的情况,java 中就提供了不同结构的集合类,可以让我们根据不同的场景进行数据存储的选择,如 Java 中提供了 数组实现的集合,链表实现的集合,哈希结构,树结构等。
体系图
Collection 接口 定义了单列集合共有的方法,其子接口Set 和List 分别定义了存储方式。
List 接口 继承了 Collection 接口,有三个实现的类,分别是:ArrayList (数组列表) | LinkedList (链表列表) | Vector 数组列表 (且线程安全)。
Set 接口 继承了 Collection 接口,有两个实现的类,分别是:HashSet | TreeSet 。
区别:
**List:**可以有重复元素
**Set:**不可以有重复元素
List 接口及实现类
ArrayList
底层有一个数组 ,可以动态扩展数组长度,并提供了一系列方法操作 ; 查询快,在中间增加 / 删除慢。
ArrayList<String> s=new ArrayList <>();
s.add("a" );
s.add("b" );
s.add("c" );
s.add("d" );
System.out.println(s.get(2 ));
s.remove(0 );
System.out.println(s);
s.set(0 ,"X" );
System.out.println(s);
s.size();
System.out.println(s.size());
s.addFirst("X" );
s.addLast("X" );
LinkedList
底层是一个链表 结构 ; 查询慢,但增加 / 删除元素快。(特点)
我们发现 ArrayList 和 LinkedList 的特点正好相反,原因如图:
Vector
和 ArrayList 一样,底层也是数组实现,不同的是 Vector 的方法默认加了锁**,线程是安全的**。
迭代器遍历 ( Iterator ) List 集合遍历有三种方式:for 循环,增强 for 循环和迭代器。
增强 for 循环 遍历元素时,不允许修改集合元素 (删除,添加)
for 循环 是允许操作 (删除) 元素的,但要注意索引的变化与元素位置的移动。
而使用迭代器即可以修改集合元素,且不用担心操作元素时索引的变化与元素位置的移动 (优点)
public static void main (String[] args) {
ArrayList<String> arrayList = new ArrayList <String>();
arrayList.add("a" );
arrayList.add("b" );
arrayList.add("c" );
arrayList.add("d" );
Iterator<String> it = arrayList.iterator();
while (it.hasNext()){
String s = it.next();
if (s.equals("a" )){
it.remove();
}
}
ListIterator 迭代器 只能对 List 接口下的实现类遍历。(条件)
从指定的位置开始向前或者向后遍历
public static void main (String[] args) {
ArrayList<String> arrayList = new ArrayList <String>();
arrayList.add("a" );
arrayList.add("b" );
arrayList.add("c" );
arrayList.add("d" );
ListIterator<String> listIterator = arrayList.listIterator(2 );
while (listIterator.hasNext()){
System.out.println(listIterator.next());
}
}
public static void main (String[] args) {
ArrayList<String> arrayList = new ArrayList <String>();
arrayList.add("a" );
arrayList.add("b" );
arrayList.add("c" );
arrayList.add("d" );
ListIterator<String> listIterator = arrayList.listIterator(arrayList.size());
while (listIterator.hasPrevious()){
System.out.println(listIterator.previous());
}
}
Set 接口及实现类
Set 中所储存的元素是不重复的,无序的,且 Set 中的元素没有索引。
由于 Set 中元素无索引,所有其实现类中没有 get() [通过索引获取指定位置元素] 且不能通过 for 循 环进行遍历
HashSet
HashSet 是一个不允许有重复元素的集合,是无序的,不是线程安全的。
public static void main (String[] args) {
HashSet set = new HashSet <>();
set.add("a" );
set.add("a" );
set.add("b" );
set.add("c" );
System.out.println(set);
HashSet set1 = new HashSet <>();
set1.add("c" );
set1.add("s" );
set1.add("x" );
set1.add("d" );
System.out.println(set1);
}
★ HashSet 在添加元素时,是如何判断元素重复的?(面试高频题)
在底层会先调用 hashCode(),注意,Object 中的 hashCode() 返回的是对象的地址,此时并不会调用;此时调用的是类中重写的 hashCode(),返回的是根据内容计算的哈希值,遍历时,会用哈希值先比较是否相等,会提高比较的效率;但哈希值会存在问题:内容不同,哈希值相同;这种情况下再调 equals 比较内容,这样既保证效率又确保安全。
TreeSet
TreeSet 可以根据值进行排序,底层使用了树形结构,树结构本身就是有序的。
TreeSet<Integer> treeSet =new TreeSet <>();
treeSet.add(2 );
treeSet.add(1 );
treeSet.add(4 );
treeSet.add(4 );
treeSet.add(3 );
System.out.println(treeSet);
向树形结构中添加元素时,如何判断元素大小以及元素是否重复?
向 TreeSet 中添加的元素类型必须实现 Comparable 接口,重写 compareTo() ; 每次添加元素时,调 用 compareTo() 进行元素大小判断 (小于 0 放左子结点,等于 0 表示重复,大于 0 放右子节点)
TreeSet 集合的遍历只能通过 增强 for 循环 和 迭代器 (Iterator) 遍历。(元素没有索引)
双列集合
Map 接口
数据存储是以 ( 键,值 ) 形式存储
键不能重复,值可以重复。
通过键找到值,一个键只能映射到一个值。
HashMap
HashMap<String,String> map =new HashMap <>();
map.put("a" ,"aa" );
map.put("w" ,"ww" );
map.put("c" ,"cc" );
map.put("s" ,"ss" );
map.put("a" ,"aaa" );
System.out.println(map);
输出:{a=aaa, c=cc, s=ss, w=ww}
HashMap<String,String> map =new HashMap <>();
map.put("a" ,"aa" );
map.remove("a" );
map.clear();
map.isEmpty();
map.containsKey("a" );
map.containsValue("aaa" );
map.get("s" );
map.size();
★ HashMap 底层存储数据的结构:(面试高频题)
底层使用了一个长度默认为16 的哈希数组,用来确定元素的位置,每次用 key 计算出哈希值,再 用哈希值%数组长度确定元素位置,将元素放在哈希表中指定的位置。
后来继续添加元素,如果出现位置相同且不重复的元素,那么将后来元素添加到之前元素的 next 节点。
当链表长度等于8 且哈希数组的长度大于64 时链表会自动转为红黑树。
补充: 哈希表负载因子为0.75 , 当哈希表使用数组的 0.75 倍时会自动扩容为原来数组长的 2 倍。
TreeMap
底层使用树形结构存储键值
键可以排序
键元素类型必须实现 Comparable 接口,重写 compareTo()
Hashtable
底层实现也是用到 key 的哈希值计算位置判断元素是否重复
方法上都添加了synchronized
Hashtable 中不能存储为 null 的键和为 null 值,但 HashMap 中可以。
HashMap<Integer,String> map =new HashMap <>();
map.put(1 ,"a" );
map.put(2 ,null );
map.put(null ,null );
System.out.println(map);
Hashtable<String,String> table =new Hashtable <>();
table.put(null ,"a" );
System.out.println(table);
Map 集合遍历
HashMap<String,String> map =new HashMap <>();
map.put("a" ,"aa" );
map.put("w" ,"ww" );
map.put("c" ,"cc" );
map.put("s" ,"ss" );
map.put("a" ,"aaa" );
System.out.println(map);
Set<Map.Entry<String,String>> entries =map.entrySet();
for (Map.Entry entry:entries){
System.out.println(entry.getKey()+":" +entry.getValue());
}
Set<String> keyset =map.keySet();
for (String key:keyset){
System.out.println(key+":" +map.get(key));
}
Collections 类 **概述:**Collections 是集合类的工具类,与数组的工具类 Arrays 类似。
sort(Comparator<? super E>):void List
对集合中的元素排序。
reverse(List<?>):void
反转集合中的元素。
shuffle(List<?>):void
打乱元素中的元素。
fill(List<? super T>,T):void
用 T 元素替换掉集合中的所有的元素。
copy(List<? super T>,List<? extend T>):void
复制并覆盖相应索引的元素
swap(List<?>,int,int):void
交换集合中指定元素索引的位置.
replaceAll(List,T,T):boolean
替换成指定的元素。
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
public class collections {
public static void main (String[] args) {
ArrayList<Integer> list =new ArrayList <>();
list.add(1 );
list.add(2 );
Collections.addAll(list,3 ,4 ,5 ,6 );
System.out.println(list);
Collections.sort(list);
System.out.println("升序:" +list);
System.out.println(Collections.binarySearch(list,5 ));
Collections.sort(list, new Comparator <Integer>() {
@Override
public int compare (Integer o1, Integer o2) {
return o2.intValue()- o1.intValue();
}
});
System.out.println("降序:" +list);
ArrayList<Integer> list1 =new ArrayList <>();
Collections.addAll(list1,1 ,2 ,3 ,4 );
Collections.fill(list1,5 );
System.out.println(list1);
Collections.replaceAll(list1,5 ,6 );
}
}
五、IO 流
File 类
一个 File 类的对象可以表示一个具体的文件或目录。
File 对象可以对文件或目录的属性进行操作,如:文件名、最后修改日期、文件大小等。
File 对象无法操作文件的具体数据,即不能直接对文件进行读/写操作
File(String pathname)指明详细的文件或目录的路径
File file = new File ("E:/demo1.txt" );
File directory = new File ("E:/temp" );
注意:delete 删除一个文件夹时,文件夹中必须是空的
File file = new File ("E:/demo3.txt" );
if (!file.exists()){
file.createNewFile();
}
file.delete();
mkdir( ) 创建单级文件夹 mkdirs( ) 创建多级文件夹
File directory = new File ("E:/demo" );
directory.mkdir();
File directorise = new File ("E:/demo/demo1/demo2" );
directorise.mkdirs();
如何得到一个目录中的所有文件:
思路:对目录进行遍历,遇到文件直接输出,遇到子目录再对子目录遍历,直到遇见文件
import java.io.File;
import java.io.IOException;
public class Review1 {
public static void main (String[] args) throws IOException {
File directory = new File ("E:/IO" );
search(directory);
}
public static void search (File file) {
File[] files =file.listFiles();
for (File f:files){
if (f.isFile()){
System.out.println(f);
}else {
search(f);
}
}
}
}
输入 (I) 与输出 (O)
输入和输出是一个相对概念,输入和输出是相对于我们的程序。
输入---Input
把电脑硬盘上的数据读到程序中,称为输入,即 input,进行数据的 read 操作。
输出---Output
从程序往外部设备写数据,称为输出,即 output,进行数据的 write 操作。
字节流与字符流
体系图
字节流:读取时以字节为单位,可以读取任意文件。
字符流:读取时以字符为单位,只能读取文本文件。
字节输入流 InputStream
字节输出流 OutputStream
字符输入流 Reader
字符输出流 Writer
常用类的基本方法 InputStream 和 OutputStream 的子类都是字节流,可以读写二进制文件,主要处理音频、图片、歌曲、字节流处理单元为 1 个字节。
读取一个字节并以整数的形式返回 (0~255),如果返回 -1 已到输入流的末尾。
int read() throws IOException
读取一系列字节并存储到一个数组 buffer,返回实际读取的字节数,如果读取前已到输入流的末尾返回 -1
int read(byte[] buffer) throws IOException
关闭流释放内存资源
void close() throws IOException
向输出流中写入一个字节数据,该字节数据为参数 b 的低 8 位
void write(int b) throws IOException
将一个字节类型的数组中的从指定位置(off)开始的 len 个字节写入到输出流
void write(byte[] b, int off, int len) throws IOException
关闭流释放内存资源
void close() throws IOException
Reader 和 Writer 的子类都是字符流,主要处理字符或字符串,字符流处理单元为 1 个字符。
读取一个字符并以整数的形式返回,如果返回 -1 已到输入流的末尾。
int read() throws IOException
读取一系列字符并存储到一个数组 buffer,返回实际读取的字符数,如果读取前已到输入流的末尾返回 -1。
int read(char[] cbuf) throws IOException
关闭 void close() throws IOException
向输出流中写入一个字符数据,该字节数据为参数 b 的 16 位
void write(int c) throws IOException
将一个字符类型的数组中的从指定位置(offset)开始的 length 个字符写入到输出流
void write(char[] cbuf, int offset, int length) throws IOException
关闭 void close() throws IOException
字节流读写文件 ① 首先在 D 盘中创建一个 ddd.docx 文件 (9.86KB),再在 E 盘中创建一个 eee.docx 文件 (空)
public static void main (String[] args) throws IOException {
FileInputStream inputStream1 = new FileInputStream ("D:/ddd.docx" );
FileOutputStream outputStream1 = new FileOutputStream ("E:/eee.docx" );
int a = 0 ;
while ((a=inputStream1.read())!=-1 ){
outputStream1.write(a);
}
inputStream1.close();
outputStream1.close();
}
③ 运行程序,可以发现我们成功将 ddd.docx 文件内容写到了 eee.docx 文件中 (0 字节➝9.86KB)
每次读写完后记得关闭通道,否则则文件一直处于打开 (占用) 状态。
高效文件读写:
创建一个 byte 数组,一次读 byte 数组长度个字节,便于提高读写效率。
public static void main (String[] args) throws IOException {
FileInputStream inputStream = new FileInputStream ("D:/demo1.txt" );
FileOutputStream outputStream=new FileOutputStream ("E:/IO.txt" );
byte [] bytes =new byte [10 ];
int size = 0 ;
while ((size=inputStream.read(bytes))!=-1 ){
outputStream.write(bytes,0 ,size);
}
inputStream.close();
outputStream.close();
}
字符流读写文件 ① 我们先创建一个 char1.txt 文本,并写入内容进行读写测试
② 通过代码实现将 char1.txt 文本的内容读写到 char2.txt 文本
public static void main (String[] args) throws IOException {
FileReader reader = new FileReader ("E:/char1.txt" );
FileWriter writer = new FileWriter ("E:/char2.txt" );
int b = 0 ;
while ((b=reader.read())!=-1 ) {
System.out.println(b);
writer.write(b);
}
reader.close();
writer.close();
}
字符流读取时以字符为单位,会将读到字节结合编码表转换为一个字符编码
上述写法的弊端:
每次运行程序会将之前所读写的内容覆盖带掉,不能做到在原内容的基础上续写
解决方法:在字符输出流的对象路径后加上true , 表示可续写。
此时当我们多次运行程序时,发现之前的所读写的内容依然存在
public static void main (String[] args) throws IOException {
FileReader reader = new FileReader ("E:/char1.txt" );
FileWriter writer = new FileWriter ("E:/char2.txt" ,true );
BufferedReader bufferedReader = new BufferedReader (reader);
BufferedWriter bufferedWriter = new BufferedWriter (writer);
String line = null ;
while ((line=bufferedReader.readLine())!=null ){
bufferedWriter.write(line);
bufferedWriter.newLine();
}
bufferedReader.close();
bufferedWriter.flush();
bufferedWriter.close();
}
注意:
由于读取一行的方法 readLine( ) 在 BufferedReader 中,换行方法 newLine( ) 在 BufferedWriter 类中,所以需要用到缓冲字符输入输出流。
此时当我们多次运行程序时,会将每次读取到的内容进行换行读写,便于记录数据。
节点流与处理流
节点流:直接封装的是文件,数据。
处理流:封装的是其他节点流对象;可以提供缓冲功能,提高读写效率。
字节输入流 FileInputStream
字节输出流 FileOutputStream
字符输入流 FileReader
字符输出流 FileWriter
缓冲字节输出流 BufferedOutputStream
缓冲字节输入流 BufferedInputStream
缓冲字符输入流 BufferedReader
缓冲字符输出流 BufferedWriter
public static void main (String[] args) throws IOException {
FileInputStream inputStream = new FileInputStream ("D:/demo1.txt" );
BufferedInputStream bufferedInputStream = new BufferedInputStream (inputStream,20 );
FileOutputStream outputStream = new FileOutputStream ("E:/IO.txt" );
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream (outputStream,20 );
int size = 0 ;
byte [] bytes =new byte [10 ];
while ((size=bufferedInputStream.read(bytes))!=-1 ){
bufferedOutputStream.write(bytes,0 ,size);
}
bufferedInputStream.close();
bufferedOutputStream.close();
}
对象输入输出流 ( 序列化 )
基础理论知识 怎么理解对象输入输出流?
把 java 中的对象输出到文件中,从文件中把对象输入到程序中。
为什么要这样做 (目的)?
当我们创建一个对象时,如 new Student("小张",20); 数据存储在对象中,对象是在内存中存储的,一旦程序运行结束,对象就会销毁,有时需要将对象的信息长久保存,就需要将对象输入到文件中。
(例如系统升级,关闭服务器时将对象保存起来,升级完毕后再重新把数据还原回来.)
把对象输入到文件的过程也称为对象的序列化。
把对象从文件输入到程序的过程称为对象的反序列化,反序列化时会生成一个新的对象,所以反序列化也是创建对象的一种方式。
注意:
当一个类的对象需要被序列化到文件时,这个类必须要生成一个序列化编号。
如果一个类需要被序列化到文件中,那么这个类就需要实现 Serializable 接口,实现后,会自动的为该类生成一个序列化编号。
编号是类的唯一标识,但是自动生成的编号在类信息改变后会重新为类生成一个编号。
可以在类中显示的生成一个编号,这样类信息修改后,编号也不会改变。
对象的输出流:ObjectOutputStream
对象的输入流:ObjectInputStream
在 ObjectInputStream 中用 readObject() 方法可以直接读取一个对象。
在 ObjectOutputStream 中用 writeObject() 方法可以直接将对象保存到输出流中。
生成序列化 ID 教程 如何在 IDEA 中设置,使可以在类中生成序列化 ID?(教程)
设置成功后,当我们把鼠标移至类名处,点击 serialVersionUID 即可生成编号
代码实践与测试 ① 我们首先创建一个学生类,需要将学生信息序列化到文件中,切记需要实现 Serializable 接口。
import java.io.Serializable;
public class Student implements Serializable {
private String name;
private int age;
public Student (String name, int age) {
this .name = name;
this .age = age;
}
@Override
public String toString () {
return "Student{" + "name='" + name + '\'' + ", age=" + age + '}' ;
}
}
② 进行序列化操作,将对象数据输出到文件中,使对象的信息可以做到持久化。
Student student = new Student ("小魏" ,20 );
FileOutputStream fileOutputStream = new FileOutputStream ("E:/obj.txt" );
ObjectOutput output = new ObjectOutputStream (fileOutputStream);
output.writeObject(student);
output.flush();
output.close();
③ 运行程序后发现,对象信息保存在了文件 obj.txt 中 (格式为乱码,但不影响最后反序列化输入结果)
④ 进行反序列化操作,将之前保存在文件 obj.txt 的对象信息输入到程序中。
public static void main (String[] args) throws IOException, ClassNotFoundException {
FileInputStream inputStream = new FileInputStream ("E:/obj.txt" );
ObjectInputStream objectInputStream = new ObjectInputStream (inputStream);
Student student = (Student) objectInputStream.readObject();
System.out.println(student);
objectInputStream.close();
}
六、异常处理机制 **本章学习内容:**使用异常处理机制,对程序运行过程中出现的异常情况进行捕捉并处理。
1. Java 异常概述
程序在运行过程中出现的不正常情况;
例如用户输入数据有问题,读写文件时文件被强制删除了,网络传输过程中突然断网...
出现异常后,会导致 jvm(虚拟机) 停止运行,后续程序无法执行。
异常指的并不是语法错误。(语法错误,编译不通过,不会产生字节码文件,根本不能运行)
异常的抛出机制:
java 中把不同的异常用不同的类表示,一旦发生某种异常,就创建该异常类型的对象,并且抛出;
然后程序员可以捕获到这个异常对象,并处理; 如果没有捕获这个异常对象,那么这个异常将会
导致程序终止。
将出现的异常,按不同的类型分类,为每种异常封装了一个类来进行标识。
当出现某种类型的异常情况时,会抛出此类的对象,然后终止虚拟机的运行。
异常的类型 (在哪种情况下出现,结合 API 定位)
异常原因
异常位置
2. Java 异常体系结构 java.lang.Throwable : 异常体系的超类
是虚拟机和 Java 代码无法解决的问题,例如虚拟机内部异常,内存不够用了。
堆溢出:OutOfMemoryError
栈溢出:StackOverflowError
这类异常时可以通过异常处理机制进行处理的一般性问题。
3. 常见的异常 ArithmeticException 算术异常
ArrayIndexOutOfBoundsException 数组索引越界
StringIndexOutOfBoundsException 字符串索引越界
ClassCastException 类型转换异常
NumberFormatException 数字格式化异常
NullPointerException 空指针异常
public static void main (String[] args) {
}
4. 异常处理
Java 中使用异常处理机制为程序提供了错误处理的能力
在编码时,就针对可能出现问题的代码,预先编写一些处理机制,当程序运行出现异常时执行处理机制,之后继续执行后续的程序。
Java 的异常处理是通过 5 个关键字来实现的:try 、catch 、 finally 、throw 、throws
try{...}catch(){...}
try{ 编写可能出现异常的代码 }catch(异常类型){ 处理机制 }
public static void main (String[] args) {
int a = 10 ;
int b = 0 ;
try {
int c = a/b;
}catch (ArithmeticException aindex){
aindex.printStackTrace();
System.out.println("算术异常" );
}
}
public static void main (String[] args) {
try {
String s = null ;
s.length();
int num = Integer.parseInt("1a" );
int [] b =new int [5 ];
for (int i = 0 ; i <=b.length ; i++) {
System.out.println(b[i]);
}
} catch (ArrayIndexOutOfBoundsException aindex) {
aindex.printStackTrace();
System.out.println("数组索引越界,越界索引:" +aindex.getMessage());
}catch (NumberFormatException nex){
nex.printStackTrace();
System.out.println("数字格式化异常:" +nex.getMessage());
}catch (Exception e){
e.printStackTrace();
System.out.println("系统繁忙~请稍后再试!" );
}
System.out.println("继续执行后续程序" );
}
当我们不确定捕获类型时可以使用 Exception; Exception 可以捕获任意类型,但是需要放在具体捕获类型后 catch,否则放到首部会覆盖其他具体的捕获类型。
finally{...}
finally 块中的内容总是会执行的,且只能有一个 finally 语句。
try{ 编写可能出现异常的代码 }catch(异常类型){ 处理机制 }finally{ 代码总能执行 }
当 catch 错误,异常没有被捕获到,后面的代码无法执行,但 finally 中的代码是可以执行的。
public static void main (String[] args) {
int [] a = new int [5 ];
try {
for (int i = 0 ; i <= a.length; i++) {
System.out.println(a[i]);
}
}catch (NumberFormatException nex){
nex.printStackTrace();
System.out.println("数字格式化异常:" +nex.getMessage());
}finally {
System.out.println("继续执行后续程序" );
}
}
public static void main (String[] args) throws IOException {
FileInputStream inputStream = null ;
try {
inputStream =new FileInputStream ("F:/demo.txt" );
}catch (FileNotFoundException e){
e.printStackTrace();
System.out.println("文件找不到异常" );
}finally {
if (inputStream!=null ){
inputStream.close();
}
}
}
七、网络编程
1. 网络编程概述 什么是计算机网络?
把分布在不同地理区域的计算机设备,通过物理线路连接起来,最终实现数据传输,资源共享。
什么是网络编程?
在网络的基础上,开发的程序能够进行数据传输。
java 语言是支持网络的,并且将网络连接的细节都封装起来了,对外提供一套网络库,就可以进行统一环境的网络编程
要进行网络数据传输的核心问题?
如何找到网络世界中的目标主机和程序?(IP 和端口)
找到后如何高效安全的进行数据传输?(协议)
OK 那就让我们带着这两个问题来一探究竟吧!
2. 网络模型 应用层 —— 内容 传输层 —— 加入协议控制
网络层 —— IP 物理层 —— 网线 光纤
IP 地址是指互联网协议地址,可以理解为计算机的地址;如 ipconfig 局域网 IP 192.168,本机回环地址 127.0.0.1 (访问自己电脑)
计算机运行的每个程序都对应分配一个整数的编号,不能重复;范围:065535;一般 01024 已经被系统使用或保留,例如 MySQl 的端口号是 3306.
是一种规范和约定,计算机网络中实现通信必须有一些约定,即通信协议;对速率,传输代码,代码结构,出错后如何应对...等制定规则。
3. TCP 协议 Transmission Control Protocol 传输控制协议
安全可靠,但传输效率低
① 客户端向服务器端发送数据前,首先要建立连接(测试网络是否通畅)
三次握手
客户端向服务器发送连接请求
当服务器接收到客户端连接请求后,给客户端做出一个回应
客户端为服务器的回应再次做出确认回应
客户端向服务器发送一个断开请求
服务器向客户端做出一个回应
服务器端把没有传完的数据传输完毕,再向客户端做出回应
客户端向服务器端的回应做出回应
4. UDP 协议 User Datagram Protocol 用户数据协议
把要发送的数据封装成一个数据包;数据包包含数据、对方的 IP、对方端口。
只管发送即可,是否发送成功是不知道的 (由于没有建立连接,故发送信息成功与否不晓得)
*优点:*不需要建立双方连接,传输速度快。
*缺陷:*由于没有建立连接,它是不安全的。
5. TCP 网络编程
new ServerSocket(端口)
ServerSocket 中的 accept() 方法来监听客户端的连接(此时程序阻塞)
try {
ServerSocket serverSocket = new ServerSocket (8888 );
System.out.println("服务器启动成功!" );
Socket socket= serverSocket.accept();
System.out.println("有客户连接到了服务器!" );
} catch (IOException e) {
e.printStackTrace();
}
try {
Socket socket = new Socket ("127.0.0.1" ,8888 );
} catch (IOException e) {
e.printStackTrace();
}
后续的操作就需要用到 IO 流了
在客户端输出一个字符串,并转换为字节的形式写入程序
public static void main (String[] args) {
try {
Socket socket=new Socket ("127.0.0.1" ,8888 );
String s="你好世界" ;
OutputStream outputStream=socket.getOutputStream();
outputStream.write(s.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
在服务器输入客户端输出的字节,并将其转为字符串进行打印
public static void main (String[] args) {
try {
ServerSocket serverSocket = new ServerSocket (8888 );
System.out.println("服务器启动成功!" );
Socket socket = serverSocket.accept();
System.out.println("有客户端连接到了服务器" );
InputStream inputStream=socket.getInputStream();
byte [] bytes =new byte [100 ];
int size = inputStream.read(bytes);
String s = new String (bytes,0 ,size);
System.out.println(s);
} catch (IOException e) {
e.printStackTrace();
System.out.println("服务器启动失败,端口已被占用" );
}
}
进阶:实现单线程聊天
我们通过 while 循环和 Scanner 控制台输入实现服务器与客户端交替聊天
我们使用包装流 DataInputStream,DataOutputStream 来输入输出,可以直接读到一个字符串,无需进行字符串与字节的转换。
static Scanner scanner = new Scanner (System.in);
public static void main (String[] args) {
try {
ServerSocket server = new ServerSocket (20049 );
System.out.println("服务器启动成功!" );
Socket socket = server.accept();
System.out.println("有客户端连接到了服务器!" );
while (true ) {
DataInputStream inputStream = new DataInputStream (socket.getInputStream());
String string = inputStream.readUTF();
System.out.println("服务器:" + string);
System.out.println("---->客户端:" );
String str = scanner.next();
DataOutputStream dataOutputStream = new DataOutputStream (socket.getOutputStream());
dataOutputStream.writeUTF(str);
}
} catch (IOException e) {
e.printStackTrace();
}
}
static Scanner scanner = new Scanner (System.in);
public static void main (String[] args) {
try {
Socket socket = new Socket ("127.0.0.1" , 20049 );
while (true ) {
System.out.println("--->服务器:" );
String s = scanner.next();
DataOutputStream output = new DataOutputStream (socket.getOutputStream());
output.writeUTF(s);
DataInputStream inputStream = new DataInputStream (socket.getInputStream());
String string = inputStream.readUTF();
System.out.println("服务器:" + string);
}
} catch (IOException e) {
e.printStackTrace();
}
}
补充:
本机回环地址:127.0.0.1
获取服务器的 IP(ipconfig 局域网 IP 以 192.168 开头)
八、线程
1. 区分程序、进程、线程 **程序:**为实现某种功能,使用计算机语言编写的一系列指令的集合。
指的是静态的代码(安装在电脑上的那些文件)
**进程:**是运行中的程序(如运行中的原神)进程是操作系统进行资源分配的最小单位。
**线程:**进程可以进一步细化为线程,是进程中一个最小的执行单元,是 cpu 进行调度的最小单元
例如:QQ 中的一个聊天窗口
进程和线程的关系:
⑴ 一个进程中可以包含多个线程(一个 QQ 程序可以有多个聊天窗口)
⑵ 一个线程只能隶属于一个进程(QQ 的聊天窗口只能属于 QQ 进程)
⑶ 每一个进程至少包含一个线程,也就是我们的主线程(像 java 中的 main 方法就是来启动主线程的)在主线程中可以创建并启动其他线程。
⑷ 一个进程的线程共享该进程的内存资源。
2. 创建线程
写一个类继承 java.lang.Thread
重写 run(
线程中要执行的任务都要写在 run() 中,或在 run() 中进行调用。
public class Demo1 extends Thread {
@Override
public void run () {
for (int i = 1 ; i <= 200 ; i++) {
System.out.println("run" +i);
}
}
}
public static void main (String[] args) {
Demo1 demo1 = new Demo1 ();
demo1.start();
for (int i = 1 ; i <= 200 ; i++) {
System.out.println("main" +i);
}
}
启动线程调用的是 start() ; 不是 run()
run() 这不是启动线程,只是一个方法调用,没有启动线程,还是单线程模式的。
创建任务,只先创建线程要执行的任务,创建一个类,实现 Runnable 接口。
重写任务执行的 Run()
创建线程,并为线程指定执行任务。
public class Demo2 implements Runnable {
@Override
public void run () {
for (int i = 0 ; i < 200 ; i++) {
System.out.println("自定义线程" );
}
}
}
public static void main (String[] args) {
Demo2 demo2 = new Demo2 ();
Thread thread = new Thread (demo2);
thread.start();
}
因为 java 是单继承,一旦继承一个类就不能在继承其他类,避免单继承的局限。
适合多线程来处理同一份资源时使用
3. Thread 类中的方法 run() 用来定义线程要执行的任务代码。
start() 启动线程
currentThread() 获取到当前线程 (.得到具体信息)
setName() 为线程设置名字
getState() 获取状态
getPriority() setPriority 获取/设置优先级
sleep() 让当前线程休眠指定时间。
join() 等待当前线程执行完毕,其他线程再执行。
yield() 主动礼让,退出 cpu 重新回到等待序列。
关于优先级:
【java 中默认优先级为 5, 设置优先级范围为 1~10】(作用:为操作系统调度算法提供的)
4. 线程生命周期 线程状态:
新建:刚刚创建了一个线程对象,并没有启动
就绪:调用 start() 后线程就进入到了就绪状态(可运行状态),进入到了操作系统的调度队列
运行状态:获得了 cpu 执行权,进入到 cpu 执行
阻塞状态:例如调用 sleep() ,有线程调用了 join(),线程中进行 Scanner 输入...
死亡/销毁:run() 方法中的任务执行完毕了
5. 多线程的概念 顾名思义指:在一个程序中可以创建多个线程执行。
【优点】提高程序执行效率(多个任务可以在不同的线程中同时执行)
提高了 cpu 的利用率
改善程序结构,将复杂任务拆分成若干个小任务
【缺点】线程也是程序,线程越多占用内存也越多,cpu 开销变大(扩充内存或升级 cpu)
线程之间同时对共享资源的访问会相互影响,若不加以控制会导致数据出错。
6. 线程同步与锁(Lock) 多个线程同时访问操作同一个共享的数据 (例如买票、抢购等) 时,可能会引起冲突,所以引入线程'同步'机制,即各线程间要有先来后到。
即通过【排队 + 锁】在关键的步骤处,多个线程只能一个一个的执行。
synchronized(同步锁)
同步锁对象作用:用来记录有没有线程进入到同步代码块,如果有线程进入同步代码块,那么其他线程就不能进入同步代码块,直到上一个线程执行完同步代码块的内容,释放锁之后,其他线程才能进入。
同步锁对象要求:同步锁对象必须是唯一的。
synchronized(同步锁对象){ 同步代码块 }
synchronized 修饰方法时,同步锁对象不需要我们指定,同步锁对象会默认提供:
非静态方法 ------ 默认是 this
静态方法 ------ 锁对象是当前类的 class 对象 (一个类的对象只有一个)
ReentrantLock 与 synchronized 区别?
synchronized 是一个关键字,控制依靠底层编译后的指令去实现。
synchronized 可以修饰一个方法或一个代码块。
synchronized 是隐式的加锁和释放锁,一旦方法或代码块出现异常,会自动释放锁。
ReentrantLock 是一个类,依靠 java 底层代码去控制 (底层有一个同步队列)
ReentrantLock 只能修饰代码块。
ReentrantLock 需要手动 的加锁和释放锁,所以释放锁最好写在 finally 中,一旦出现异常,保证锁能释放。
误区:不是只要有线程就需要加锁,只有多个线程对同一资源共享时才加锁
public class MyThread extends Thread {
static int num = 10 ;
static String obj = new String ();
@Override
public void run () {
while (true ){
synchronized (obj){
if (num>0 ){
try {
Thread.sleep(800 );
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"买到了第" +num+"张票" );
num--;
}else {
break ;
}
}
}
}
}
public static void main (String[] args) {
MyThread myThread1 = new MyThread ();
myThread1.setName("窗口 1" );
myThread1.start();
MyThread myThread2 = new MyThread ();
myThread2.setName("窗口 2" );
myThread2.start();
}
相关免费在线工具 Keycode 信息 查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
Escape 与 Native 编解码 JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
JavaScript / HTML 格式化 使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
JavaScript 压缩与混淆 Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
Gemini 图片去水印 基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online