目录
String字符串
定义
字符串对象的含义
关于字符串对象内存地址的问题
1.使用“”赋值创建字符串对象
2.通过构造方法创建字符串对象
3.使用+拼接""和new出来的字符串对象创建字符串对象 (难点)
比较字符串时为什么要使用equals方法而不用“==”
两者比较的本质
使用equals方法的注意点
可变字符串
使用StringBuilder和StringBuffer时的注意点
可变字符串相关面试题
方法调用的传值问题
与八大原始类型对应的包装类
异常
方法
try-catch-finally
try-catch-finally相关面试题
throws关键字
数组和集合的区别
泛型
集合家族
Collection接口
常用方法
lterable接口为什么不是集合的根接口
Collections集合工具类
list接口
实现类接口-ArrayList接口
实现类接口-LinkedList接口
set接口
实现接口-HashSet接口
实现接口-TreeSet接口
特点
集合和数组之间的转换
Map接口
实现类-HashMap接口
遍历集合中元素的方式
数组管理系统 VS 集合管理系统
使用数组完成-ATM系统
使用(List集合)ArrayList实现-新闻管理系统
使用(Map集合)HashMap实现-用户管理系统
文件类File
IO
流Stream
使用FileInputStream和FileOutputStream实现-单文件的复制
文件夹的复制
序列化和反序列化应用
网络编程
InetAddress类
Socket类和ServerSocket类
使用套接字对象实现两个端点(Socket和ServerSocket)之间发送文件
进程和线程
并行和并发
同步和异步
Java中的线程Thread类
实现多线程
生命周期
守护线程
多线程访问同一个资源
死锁
多线程相关面试题
个人总结
String字符串
定义
是一个属于数据类型的引用类型,本质是一个类。
字符串对象的含义
使用“”引起来的内容
关于字符串对象内存地址的问题
1.使用“”赋值创建字符串对象
说明:
- 第一段语句:创建了“Java”的字符串对象,确实将这个字符串对象的地址保存在了变量name中。(在字符串常量池中没找到“Java”字符,就会创建一个新的“Java”字符串对象)
- 第二段语句:表面上是重新给name变量赋了值,实质是创建了新的“OK”字符串对象。(过程就是执行时,发现“OK”在字符串常量池中不存在,就会创建一个新的“OK”字符串对象)
- 第三段语句:表面上创建了新的字符串对象,其实是直接引用“Java”字符对象(这时它已经在常量池中找到了,所以就直接使用。)
- 第四段语句:+两端都是""定义的字符串,值得注意的是:它是在拼接后再判断字符串常量池(缓冲区)中是否存在(拼接后的"Java"依然存在,将其地址保存到thing变量中)
以上变量(除却name创建新的“OK”字符串对象)的内存地址都是相等的(用的“==”做判断)
2.通过构造方法创建字符串对象
说明:
- 第一段语句:在字符串常量池中寻找"Java",不存在,所以在堆中newString(),将字符串常量池中的"Java"保存到new出来的区域,最后将堆中new出来的地址保存到栈中变量str1中。
- 第二段语句:在字符串常量池中寻找"Java",存在,直接引用,同样的在堆中newString(),将字符串常量池中的"ab"保存到new出来的区域,最后将堆中new出来的地址保存到栈中变量str2中。
以上str1和str2的内存地址不同(用的“==”做判断)(原因:str1和str2是堆中的两个区域)
3.使用+拼接""和new出来的字符串对象创建字符串对象 (难点)
说明:
- 创建StringBuilder对象
- 在字符串常量池中创建"Ja"
- 在字符串常量池中创建"va"
- 创建String对象,保存"va"的地址
- 调用StringBuilder的append方法,将"Ja"和newString("va")拼接
以上过程一共创建4个对象("Ja","va",String, StringBuilder对象)
比较字符串时为什么要使用equals方法而不用“==”
两者比较的本质
- “==”判断的是内存地址
- 使用String类重写的equals方法进行判断,就是将两个字符串用字符数组保存,逐个判断字符数组中的每个字符,全部一致时返回true,所以比较的是字面值。
使用equals方法的注意点
要将已知不为空的字符串作为调用者,才不会出现空指针异常的问题。
可变字符串
使用StringBuilder和StringBuffer时的注意点
- 普通方法都是在直接操作同一个字符串对象,每次调用方法后,原字符串都会发生变化
- StringBuffer和StringBuilder并没有重写equals方法,所以可变字符串的值是否相同时,调用的是equals中原始的==判断。如果要判断两个可变字符串的值是否相同时,需要将其转换为String后调用equals判断
可变字符串相关面试题
比较String,StringBuilder和StringBuffer的区别
- 相同点
- 都可以表示字符串。都提供了一些操作字符串的方法。
- 都有相同的方法,如charAt()、indexOf等。
- 都是被final修饰的类,不能被继承。
- 不同点
渝中java基础
- String定义的字符串是一个常量,可变字符串定义的字符串是一个变量。
- String类中的方法,调用后,不会改变原本字符串的值;可变字符串类中的方法,调用后,会改变原本字符串的值。
- StringBuilder是非线程安全的可变字符串类,StringBuffer是线程安全的可变字符串类,其中的方法被synchronized修饰。
- 总结
在频繁操作同一个字符串时,一定要使用可变字符串StringBuidler或StringBuffer类的对象,不能使用String类的对象。
方法调用的传值问题
只有参数是引用类型(类,数组,接口),在方法中是直接操作该参数,才会对实际参数造成影响。(引用类型如果在方法中再创建新对象则不会对实际参数造成影响)
与八大原始类型对应的包装类
异常
方法
try-catch-finally
原理
注意点
- try、catch、finally都不能单独使用,try需要配合catch或finally或catch和finally一起使用。
- 执行try中的内容时,当某行代码抛出异常,不再执行try中该行代码后续的内容。
- 无论try中的代码是否会抛出异常,finally中的代码一定会执行。
- 如果代码会抛出多个异常,可以使用多个catch进行捕获,需要将异常子类放在最前,异常父类放在最后。
- 如果代码会抛出多个异常,可以使用多个catch进行捕获,需要将异常子类放在最前,异常父类放在最后。
- 在try中定义的内容,无法在try之外的地方使用。
- try中如果有return,不影响finally的执行,finally优先于return执行。
try-catch-finally相关面试题
final、finally、finalize的区别
- final是一个修饰符,被final修饰的属性称为常量,方法不能被重写,类不能被继承。
- finally是try-catch-finally结构中的关键字,在无论是否抛出异常,都会执行的代码块。
- finalize是Object类中的方法,finalize()在某个对象被回收前调用的方法。
throws关键字
throw和throws的区别
- throws表示用于声明方法有可能出现的异常。使用时写在方法的小括号之后。
- throw用于手动抛出异常对象。使用时,写在方法体中,常用于满足某种情况时,强制中断程序用法:throw异常对象。
数组和集合的区别
数组的特点
- 数组中保存的元素都是有序的。可以通过下标快速访问。
- 数组中保存的数据都是同一种类型。
- 数组的长度在定义后,无法改变。
- 数组无法获取其中保存的元素实际数量。
集合的特点
- 能保存一组数据,可以有序也可以无序。
- 集合的容量可变。
- 集合中可以保存不同类型的数据。
- 可以获取集合中保存的元素实际数量。
泛型
定义
一种规范,常用于限制集合中元素的类型,省去遍历元素时判断是否为对应类型和转型的过程。
用法
在定义集合遍历时,在类后面写上<引用数据类型>
集合类或接口<引用数据类型> 集合变量名= new集合实现类();
集合家族
Collection接口
常用方法
lterable接口为什么不是集合的根接口
原因
- lterable被称为迭代器,是用于遍历集合元素的一个工具接口。
Collections集合工具类
与Collection的区别
- Collection是集合的根接口,定义了集合操作元素的方法。
- Collections是集合的工具类,定义了集合操作元素的静态方法。
常用方法
list接口
特点
- 有序可重复
- 可以根据索引进行删除、获取元素等
实现类接口-ArrayList接口
特点
- 采用数组实现的集合。
- 可以通过索引访问元素,可以改变集合大小,如果要在其中插入和删除元素时,会影响后续元素。
- 该集合中保存的都是引用类型。
- 该集合查询效率高,中途增加和删除元素效率低。
构造方法
常用方法实现了Collection接口和List接口。
实现类接口-LinkedList接口
特点
- 采用双向链表实现的集合。
- 集合中保存的每个元素也称为节点,除首尾节点外,其余节点都保存了自己的信息外,还保存了其前一个和后一个节点的地址。
- 如果在双向链表的数据结构中插入和删除操作节点时,不会影响其他节点的位置。如添加时新节点时,只需要重写定义新节点的前后节点位置即可。
- 如果要查询某个节点时,需要从头结点或尾结点开始一步步得到目标节点的位置。
- 双向链表在中间插入和删除的效率高,随机读取的效率低。
构造方法
常用方法不仅实现了Collection接口和List接口,还实现了Deque接口的方法。
set接口
特点
- 无序不重复。
- 允许保存null,没有索引。
- 没有自己定义的方法,都是继承于Collection接口中的方法。
哈希表(set集合实现原理)
- 哈希表,也称为散列表,是—种数据结构,能更快地访问数据。
- 要保存的数据称为原始值,这个原始值通过一个函数得到一个新的数据,这个函数称为哈希函数,这个新数据称为哈希码,哈希码和原始值之间有一个映射关系,这个关系称为哈希映射,可以构造一张映射表,这个表称为哈希表。在哈希表中,可以通过哈希码快速地访问对应的原始值。
- 哈希冲突,多出现在哈希函数有一定的几率让多个原始值得到相同的哈希码(哈希码一致,实际值不同)。
- 值得注意的是,如果两个对象的hashCode不同,这两个对象一定不同。如果两个对象的hashCode相同,这两个对象不—定相同。(此处就可能出现哈希冲突)
- 解决哈希冲突,就要使用"拉链法",将重复的这个哈希码所在的位置向链表—样进行延伸。
实现接口-HashSet接口
特点
- 采用哈希表实现。
- 元素不能重复,无序保存,允许保存null。
- 本质是一个HashMap对象。
- 使用HashSet集合时,通常要重写实体类中的equals和hashcode方法。
常用方法没有属于自定义的方法,都是重写了父接口Set和Collection中的方法。(注意没有与索引相关的方法。)
添加数据原理
- 如果两个的hashCode相同且equals结果为true,视为同一个对象,不能添加。
- 每次向集合中添加元素时,先判断该元素的hashCode是否存在:
如果不存在,视为不同对象,直接添加;
如果存在,再判断equals方法的结果(如果false,视为不同对象,可以添加;如果true,视为同一对象,不能添加)
equals方法和hashCode的关系
- 如果没有重写equals,默认是Object中使用==判断,如果结果为true,说明是同一个对象,hashCode一定相同。
- hashCode不同,说明不是同一个对象,没有重写equals,说明使用Object中equals的==判断,结果为false。
- 如果两个对象的hashCode相同,equals方法的比较结果为:可能是true(同一地址),可能是false(哈希冲突)。
实现接口-TreeSet接口
特点
- 特殊的Set实现类,数据可以有序保存,可以重复,不能添加null。
- 采用红黑树(自平衡二叉树实现的集合。
- 只能添加同一种类型的对象且该类实现了Comparable接口。
- compareTo()方法的返回值决定了能否添加新元素和新元素的位置。
- 添加的元素可以自动排序。
构造方法
使用场景
- 如果要保存的元素需要对其排序,使用该集合。
- 保存在其中的元素必须要实现Comparable接口,且重写compareTo()方法,自定义排序规则。
集合和数组之间的转换
集合转换为数组
使用Collection接口中的toArray()方法。
数组转换为集合
遍历数组的同时添加到集合中。
一组数据转换为集合
使用Arrays工具类中的asList(一组数据)方法。
Map接口
特点
- Map成为映射,数据以键值对的形式保存。
- 键称为Key,值称为Value,键不能重复,键允许出现一个null作为键,值无限制。
- 键和值都是引用类型。
常用方法
实现类-HashMap接口
构造方法
常用方法继承自Map。
遍历集合中元素的方式
遍历List集合
方式一:普通for循环
方式二:增强for循环
方式三:迭代器
遍历Set集合
方式一:增强for循环
方式二:迭代器
遍历Map集合
方式一:根据键得到对应的值
方式二:遍历键值对
数组管理系统 VS 集合管理系统
使用数组完成-ATM系统
User类(封装的用户类)
Bill类(封装的账单类)
自定义异常类-MoneyException类(取多抛出的异常)
自定义异常类-passwordException类(密码多次错误抛出的异常)
ATM类(主要方法集合)(注意整个过程返回对象之后,就会一直直接操作此对象,所有不需要再更新信息回数组中,此处本人犯过错,需谨慎!!!)
Main类(主入口)
使用(List集合)ArrayList实现-新闻管理系统
News类(封装的新闻)
NewsAgency类(方法集合体) (注意此处根据自动生成的有顺序的编号进行标识和索引,所以不用遍历整个集合,直接根据索引值判断即可)
Main类(主入口)
使用(Map集合)HashMap实现-用户管理系统
User类(封装的用户)
Main类(主方法)
文件类File
定义
- Java中的File类,表示本地硬盘中的文件(文件和目录)的一个类。
- 通过这个类创建的对象,可以操作对应的文件。
构造方法
常用方法
IO
流Stream
特点
- 在Java中,流用于表示计算机硬盘与内存之间传输数据的通道。
- 将内存中的数据存入到硬盘中,称为写write,也称为输出Output。
- 将硬盘中的数据存入到内存中,称为读read,也称为输入Input。
流的分类
字节输入流InputStream
FileInpuStream、ObjectInputStream
字节输出流OutputStream
FileOutputStream、ObjectOutputStream
字符输入流Reader
FileReader、BufferedReader、OutputStreamWriter
字符输出流Writer
FileWriter、BufferedWriter、InputStreamReader
按方向分类
- 输入流:InputStream、Reader(将硬盘中的数据读取到内存中)
- 输出流:OutputStream、Writer(将内存中的数据写入到硬盘中)
按类型分
- 字节流:InputStream、OutputStream(读写非文本类型文件。如图片、音视频、其他文件等。)
- 字符流:Reader、Writer(读写纯文本类型文件。如txt、md等)
应用场景
- 如要将硬盘中某个txt文件中的内容读取到程序中,使用Reader。
- 如要将硬盘中的某个图片读取到程序中,使用InputStream。
- 如要将程序中的文本写入到硬盘中为txt类型文件时,使用Writer。
- 如要将程序中的数据写入到硬盘中为非文本文件时,使用OutputStream。
流的四个父类的特点
- 这四个父类都是在java.io包下,都是抽象类,不能直接创建其对象,使用其子类创建对象。
- 这四个父类中都定义了close()方法,用于关闭流对象,释放资源。
- 输入流(InputStream和Reader)都有read()方法读取数据到内存中,输出流都有write()方法写入数据到硬盘中。
- 输出流(OutputStream和Writer)都有flush()方法,用于将流中的数据冲刷到硬盘中
在使用输出流对象时,一定要调用flush()或close()方法后,才能真正将数据写入到硬盘中。- 所有的流中,以Stream结尾,都是字节流,数据以字节传输;以Reader或Writer结尾的,都是字符流,数据以字符传输。
- 读取硬盘中的数据,使用输入流,读取的文件必须存在;将数据写入到硬盘中,使用输出流,文件可以不存在,但父目录必须存在。
- 读入或写入文本时,使用字符流;读取或写入非文本时,使用字节流。
使用FileInputStream和FileOutputStream实现-单文件的复制
文件夹的复制
序列化和反序列化应用
Person类(实现Serializable接口)
Main类
网络编程
InetAddress类
定义
表示IP对象的一个类。
Socket类和ServerSocket类
相同点
都属于Socket(套接字)对象,表示网络中的某个端点。
不同点
Socket指普通端。
ServerSocket指服务器端。
使用套接字对象实现两个端点(Socket和ServerSocket)之间发送文件
服务端
客户端
进程和线程
进程Process
进程就是操作系统中执行的程序。一个程序就是一个执行的进程实体。每个运行中的进程,都有属于它独立的内存空间,各个进程互不影响。
线程Thread
- 线程是一个进程中的执行单元,一个进程中可以有多个线程。
- 多个线程,可以访问同一个进程中的资源。
- 每个线程都有一个独立的栈空间,这些线程所在的栈空间位于同一个进程空间中。
多线程
- 如果一个进程中,同时在执行着多个线程,就称为多线程。
- 多线程可以提高程序执行效率。如多个窗口卖票,可以加快卖票的效率。
- 其实每个执行的Java程序,都是多线程执行,main方法称为主线程,还有gc线程(守护线程)在同时运行。
并行和并发
并行
各个进程同时执行。
并发
多个线程同时执行,称为并发。
同步和异步
同步
所有的任务排队执行。
异步
在执行任务A的同时,执行任务B。
Java中的线程Thread类
获取当前正在运行的线程对象
创建一个线程对象(构造方法)
常用方法
实现多线程
方式一:继承Thread类
- 创建一个类,继承Thread类。
- 重写Thread类中的run()方法。
- 创建自定义的线程子类对象后,调用start()方法。
方式二:实现Runnable接口(建议使用)
自定义一个类,实现Runnable接口。
重写run()方法,将多线程要执行的内容写在该方法中。
创建Runnable接口的实现类对象。
使用构造方法Thread(Runnable target)或Thread(Runnable target,String name)将上一步创建。
的Runnable实现类对象包装为Thread对象。
方式三:使用匿名内部类
使用匿名内部类充当Runnable接口的实现类。
生命周期
新生状态
- 当线程对象被创建后,就进入了新生状态。
就绪状态
- 当某个线程对象调用了start()方法后,就进入了就绪状态。
- 在这个状态下,线程对象不会做任何事情,只在等他CPU调度。
运行状态
- 当某个线程对象得到CPU时间片(CPU执行这个线程的机会所给的时间),则进入运行状态,开始执行run()方法。
- 不会等待run()方法执行完毕,只会在指定的时间内尽可能地执行run()方法。只要调用玩run()方法后,就会再进入就绪状态。
阻塞状态
- 如果某个线程遇到了sleep()方法或wait()方法时,就会进入阻塞状态。
- sleep()方法会在指定时间后,让线程重新就绪。
- wait()方法只有在被调用notify()或notifyAll()方法唤醒后才能重新就绪。
终止状态
- 当某个线程的run()方法中的所有内容都执行完,就会进入终止状态,意味着该线程的使命已经完成。
守护线程
特点
- 如果将一个线程设置setDeamon(true),表示该线程为守护线程。
- 守护线程会随着其他非守护线程终止而终止。
多线程访问同一个资源
可能出现的问题
多个线程对同一对象异步进行操作,导致结果不能达到预期。
出现问题的原因
- 由于线程调用start()方法后,就进入就绪状态。如果获得了CPU时间片,就开始调用run()方法,调用run()方法后,就会再次进入就绪状态,不会等待run()方法执行完毕,所以在线程A执行run()方法的时候,线程B也开始执行了,这样就会出现数据共享的问题。
- 因为现在所有的线程都是异步(同时)执行。
如何解决
让线程同步(排队)执行即可。这样一来,某个线程执行run()方法的时候,让其他线程等待run()方法的内容执行完毕。
synchronized关键字
可以修饰方法或代码块
修饰方法
写在方法的返回值之前,这时该方法就称为同步方法。
修饰代码块
写在一个独立的{}前,这时该段内容称为同步代码块。
原理
- 每个对象默认都有一把"锁",当某个线程运行到被synchronized修饰的方法时,该对象就会拥有这把锁,在拥有锁的过程中,其他线程不能同时访问该方法,只有等待其结束后,才会释放这把锁。
- 使用synchronized修饰后的锁称为"悲观锁"。
- 方法被synchronized修饰后,称为同步方法,就会让原本多线程变成了单线程(异步变为同步)。
死锁
场景
当多个线程同时获取不同资源时,恰好获得的资源都是对方下一步执行所需的资源,但是双方都在等待资源发放,导致多个线程一直不结束运行,也不继续执行下去。
解决方式
方式一
- 让两个线程获取资源的顺序保持一致。
方式二
- 让两个线程在获取资源A和B之前,再获取第三个资源,对第三个资源使用synchronized进行同步,这样某个线程在获取第三个资源后,将后续内容执行完毕,其他线程才能开始执行。
多线程相关面试题
实现多线程的方式?
- 继承Thread类
- 实现Runnable接口后,包装为Thread对象
- 匿名内部类
为什么说StringBuilder或ArrayList、HashMap是非线程安全的?
因为多个线程对这些类型创建的对象进行操作不能达到预期的结果。
个人总结
版权声明:
本文来源网络,所有图片文章版权属于原作者,如有侵权,联系删除。
本文网址:https://www.bianchenghao6.com/h6javajc/1494.html