华清远见-重庆中心-JAVA高级阶段个人总结

String字符串

概念

String 是一个类,属于数据类型中的引用类型。
Java 中一切使用 "" 引起来的内容,都是这个类的实例,称为字符串对象。
字符串 在定义后,值不可改变,是 一个常量 ,实际是一个 字符数组
// 这句话执行时,创建一个 "Tom" 字符串对象,将其地址保存在变量 name
String name = "Tom" ;
// 这句话执行看似在改变字符串的值,实际是创建了一个新的 "Jerry" 字符串对象,将其地址保存到变量 name中
name = "Jerry" ;
// 以上两句,在内存中,会有两个字符串对象 "Tom" "Jerry", 没有任何字符串发生了改变,只是 name引用 了不同的字符串地址

String类使用时注意:

右上方案例可见,如果频繁地将一个String类型变量的值进行更改时,会创建很多字符串对象。效率低,浪费内存空间。

所以在频繁更改字符串时,不要使用String类变量。

不同方式创建字符串的过程

1使用""赋值的形式创建

System . out . println ( str1 == str2 ); //true
System . out . println ( str1 == str3 ); //true
如图:

 2使用构造方法String(String str)创建

//1. 在字符串常量池中寻找 "ab", 不存在 , 创建
//2. 在堆中 new String(), 将字符串常量池中的 "ab" 保存到 new 出来的区域
//3. 将堆中 new 出来的地址保存到栈中变量 str1

String str1 = new String("ab");

//1. 在字符串常量池中寻找 "ab", 不存在 , 创建
//2. 在堆中 new String(), 将字符串常量池中的 "ab" 保存到 new 出来的区域
//3. 将堆中 new 出来的地址保存到栈中变量 str1
String str2 = new String ( "ab" );

3使用+拼接""new出来的字符串对象创建

// 在字符串常量池中创建 "ab"
String str1 = "ab" ;
//1. 创建 StringBuilder 对象
//2. 在字符串常量池中创建 "a"
//3. 在字符串常量池中创建 "b"
//4. 创建 String 对象
//5. 调用 StringBuilder append 方法,将 "a" new String("b") 拼接
String str2 = "a" + new String ( "b" ); // 一共创建了 "a","b",String,StringBuilder 这四个对
// 两个不同的地址
System . out . println ( str1 == str2 ); //false
如图:

 总结:

0.上述创建字符串对象先判断堆中字符串常量池是否已有该字符,如有,不创建对象,没有创建对象,使用+拼接""会创建StringBuilder对象然后调用StringBuilderappend方法,将"a"new String("b")拼接,如:String str = "a" + new String("b");//一共创建了"a","b",String,StringBuilder这四个对象。

1.在使用字符串时,如果要比较其值是否相同,不要使用==判断,因为==判断的是内存地址。

2.所以在比较字符串是否相同时,要使用String类重写的equals方法进行判断

3.equals方法,该方法判断的原理大致为:将两个字符串用字符数组保存,逐个判断字符数组中的每个字符,全部一致 时返回true。注意比较的是字面值。在使用equals方法时,通常将已知的非空字符串作为调用者

username . equals ( "admin" ); // 这样写, username 变量可能为空,会抛出空指针异常
"admin" . equals ( username ); // 这样写能避免空指针异常

字符串相关面试题

// 题目一
String str1 = "ab" ; // 常量池中创建 "ab"
String str2 = new String ( "ab" ); // 堆中 new String() 保存常量池中已有的 "ab"
String str3 = "a" + "b" ; // 用常量池已有的 "ab"
String str4 = "a" + new String ( "b" ); // 常量池中创建 "a" "b", 堆中 new String() new
StringBuilder()
String str5 = "ab" ; // 用常量池已有的 "ab"
System . out . println ( str1 == str2 ); //false
System . out . println ( str1 == str3 ); //true
System . out . println ( str1 == str4 ); //false
System . out . println ( str1 == str5 ); //true
// 题目四
// 这两句话执行后,会创建几个对象
String s5 = "hello" ; // 常量池: "hello"
String s6 = "hel" + new String ( "lo" ); // 常量池 :"hel" "lo" :new String() new
StringBuilder
//5 个对象:常量池: "hello" "hel" "lo" ,堆 :new String() new StringBuilder

字符串String类中的常用方法

 主要用于我们操作字符串

可变字符串

