1. 集合框架
Java集合类主要有两个根接口Collection和Map派生出来
Collection派生两个子接口
- List
List代表了有序可重复集合,可以直接根据元素的索引进行访问 - Set
Set代表无序不可重复集合,只能根据元素本身进行访问
Map接口派生
Map代表的是存储key-value对的集合,可根据元素的key来访问value
1.1 List
List集合特点:有索引,可重复,有序
扩容机制
- 初始值为10
- 如果数组的长度小于需要的最小容量则自动扩容
- 扩容到原来的1.5倍
ArrayList
底层通过数组实现,查询快,增删慢,线程不安全
访问数据的时间复杂度是O(1)
LinkedList
底层通过双向 链表实现,查询慢,增删快,线程不安全
访问数据的时间复杂度是O(n)
Vector
List接口的古老实现类。线程安全,执行效率低
扩容机制不同于ArrayList,由1.5倍改成1倍
1.2 Set
1.2.1 HashSet
其底层HashMap,也就是数组+链表+红黑树(JDK8)的结构
HashSet集合特点:无序,不可重复,无索引
扩容机制
HashSet底层也是数组,初始容量为16,当使用了超过0.75,即16*0.75=12时,就会扩大容量为原来的两倍
什么情况下为转为红黑树
当链表长度 >= 8,数组长度 >= 64时,会转为红黑树
LinkedHashSet
继承于HashSet,底层是LinkedHashMap,是一个有序去重集合,线程不安全
有序因为它根据hashCode决定元素的存储位置,同时使用链表来维护元素的次序
1.2.2 TreeSet
TreeSet是SortedSet接口的实现类,底层是红黑树,元素有序
排序方式
自然排序(实现Comparable接口)和 定制排序(Comparator)
1.3 Map
Map集合特点
- 键值对(key-value)映射关系
- 是单向的一对一关系,即一个键对应一个值
- 键不可重复,值可重复
- 无序
1.3.1 HashMap
HashMap的底层在jdk7及以前,是数组+链表;jdk8及以后,是数组+链表+红黑树
如何判断重复
先比较hashcode,若相同则使用equals比较插入的值,若值不同,则插入,若值不同,则不插入
2 IO流
IO流的体系
IO流的四大类
- 字节输入流: 以内存为基准,把磁盘文件/网络中的数据以字节的形式读入到内存中去
- 字节输出流: 以内存为基准,把内存中的数据以字节的形式写出到磁盘文件或网络中去
- 字符输入流: 以内存为基准,来自磁盘文件/网络中的数据以字符的形式读入内存中
- 字符输出流: 以内存为基准,把内存中的数据以字符的形式写出到磁盘文件或网络中去
2.1 IO流实现文件的上传下载
2.1.1 文件上传
文章引用IO流–文件上传实现(侵删)
文件上传,我们就需要的一个服务器和客户端,两者需要进行通信,两者通信的要素是服务器的IP地址和对应的端口号才能建立连接
- 首先开启服务器,并开启一个端口号给客户端进行连接通信
serverSocket = new ServerSocket(port);
- 服务器通过监听端口来进行确定是否有客户端进行访问
socket = serverSocket.accept();
- 服务器准备完成,可以与客户端进行通信,首先客户端创立连接
socket = new Socket(InetAddress.getByName(IP), port);
- 连接成功,然后通过IO流进行文件传输,先读取文件,再将文件转为IO流输出(读取是本地读取,输出是输出给服务端)
//2.然后向服务器发送消息,需要一个输出流
os = socket.getOutputStream();
//3.读取本地的文件并输出,需要一个输入流
is = new FileInputStream("123.txt");
- 客户端需要通过一个缓冲区来让文件实现完整的上传和输出,通过一个字节数组,保证整个文件不会因为字符数量的大小导致文件上传失败
//4.读取文件中的内容并输出,读取文件的内容需要一个字节流,用字节数组来接收
byte[] buffer = new byte[1024];//这个字节数组是属于IO流中的缓冲区,
int length ;
while ((length=is.read(buffer) )!= -1){
os.write(buffer,0,length);
}
- 到这一步,客户端的任务完成了大半,然后就是服务器端的接受操作,与客户端也是大同小异,通过文件字节输入流和文件字节输出流对连接中的数据进行读取和输出
//1.先创建一个端口号供客户端去连接,其实应该先有一个服务器端口号,那客户端才能连上(怎么创建一个端口号?)
serverSocket = new ServerSocket(8099);
//2.服务器监听端口号,看端口号是否有客户端连接
socket = serverSocket.accept();
//3.创建一个输入流和一个输出流,输入流用于接收客户端传来的数据,输出流用于输出
is = socket.getInputStream();//在建立起的连接中获取信息
os = new FileOutputStream("receive.txt");//这里输出的用的是文件流
int len ;
byte[] bytes = new byte[1024];
while ((len = is.read(bytes)) != -1){
os.write(bytes,0,len);
}
完整代码实现
客户端
/**客户端*/
public class TcpClient {
public static void main(String[] args) {
Socket socket = null;
OutputStream os = null;
FileInputStream is = null;
try {
//1.客户端要连接服务器,需要创建一个连接,创建连接需要服务器的IP号和对应的端口号
socket = new Socket(InetAddress.getByName("127.0.0.1"), 8099);
//2.然后向服务器发送消息,需要一个输出流
os = socket.getOutputStream();
//3.读取本地的文件并输出,需要一个输入流
is = new FileInputStream("123.txt");
//4.读取文件中的内容并输出,读取文件的内容需要一个字节流,用字节数组来接收
byte[] buffer = new byte[1024];//这个字节数组是属于IO流中的缓冲区,
int length ;
while ((length=is.read(buffer) )!= -1){
os.write(buffer,0,length);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
//5.关闭连接
if (is != null){
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (os != null){
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (socket != null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
服务器
/**服务器*/
public class TcpServer {
public static void main(String[] args) {
ServerSocket serverSocket = null;
Socket socket = null;
InputStream is = null;
FileOutputStream os =null;
try {
//1.先创建一个端口号供客户端去连接,其实应该先有一个服务器端口号,那客户端才能连上(怎么创建一个端口号?)
serverSocket = new ServerSocket(8099);
//2.服务器监听端口号,看端口号是否有客户端连接
socket = serverSocket.accept();
//3.创建一个输入流和一个输出流,输入流用于接收客户端传来的数据,输出流用于输出
is = socket.getInputStream();//在建立起的连接中获取信息
os = new FileOutputStream("receive.txt");//这里输出的用的是文件流
int len ;
byte[] bytes = new byte[1024];
while ((len = is.read(bytes)) != -1){
os.write(bytes,0,len);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
if (os != null){
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (is != null){
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (socket != null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (serverSocket != null){
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
2.1.2 文件下载
文章引用利用io流下载文件(侵删)
public void download(HttpServletRequest request, HttpServletResponse response) {
InputStream fis=null;
OutputStream toClient=null;
try {
//获取下载的路径
File file=new File("C:\\Users\\kinggao1\\Desktop\\工作周总结.docx");
//获取文件名
String filename=file.getName();
//取得文件的后缀名
String ext=filename.substring(filename.lastIndexOf(".")+1).toUpperCase();
//以流的形式下载文件
fis=new BufferedInputStream(new FileInputStream(file));
System.out.println("文件大小:"+fis.available());
//创建一个和文件一样大小的缓存区
byte[] buffer=new byte[fis.available()];
//读取流
fis.read(buffer);
//清空首部空白行
response.reset();
//设置文件下载后的指定文件名
response.addHeader("Content-Disposition", "attachment;filename=" + new String(filename.getBytes("gb2312"),"ISO8859-1"));
response.addHeader("Content-Length", "" + file.length());
//response.getOutputStream() 获得字节流,通过该字节流的write(byte[] bytes)可以向response缓冲区中写入字节,再由Tomcat服务器将字节内容组成Http响应返回给浏览器。
toClient = new BufferedOutputStream(response.getOutputStream());
response.setContentType("application/octet-stream");
//将buffer 个字节从指定的 byte 数组写入此输出流。
toClient.write(buffer);
//刷新此缓冲的输出流。这迫使所有缓冲的输出字节被写出到底层输出流中。 把缓存区的数据全部写出
toClient.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
//关闭流
fis.close();
//关闭缓冲输出流
toClient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
2.2 IO流实现文件上传时限制文件大小
获取文件IO流的大小
long fileLenght = getFileLenght(file);
再和给定大小判断
如果超出就跑出自定义异常
3 线程
3.1 4种线程的创建方法
- 继承Thread方法,实现run方法
- 实现Callable接口,重写call方法
- 实现Runnable接口,重写run方法
- 线程池构建线程
3.2 线程的状态
3.3 自定义线程池的核心参数
- 核心线程数
- 最大线程数
- 最大存活时间
- 最大存活时间单位
- 阻塞队列
- 线程工厂
- 拒绝策略
拒绝策略有四种:
- 抛出异常
- 把处理返回给调用者,谁调用谁处理
- 直接拒绝
- 清理掉最早的线程,再加入新建的线程
3.4 锁
文章参考于阿里面试失败后,一气之下我图解了Java中18把锁
3.4.1 悲观锁
悲观锁相当与生活中悲观的人,什么事都会忘坏的方向思考
具体来说,就是一个共享数据加了悲观锁,那么线程每次想要操作数据时,都会考虑数据可能也会被其他线程操作,所以每次操作前都会上锁,这样别的线程想要操作这个数据但是没有锁只能够阻塞了
Java中,synchronized就是一种典型的悲观锁
3.4.2 乐观锁
与悲观锁相反,乐观锁相当于生活中的一个乐观的人,什么事情都会往好的方向进行思考
所以,乐观锁在操作数据时不会给共享数据上锁,但是会在更新的时候判断在此期间是否会有其他线程对数据进行了更新
乐观锁可以使用<font color="pink"版本号机制和CAS算法实现,在Java语言中 java.util.concurrent.atomic 包下的原子类就是使用CAS乐观锁实现的
两种锁的使用场景
乐观锁适用于冲突小(可以理解为读多写少)的场景,因为不用上锁,释放锁,省去了锁的开销,从而提升了吞吐量
悲观锁适用于写多读少的场景,冲突比较严重,线程竞争比较激烈,使用乐观锁就是导致线程不断的进行重试,这样还可能降低了性能
3.4.3 互斥锁
互斥锁是独占锁的一种常规实现,是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排他性
互斥锁一次只能一个线程拥有互斥锁,其他线程只有等待。
3.4.4 自旋锁
自旋锁是指线程在没有获得锁资源时不是直接挂起,而是执行一个忙循环,这个忙循环就是所谓的自旋