String字符串
概念
![](https://i-blog.csdnimg.cn/blog_migrate/6433c26956e1108110578e533af6474e.png)
String类使用时注意:
右上方案例可见,如果频繁地将一个String类型变量的值进行更改时,会创建很多字符串对象。效率低,浪费内存空间。
所以在频繁更改字符串时,不要使用String类变量。
不同方式创建字符串的过程
1使用""赋值的形式创建
![](https://i-blog.csdnimg.cn/blog_migrate/32fd972e16a9ffdd93290ee5f2067855.png)
2使用构造方法String(String str)创建
String str1 = new String("ab");
3使用+拼接""和new出来的字符串对象创建
总结:
0.上述创建字符串对象先判断堆中字符串常量池是否已有该字符,如有,不创建对象,没有创建对象,使用+拼接""会创建StringBuilder对象然后调用StringBuilder的append方法,将"a"和new String("b")拼接,如:String str = "a" + new String("b");//一共创建了"a","b",String,StringBuilder这四个对象。
1.在使用字符串时,如果要比较其值是否相同,不要使用==判断,因为==判断的是内存地址。
2.所以在比较字符串是否相同时,要使用String类重写的equals方法进行判断。
3.equals方法,该方法判断的原理大致为:将两个字符串用字符数组保存,逐个判断字符数组中的每个字符,全部一致 时返回true。注意比较的是字面值。在使用equals方法时,通常将已知的非空字符串作为调用者。
字符串相关面试题
字符串String类中的常用方法
主要用于我们操作字符串
![](https://i-blog.csdnimg.cn/blog_migrate/bd3ab81fe0c34fc1e8e8967de10cf331.png)
可变字符串
如要执行10000次循环重新赋值的过程,就要创建10000个字符串对象,执行效率很低,这时就需要使用可变字符串对象。
StringBuilder类
StringBuffer类
常用方法
作用
可变字符串与String之间的转换
可变字符串面试题
总结
System类
RunTime类
方法调用时传值问题
参数只有是引用类型(类、数组、接口),并且方法中在直接操作该参数时,才会对实际参数造成影响。
Date类
Calendar类
包装类
字符串与原始类型之间的转换
如int类型
异常家族
![](https://i-blog.csdnimg.cn/blog_migrate/0e9fadb5f3c6764e07fc65658c0e9eef.png)
处理异常
方式一:try-catch-finally语句
try{
//可能出现异常的代码
}catch(异常类 异常对象){
//如果出现异常对象,且与catch小括号中的异常类型匹配,就会执行这里的代码
}catch(异常类 异常对象){
//如果出现异常对象,且与catch小括号中的异常类型匹配,就会执行这里的代码
}finally{
//无论程序是否会抛出异常,都要执行的代码
}
![](https://i-blog.csdnimg.cn/blog_migrate/ea34da0e3a0f28651b9f0fc226234d4a.png)
final、finally、finalize的区别
方式二:throws关键字
这种方式,可以让编译时异常通过编译。
集合框架(集合家族)
ArrayList和LinkedList的区别
泛型
集合类或接口<引用数据类型> 集合变量名 = new 集合实现类();
哈希表hash table
哈希表,也称为散列表,是一种数据结构,能更快地访问数据。
HashSet实现类
HashSet的应用
如果想要保存的对象保证不重复,且无关顺序,可以使用HashSet。如学生管理
保存在其中的元素必须要实现Comparable接口,且重写compareTo()方法,自定义排序规则
Map接口
Map称为映射,数据以键值对的形式保存。保存的是键与值的对应关系。
文件类File
斐波那契数列
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
流的分类
流的四个父类的特点
FileOutputStream文件字节输出流:按字节将内存中的数据写入到硬盘中。文件可以不存在,但父目录必须存在。
FileInputStream文件字节输入流
FileOutputStream文件字节输出流
使用FileInputStream和FileOutputStream 读写时的注意事项
使用FileInputStream和FileOutputStream 实现单文件的复制
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缓冲字符输入流
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对象字节输出流(序列化)
ObjectInputStream对象字节输入流(反序列化)
网络编程
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和 ServerSocket)之间发送文件
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);
}
}
}
线程的生命周期
线程的初始化到终止的整个过程,称为线程的生命周期。
![](https://i-blog.csdnimg.cn/blog_migrate/fe77c4ae86ec8844b62dbbb7ad9ef53c.png)
守护线程
如果将一个线程设置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高级阶段所学的部分基础知识,涵盖不全但是都是基础知识和核心原理,这个阶段比前面阶段难度上升了,知识点多杂,需要进行梳理,总结。