String 字符串对象是一个常量,在定义后,值不可改变
如果使用 String 类的对象,对其频繁更新时,就会不停地创建新的对象,不停引用给同一个变量

如要执行10000次循环重新赋值的过程,就要创建10000个字符串对象,执行效率很低,这时就需要使用可变字符串对象

StringBuilder

用于表示可变字符串的一个类,是 非线程安全 的,建议在单线程环境下使用

StringBuffer

用于表示可变字符串的一个类,是 线程安全 的,建议在多线程环境下使用
注意/区别:StringBuilder StringBuffer 中的方法都一致,只不过 StringBuffer 中的方法使用了 synchoronized关键字修饰 表示是一个同步方法,在多线程环境下不会出现问题

常用方法

作用

append(Object obj)                                将任意类型的参数添加到原可变字符串末尾
delete(int start,int end)                          删除[start,end)区间内的字符
deleteCharAt(int index)                         删除index索引上的字符
insert(int index,Object obj)                   在索引index上插入obj
replace(int start,int end,String str)       将[start,end)区间内的字符替换为 str
reverse()                                                 反转字符串

可变字符串与String之间的转换

String 转换为可变字符串
可变字符串转换为 String( 任意类型对象转换为 String)
方法一: String.valueOf(Object obj) 方法
方法二:对象 .toString();
String str = "hello" ;
// 通过构造方法将 String" 包装 " 为可变字符串对象
StringBuilder sb = new StringBuilder ( str );
StringBuilder sb = new StringBuilder ( " 你好 " );
// 调用静态方法
String str = String . valueOf ( sb ); 方法三:

可变字符串面试题

比较 String StringBuilder StringBuffer 的区别
相同点:
这三个类都可以表示字符串。都提供了一些操作字符串的方法。
这三个类中有相同的方法,如 charAt() indexOf()
这三个类都是 final修饰的类,不能被继承
不同点:
String 定义的字符串是一个常量 变字符串定义的字符串是一个变量
String 类中的方法,调用后,不会改变原本字符串的值;可变字符串类中的方法,调用后,会改变
原本字符串的值
StringBuilder 是非线程安全的可变字符串类, StringBuffer 是线程安全的可变字符串类,其中的方
法被 synchronized 修饰

总结

频繁操作同一个字符串时,一定要使用可变字符串 StringBuidlerStringBuffer 类的对象,不能使用 String类的对象。

System

这个类中包含了一些系统相关的信息和一些方法。其中的属性和方法都是静态的。
该类不能创建对象,不是因为它是一个抽象类,而是因为它的构造方法是私有的。

RunTime

Runtime 类的对象,表示 程序运行时对象 ( 程序运行环境对象 )
包含了程序运行环境相关的信息。常用于获取运行环境信息 ( 如虚拟机内存 ) 或执行某个命令
这个类不是一个抽象类,但不能创建对象,因为它的构造方法是私有的。
这个类提供了一个静态方法 getRuntime() ,通过这个方法,可以获取一个 Runtime 类的对象。
这是 Java 中的一种设计模式 -- 单例模式 ( 一个类只能有一个创建对象 )

方法调用时传值问题

参数只有是引用类型(类、数组、接口),并且方法中在直接操作该参数时,才会对实际参数造成影响。

Date

用于表示日期时间的类,位于 java.util 包下
SimpleDateFormat
用于格式化日期的类。

Calendar

表示日历的类,包含了很多日历相关的信息。
是一个抽象类,无法创建对象。可以通过静态方法 getInstance() 获取该类的一个实例。
// 获取 Calendar 类的对象
Calendar cal = Calendar . getInstance ();

包装类

Java 是纯面向对象语言,宗旨是将一切事物视为对象处理。
但原始类型不属于对象,不满足面向对象的思想。但原始类型在使用时无需创建对象,保存在栈中,效
率高。
为了让原始类型也有对应的类类型,达到 " 万物皆对象 " 的理念,所以就有了包装类的概念。
包装类就是原始类型对应的类类型。 包装类通常用于字符串与原始类型之间的转换。
包装类
原始类型
Byte                   byte
Short                 short
Integer                int
Long l                ong
Float                 float
Double             double
Character        char
Boolean           boolean
特点
八个原始类型中,除了 int char ,其余类型的包装类,都是将首字母改为大写 int 对应
Integer,char 对应 Character
包装类都是被 final 修饰的,不能被继承
除了 Character 类,其余包装类都有两个构造方法:参数为原始类型或 String 的构造方法。
Character 的构造方法只有一个,参数为 char 类型。这些构造方法用于将原始类型或字符串转换为
包装类对象
除了 Character 类,其余类都有 静态方法 parse 原始类型 (String str) ,用于将字符串转换为相应的
原始类型
        数值型的包装类的parseXXX()方法,如果参数不是对应的数字,转换时就会抛出         NumberFormat异常。如"123abc" ,或 "123.4" ,在使用 Integer.parseInt() 时都会抛出异常
        Boolean类型中的 parseBoolean() 方法,参数如果是 "true" 这四个字母,不区分大小写,都能
        转换为真正boolean 类型的 true ,只要不是 "true" 这个单词,转换结果都为 false
除了 Boolean 类,其余包装类都有 MAX_VALUE MIN_VALUE 这两个静态属性,用于获取对应类
型支持的最大最小值
所有包装类都重写了 toString() 方法,用于将包装类对象转换为 String 对象

字符串与原始类型之间的转换

如int类型

String num = "123" ;
int i = Integer . parseInt ( num ); //123
其他同理
原始类型转换为字符串
1使用 + 拼接一个空白字符串
2将原始类型转换为包装类后,调用 toString() 方法
3String.valueOf( 原始类型数据 )
装箱和拆箱
原始类型的数据转换为相应的包装类对象。这个过程称为装箱boxing
// 手动装箱
int i = 123 ; // 定义原始类型数据
Integer integer = Interger . valueOf ( i ); // 调用包装类 Integer 的静态方法 valueOf() 将原
始类型转换为包装类对象
用于将包装类对象转换为原始类型。这个过程称为拆箱unboxing
// 手动拆箱
Integer integer = new Integer ( 123 ); // 创建一个包装类对象
int i = integer . intValue (); // 调用包装类对象的 intValue() 方法将包装类对象转换为原始类

异常家族

处理异常

方式一:try-catch-finally语句

try{
//可能出现异常的代码
}catch(异常类 异常对象){
//如果出现异常对象,且与catch小括号中的异常类型匹配,就会执行这里的代码
}catch(异常类 异常对象){
//如果出现异常对象,且与catch小括号中的异常类型匹配,就会执行这里的代码
}finally{
//无论程序是否会抛出异常,都要执行的代码
}

finalfinallyfinalize的区别

final 是一个修饰符,被 final 修饰的属性称为常量,方法不能被重写,类不能被继承
finally try-catch-finally 结构中的关键字,在无论是否抛出异常,都会执行的代码块
finalize Object 类中的方法, finalize() 在某个对象被回收前调用的方法

方式二:throws关键字

这种方式,可以让编译时异常通过编译。

public class Test {
public void fun () throws InterruptException { // 这时该方法就会有一个声明:该方法可能
会抛出异常
// 这句话直接写完后,会报错,因为 sleep() 方法可能会抛出 InterruptException 异常,属于
编译时异常,必须要处理
Thread . sleep ( 500 );
}
}

集合框架(集合家族)

ArrayListLinkedList的区别

这两个类都是List接口的实现类保存的元素有 序可重复,允许保存 null
ArrayList 采用数组实现,随机读取效率高,插入删除效率低 适合用于查询
LinkedList 采用双向链表实现,插入删除时不影响其他元素,效率高,随机读取效率低 适合用于
频繁更新集合

泛型

一种规范,常用于限制集合中元素的类型,省去遍历元素时判断是否为对应类型和转型的过程

集合类或接口<引用数据类型> 集合变量名 = new 集合实现类();  

List < String > list = new ArrayList ();
// 当前集合只能保存 String 类型的元素
list . add ( "sdfsdf" );
//list.add(123);// 无法添加
List < Integer > list2 = new ArrayList ();
list2 . add ( 123 );

哈希表hash table

哈希表,也称为散列表,是一种数据结构,能更快地访问数据。

哈希码的特点
如果两个对象的 hashCode 不同,这两个对象一定不同
如果 两个对象的hashCode相同 ,这两个对象 不一定相同
hashCode 相同,对象不同,这种现象称为哈希冲突
" 通话 " " 重地 " 这两个字符串的 hashCode 相同,但是两个不同的对象

HashSet实现类

采用哈希表实现
元素不能重复,无序保存,允许保存一个 null
本质是一个 HashMap 对象
使用 HashSet 集合时,通常要重写实体类中的 equals hashcode 方法

 HashSet的应用

如果想要保存的对象保证不重复,且无关顺序,可以使用HashSet。如学生管理

TreeSet 实现类
1特殊的 Set 实现类,数据可以有序保存,可以重复,不能添加 nul
2采用红黑树 ( 自平衡二叉树 ) 实现的集合
3只能添加 同一种类型 的对象且该类 实现了 Comparable接口(必须要重写 compareTo() 方法
4compareTo() 方法的返回值决定了能否添加新元素和新元素的位置
5添加的元素可以自动排序
TreeSet 的应用
如果要保存的元素需要对其排序,使用该集合。

保存在其中的元素必须要实现Comparable接口,且重写compareTo()方法,自定义排序规则

Map接口

Map称为映射,数据以键值对的形式保存。保存的是键与值的对应关系。

文件类File

Java 中的 File 类,表示本地硬盘中的文件 ( 文件和目录 ) 的一个类。
通过这个类创建的对象,可以操作对应的文件。
// 如想要表示 “F:\221001\ 笔记 \ 面向对象部分回顾 .pdf” 这个文件
//File(String pathName)
File file1 = new File ( "F:\\221001\\ 笔记 \\ 面向对象部分回顾 .pdf" );
//File(String parent,String child)
File file2 = new File ( "F:\\221001\\ 笔记 " , " 面向对象部分回顾 .pdf" );
//File(File parent,String child)
File parent = new File ( "F:\\221001\\ 笔记 " );
File file3 = new File ( parent , " 面向对象部分回顾 .pdf" );
//file1 file2 file3 都表示同一个文件
斐波那契数列
package com.hqyj.FileTest;
public class Test2 {
public static void main(String[] args) {
//兔子问题
//有一对兔子在第三个月开始,每个月都能生一对小兔子
//如果所有兔子不死亡,且每次生下的都是一雌一雄,问10个月后共有多少对兔子
//1月 2月 3月 4月 5月 6月 7月 8月 9月 10月
//1 1 2 3 5 8 13 21 34 55
//斐波那契数列
//f(n)=f(n-1)+f(n-2) n>2
Test2 t = new Test2();
System.out.println(t.f(20));
}
/*
* 递归方法
* */
public int f(int n) {
if (n > 2) {
return f(n - 1) + f(n - 2);
}
return 1;
递归遍历文件夹
}
}

递归遍历文件夹

public class Test3 {
//查看某个目录下的所有文件
public static void main(String[] args) {
File source = new File("E:\\adobe");
Test3 t = new Test3();
t.fun(source);
}
/*
* 递归遍历文件夹
* */
public void fun(File source) {
//输出某个目录中超过3个月未使用且大于500MB的文件
/*
long start = source.lastModified();
long end = System.currentTimeMillis();
if ((end - start) / 1000 / 3600 / 24 > 90 && source.length() / 1024 /
1024 > 500) {
System.out.println(source.getName() + "\t" + new
Date(source.lastModified()) + "\t" + source.length() / 1024 / 1024);
}*/
//判断是否为目录
if (source.isDirectory()) {
//将其展开
for (File child : source.listFiles()) {
//因为子文件有可能是目录,继续调用本方法
fun(child);
}
}
 

}
}

Stream

流的分类

Java 中将流定义为类,以对象的形式表现流。流有 " 四大家族 " ,是所有流的父类。
字节输入流 InputStream
字节输出流 OutputStream
字符输入流 Reader
字符输出流 Writer

流的四个父类的特点

这四个父类都是在 java.io 包下,都是抽象类,不能直接创建其对象,使用其子类创建对象
FileInputStream文件字节输入流:按字节读取硬盘中的文件。读取的文件必须存在

FileOutputStream文件字节输出流:按字节将内存中的数据写入到硬盘中。文件可以不存在但父目录必须存在。

FileInputStream文件字节输入流

read()
读取一个字节,返回读取到的字节
read(byte[] bytes)
按字节数组读取,返回读取到的 字节数量 ,读取到的 内容保存在字节数组

FileOutputStream文件字节输出流

 

使用FileInputStream和FileOutputStream 读写时的注意事项

FileInputStream 对象使用 read(byte[] bytes) 方法时,每次读取指定数组的字节,将读取到
的字节保存在字节数组中,该方法返回读取到的字节数量如果最后一次读取的字节数不足字节数
组的大小时,只会将读取到内容覆盖数组中最前的几个元素。所以会导致读取到的内容多于实际内
容。
在通过 FileOutputStream 对象使用 write(byte[] bytes) 方法时,会将字节数组中的所有内容写入到
输出流中,在最后一次写入时,可能会写入多余的内容。所以在写入时,最好使用 write(byte[]
bytes,int off,int lef) 方法,表示将字节数组中的内容, off开始写入len
如有 word.txt 文件,其中保存 aaabbbccc
FileInputStream fis = new FileInputStream ( "d:/word.txt" );
FileOutputStream fos = new FileOutputStream ( "d:/copy.txt" );
byte [] bytes = new byte [ 4 ];
// 第一次读取 4 个字节,即 aaab count 4
int count = fis . read ( bytes );
// 写入数组中的全部内容
fos . write ( bytes );
// 第二次读取 4 个字节,即 bbcc count 4
count = fis . read ( bytes );
// 写入数组中的全部内容
fos . write ( bytes );
// 第三次读取 1 个字节 c ,覆盖数组中的第一个元素,即数组现在为 cbcc,count 1
count = fis . read ( bytes );
// 写入数组中的全部内容
fos . write ( bytes ); // 最终会写入 aaabbbcccbcc
fos . write ( bytes , 0 , count ); // 这样最后一次只会写入实际读取到的 c
fos . close ();
fis . close ();

使用FileInputStreamFileOutputStream 实现单文件的复制

import java.io.*;
public class CopyFile {
public static void main(String[] args) throws IOException {
//定义原文件和目标文件
File source = new File("F:\\221001\\录屏\\FileInputStream和
FileOutputStream.mp4");
File target = new File("F:\\221001\\copy.mp4");
//定义文件字节输入流,用于读取原文件
FileInputStream fis = new FileInputStream(source);
//定义文件字节输出流,用于写入文件
FileOutputStream fos = new FileOutputStream(target);
/*
//调用无参的read()方法,表示读取一个字节,返回读取到的字节
int read = fis.read();
//如果能读取到内容
while (read > -1) {
//将读取到的内容写入到文件中
fos.write(read);
//继续读取
文件夹的复制
read = fis.read();
}
*/
//定义一个字节数组,大小为8MB
byte[] bytes = new byte[1024 * 1024 * 8];
//按字节数组读取,返回读取到的字节数量
int count = fis.read(bytes);
//循环读取写入
while (count > -1) {
//将读取的字节数组写入到文件中
// fos.write(bytes);//如果调用该方法,最后一次会多写入上一次残留的数据
fos.write(bytes,0,count);//如果调用该方法,实际读取到了多少字节就写入多少
count = fis.read(bytes);
}
fis.close();
fos.close();
if (target.exists()) {
System.out.println("复制成功");
}
}
}

BufferedReader缓冲字符输入流

自带缓冲区 ( 字符数组 ) 的字符输入流。默认字符数组大小为 8192 ,每次最多读取 8192 个字符。
在读取纯文本文件 (txt md) 时,首选该类。
读取文本练习
import java.io.*;
public class Test2 {
public static void main(String[] args) throws IOException {
/*
File file = new File("F:\\221001\\笔记\\Java基础回顾.md");
//FileReader(File file)
Reader fr = new FileReader(file);
//BufferedReader(Reader in)
BufferedReader br = new BufferedReader(fr);
*/
//创建带有缓冲区的字符输入流对象
BufferedReader br = new BufferedReader(new FileReader("F:\\221001\\笔记
\\Java基础回顾.md"));
//循环判断是否还有字符
while (br.ready()) {
//读取整行
System.out.println(br.readLine());
}
//关闭最大的流对象即可
br.close();
}
}

BufferedWriter缓冲字符输出流

自带缓冲区(字符数组)的字符输出流

写入文本练习

import java.io.*;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.logging.SimpleFormatter;
public class Test3 {
public static void main(String[] args) throws IOException {
File file = new File("221001.txt");
//创建缓冲字符输入流对象,读取文本
BufferedReader br = new BufferedReader(new FileReader(file));
//创建集合,保存读取到的姓名
ArrayList<String> list = new ArrayList<>();
//循环读取文件中的所有字符
while (br.ready()) {
String name = br.readLine();
list.add(name);
}
//关闭
br.close();
//打乱集合中的元素
Collections.shuffle(list);
//创建日期字符串
String today = new SimpleDateFormat("yyyy.MM.dd").format(new Date());
//创建缓冲字符输出流,用于写文本,文件名为"日期+作业情况.txt",如果每次都是新建,这样
写
// BufferedWriter bw = new BufferedWriter(new FileWriter(today + "作业情
况.txt"));
//如果要追加,在new FileWriter("文件名",true)设置
BufferedWriter bw = new BufferedWriter(new FileWriter(today + "作业情
况.txt",true));
//写入字符串
bw.write("姓名\t\t是否完成");
//换行
bw.newLine();
Scanner sc = new Scanner(System.in);
//随机3个人
for (int i = 0; i < 3; i++) {
String name = list.get(i);
构造方法 说明
ObjectOutputStream(OutputStream
os)
创建一个对象字节输出流对象,参数为一个字节输出
流对象,由于OutputStream是抽象类,所以使用其
子类,如FileOutputStream对象,在其中定义要写入
的文件
常用方法 作用
writeObject(Object obj) 将一个对象写入到本地文件中
close() 关闭流对象
ObjectOutputStream对象字节输出流(序列
化)(掌握)
序列化:将对象转换为文件的过程
被序列化的对象,必须要实现Serializable接口。
这个接口是一个特殊的接口,没有定义任何方法,只是给该类加上标记,表示该类可以被序列化
构造方法
常用方法
ObjectInputStream对象字节输入流(反序列
化)(掌握)
反序列化:将文件转换为对象的过程
构造方法
System.out.println(name + "完成情况:");
String str = sc.next();
//写入读取到的内容
bw.write(name + "\t\t" + str);
//换行
bw.newLine();
}
bw.close();
}
}

ObjectOutputStream对象字节输出流(序列)

序列化:将对象转换为文件的过程
被序列化的对象,必须要实现 Serializable 接口。
这个接口是一个特殊的接口,没有定义任何方法,只是给该类加上标记,表示该类可以被序列化。

ObjectInputStream对象字节输入流(反序列)

反序列化:将文件转换为对象的过程

网络编程

InetAddress
public static void main(String[] args) throws UnknownHostException {
//获取本机的ip对象
 InetAddress ip = InetAddress.getLocalHost();
//获取域名
 System.out.println(ip.getHostName());
//获取真实ip地址
 System.out.println(ip.getHostAddress()); }

Socket类和ServerSocket

都属于 Socket( 套接字) 对象,表示网络中的某个端点
Socket指普通端
ServerSocket指服务器端

使用套接字对象实现两个端点(SocketServerSocket)之间发送文件

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
/*
* 使用套接字对象,实现客户端向服务端发送文件
*
* 定义服务端套接字对象客户端
* */
public class Server {
public static void main(String[] args) throws IOException {
//以本机创建服务端套接字对象
ServerSocket server = new ServerSocket(8899, 100,
InetAddress.getLocalHost());
//等待客户端连接,返回连接的客户端套接字对象
Socket client = server.accept();
//定义要将读取到的数据写入到本地的文件字节输出流对象
FileOutputStream fos = new FileOutputStream("上传文件.md");
//获取客户端与服务端的输入流对象,读取发送的数据
InputStream is = client.getInputStream();
//定义读取的字节数组
byte[] bytes = new byte[1024 * 1024 * 8];
int count = is.read(bytes);
while (count != -1) {
//将读取到的数据写入到本地
fos.write(bytes, 0, count);
count = is.read(bytes);
}
fos.close();
is.close();
}
}
package com.hqyj.uploadTest;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
/*
* 定义客户端套接字对象
* */
public class Client {
public static void main(String[] args) throws IOException {
//创建客户端套接字对象,连接指定的服务端套接字对象
Socket client = new Socket("192.168.31.39", 8899);
//获取客户端与服务端的输出流对象
OutputStream os = client.getOutputStream();
//成功连接后,将某个文件发送给服务端
//定义要发送的文件对象
File file = new File("F:\\221001\\笔记\\面向对象部分回顾.md");
//读取要发送的文件
FileInputStream fis = new FileInputStream(file);作业
//定义字节数组
byte[] bytes = new byte[1024 * 1024 * 8];
//循环读取要发送的文件
int count = fis.read(bytes);
while (count != -1) {
//将读取到的数据写入到客户端套接字与服务端套接字的通道中
os.write(bytes,0,count);
count = fis.read(bytes);
}
fis.close();
os.close();
}
}

进程和线程

进程Process

进程就是操作系统中执行的程序。一个程序就是一个执行的进程实体

每个运行中的进程,都有属于它独立的内存空间,各个进程互不影响。

线程Thread

线程是一个进程中的执行单元,一个进程中可以有多个线程

多个线程,可以访问同一个进程中的资源

每个线程都有一个独立的栈空间,这些线程所在的栈空间位于同一个进程空间中。

多线程

如果一个进程中,同时在执行着多个线程,就称为多线程。

多线程可以提高程序执行效率。如多个窗口卖票,可以加快卖票的效率。

其实每个执行的Java程序,都是多线程执行,main方法称为主线程,还有gc线程(守护线程)在同时运行。

如有一个工厂,工厂中有很多车间,每个车间有很多流水线。

工厂就是内存,车间就是各个进程,每个流水线都是一个进程中的一个线程

并行和并发

并行

各个进程同时执行,称为并行。

并发

多个线程同时执行,称为并发。

同步和异步

同步

所有的任务排队执行,称为同步执行。

异步

在执行任务A的同时,执行任务B,称为异步执行。

Java中的线程Thread类

Java中,线程以对象的形式存在

Thread类表示线程类

获取线程对象

  • 获取当前正在运行的线程对象

    Thread ct = Thread.cuurentThread();

实现多线程

方式一:继承Thread类

  • 1.创建一个类,继承Thread类
  • 2.重写Thread类中的run()方法
  • 3.创建自定义的线程子类对象后,调用start()方法

自定义Thread线程的子类

package com.hqyj.ThreadTest;

/*
 * 实现多线程步骤
 * 1.成为Thread的子类
 * 2.重写run()方法
 * 3.创建当前类对象后,调用start()方法
 * */
public class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            //让该线程输出0-99
            System.out.println(getName() + ":" + i);
        }
    }

    public MyThread(String name) {
        super(name);
    }

    public MyThread() {
    }
}

main类

package com.hqyj.ThreadTest;

public class Test2 {
    public static void main(String[] args) {

        //创建无参数的自定义线程对象
        MyThread t1 = new MyThread();
        t1.setName("线程A");
        //创建自定义线程对象,参数为线程名
        MyThread t2 = new MyThread("线程B");

        //让两个线程自动执行,必须调用start()
        t1.start();
        t2.start();
    }
}

方式二:实现Runnable接口(建议使用)

由于Java中是单继承,如果某个类已经使用了extends关键字去继承了另一个类,这时就不能再通过extends继承Thread实现多线程。

就需要实现Runnable接口的方式实现多线程。

  • 1.自定义一个类,实现Runnable接口
  • 2.重写run()方法,将多线程要执行的内容写在该方法中
  • 3.创建Runnable接口的实现类对象
  • 4.使用构造方法Thread(Runnable target)或Thread(Runnable target,String name)将上一步创建的Runnable实现类对象包装为Thread对象

自定义Runnable接口的实现类

package com.hqyj.ThreadTest;
/*
 * 实现多线程步骤
 * 1.成为Runnable的实现类
 * 2.重写run()方法
 * 3.创建该类对象
 * 4.将其包装为Thread对象
 * */
public class MyThread2 implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            //让该线程输出0-99
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }
}

main类

package com.hqyj.ThreadTest;

public class Test2 {
    public static void main(String[] args) {

        //创建Runnable接口的实现类
        Runnable target = new MyThread2();
        //由于启动多线程必须要通过Thread的start()方法,所以一定要创建Thread对象
        Thread mt = new Thread(target,"线程A");//这里使用Thread(Runnable target)构造方法创建Thread对象
        //让线程就绪
        mt.start();
        //创建另一个线程对象,让线程就绪
        new Thread(new MyThread2(),"线程B").start();
    }
}

方式三:使用匿名内部类

如果不想创建一个Runnable接口的实现类,就可以使用匿名内部类充当Runnable接口的实现类

package com.hqyj.ThreadTest;

/*
 * 实现多线程的方式三:
 * 使用匿名内部类
 * */
public class Test3 {
    public static void main(String[] args) {


        //使用Thread(Runnable target ,String name)构造方法创建线程对象
        //此时new Runnable() { @Override public void run() {}}就是一个匿名内部类
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println(Thread.currentThread().getName() + ":" + i);
                }
            }
        }, "自定义线程").start();

        //如果main方法当做一个线程时,需要先启动其他线程后,在执行main方法中的内容,否则依然是按顺序执行
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }

}

线程的生命周期

线程的初始化到终止的整个过程,称为线程的生命周期。

守护线程

如果将一个线程设置setDeamon(true),表示该线程为守护线程。

守护线程会随着其他非守护线程终止而终止

多线程访问同一个资源可能出现的问题

如银行存款100,同一时刻在手机和ATM一起取出,如果用多线程模拟,可能会出现两个线程都取出100的情况。要避免这种情况发生。

出现问题的原因

由于线程调用start()方法后,就进入就绪状态。如果获得了CPU时间片,就开始调用run()方法,调用run()方法后,就会再次进入就绪状态,不会等待run()方法执行完毕,所以在线程A执行run()方法的时候,线程B也开始执行了,这样就会出现数据共享的问题。

因为现在所有的线程都是异步(同时)执行。

如何解决

让线程同步(排队)执行即可。这样一来,某个线程执行run()方法的时候,让其他线程等待run()方法的内容执行完毕。

synchronized关键字

这个关键字可以修饰方法或代码块

修饰方法

写在方法的返回值之前,这时该方法就称为同步方法。

public synchronized void fun(){
    //会排队执行的代码
}

修饰代码块

写在一个独立的{}前,这时该段内容称为同步代码块。

synchronized(要同步的对象或this){
    //会排队执行的代码
}

原理

每个对象默认都有一把"锁",当某个线程运行到被synchronized修饰的方法时该对象就会拥有这把锁,在拥有锁的过程中,其他线程不能同时访问该方法,只有等待其结束后,才会释放这把锁。

使用synchronized修饰后的锁称为"悲观锁"。

方法被synchronized修饰后,称为同步方法,就会让原本多线程变成了单线程(异步变为同步)。

多线程相关面试题

  • 实现多线程的方式

    • 继承Thread类
    • 实现Runnable接口后,包装为Thread对象
    • 匿名内部类
  • 为什么说StringBuilder或ArrayList、HashMap是非线程安全的

    package com.hqyj.ThreadSafe;
    
    public class Test {
        public static void main(String[] args) throws InterruptedException {
            // StringBuilder sb = new StringBuilder();
            StringBuffer sb = new StringBuffer();
            //循环10次创建10个线程对象
            for (int i = 0; i < 10; i++) {
                //创建线程对象
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        //每个线程都向StringBuilder对象中添加100次字符串
                        try {
                            Thread.sleep(1);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        for (int j = 0; j < 10; j++) {
                            sb.append("hello");
                        }
                    }
                }).start();
            }
            Thread.sleep(5000);
            //如果正常,应该长度为10线程*10次添加*每次5个字母  长度为500
            System.out.println(sb.length());
            //如果用StringBuilder,最终的长度可能不为500
            //如果用StringBuffer,最终的长度一定为500
            //所有StringBuffer是线程安全的,适用于多线程
            //所有StringBuilder是非线程安全的,适用于单线程
        }
    }
  • 什么叫死锁?怎么产生?如何解决?

    如果有两个人吃西餐,必须有刀和叉,此时只有一副刀叉。

    如果A拿到了刀,B拿到了叉,互相都在等待另一个工具,但都不释放自己拥有的,这时就会造成僵持的局面,这个局面就称为死锁,既不结束,也不继续

  • 死锁的解决方式

    方式一

    让两个线程获取资源的顺序保持一致。

    如两个线程都先获取knife,再获取fork

    @Override
    public void run() {
        synchronized (knife) {
            System.out.println(Thread.currentThread().getName() + "获取了knife,3s后获取fork");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (fork) {
                System.out.println(Thread.currentThread().getName() + "获取了fork,可以吃饭了");
            }
        }
    }

    方式二

    让两个线程在获取资源A和B之前,再获取第三个资源,对第三个资源使用synchronized进行同步,这样某个线程在获取第三个资源后,将后续内容执行完毕,其他线程才能开始执行。

    如在获取knife和fork之前,先获取paper对象

    @Override
    public void run() {
        //先获取paper,再进行后续操作
        synchronized (paper) {
            synchronized (knife) {
                System.out.println(Thread.currentThread().getName() + "获取了knife,3s后获取fork");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (fork) {
                    System.out.println(Thread.currentThread().getName() + "获取了fork,可以吃饭了");
                }
            }
        }
    }

总结: 以上就是在Java高级阶段所学的部分基础知识,涵盖不全但是都是基础知识和核心原理,这个阶段比前面阶段难度上升了,知识点多杂,需要进行梳理,总结。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值