JavaSE基础-日志版
1.计算机语言的分类
Day02
1 集成开发环境(IDE)
- NetBeans
- JBuilder
- Eclipse IBM公司捐献出来的集成开发环境,免费比较好用
- Intellij IDEA 使用频率最高,收费软件1
IDEA
IDEA的项目都是以模块的形式存在的项目的JDK就是依赖库
2 Java的相关语法
2.1快捷键
1 复制当前行ctrl+alt+down
2 alt+up/down当前行与上/下行交换位置
3 删除一行 ctrl+D(其实这个是我的复制当前行+放在下一行上,可以自己设置当前行,只要你知道有哪些快捷键就行了,具体是什么按键可以自己设置)
2.2注释
1注释的两个操作:
- 对于源代码的解释说明(注释不是随便写的,在后面注释会被程序检索存储,用于文档显示)
- 不参与程序的编译
- 两个写注释的方法:
- //双斜线 现在推荐使用的是注释单独在一行,放在所描述代码的上一行
- 使用注释来调试代码(就是屏蔽代码,使编译器跳过带脉啊编译)
2注释的分类
单行注释// 快捷键ctrl+/
多行注释/**/ 快捷键ctrl+shift +/ 取消或者添加
文档注释 规定以/**开始中间都是以星*打头最后以*/结束
文档注释主要用于文档管理系统检索和管理 使用javadoc命令来生成文档是html格式的doc
使用文档模板来自动添加文档头
2.3 变量
变量存储在内存里面,内层分配一块地址用于存放不同的类型变量,
其实变量就是一个盒子
但是变量在使用的时候其实需要一个类型,来限制需要的操作
变量在使用之前必须要先声明(python就不一样)
Java中有两种数据类型,基本数据类型和引用数据类型
需要先指定变量类型
2.5 分隔符
2.5.1 概念
用于分隔程序代码的,要求任意两个相邻的标识符,数字,关键字或者语句之间必须至少要有一个分隔符,使得编译器能够识别
2.5.2 分类
分号用于终结一个语句
大括号:用来包括程序类,方法,代码块,或者用于初始化数组的值
() 改变运算优先级,或者表示调用方法传参 强制转化
[]
, 一次可以生命多个变量,多个参数,等等
. 用作包名的划分,引用变量的方法或属性
空格 换行 tab 空白符号这种
计算机中的存储
bit:二进制位,也就是一个bit是一个0或者1,是计算机运算最基础的单位
byte:字节,指文件大小的一个基础单位
换算关系是:一个byte=8bit
进制
十进制 二进制 八进制 十六进制
八进制是以0开头
二进制后面带0b
十六进制0x开头,有的是以h结尾
2.5.4 八种基本数据类型
数据类型转换
自动转换: 精度低数据类型转向精度过数据类型,或者说内存小的数据类型转向扩大的内存(整数到浮点也是这种情况)
需要注意的是一个long类型的数据是把位,但float是32位可是float能够表示的范围更大,所以可以将long自动转换位float强制类型转化:会丢失精度,或者范围表示不下,所以需要进行强制转化方法是使用(加上转换类型)例如(int)1231L
截断情况:高位全部丢弃了,按照的是补码存储
直接嘎掉小数部分
基本算术运算
* / % + -
注意/,如果有任意一个操作数位浮点数,最后结果会保留位浮点数(全是整数则会舍去小数部分)
自动提升java默认整数操作类型是int类型.,如果使用了其他数据类型操作运算符会自动提升位int
键盘接收数据
Day17
集合类
非常重要
介绍:
其实就是和数组一样,都是为了用来存储多个数据的一种结构,所以也可以称作容器
但是和数组有很大区别,数组是非常经典的一种容器,但是为什么会出现其他容器呢?
因为实施组具有以下缺点:
1 定长,在初始化的时候就固定了长度,长度不可变
2 没有办法保存具有映射关系的数据(映射关系的数据就是python中的字典)
这些功能都是很常用的功能,所以Java提供了官方的集合类,以及提供我们继承父类,来制作我们自己的集合类.
所有的集合类都在Java.util包下面提供了一个表示和操作对象集合的统一架构.包含了大量接口和类 ,并且包含了这些类的和接口和实现类的操作和算法.以及数据结构
和数组处理的区别:
可以存储的数据长度,可变与否,
数组的各种方法,插入删除,等等操作不方便,效率低
数组的特点是:有序/可重复
数组可以存储基本数据类型和引用类型,而集合类只能存储引用类型,其实就是Object以及器子类
其实就是在堆中开辟的集合中存储数据的引用地址,在集合中所说的存储,指的是内存层面的存储,不涉及持久化数据,一种办法就是数据库,一种就是保存在磁盘文件上
泛型介绍:
在我们自定义的集合类中,底层是一个object类的数组,所以我们不能确定这个容器到底要存什么类型
泛型就是在传入的时候检测类型
泛型其实就是一个类型,你传值给他他就确定了,就相当于一个类型
这种参数可以使用在接口.类,方法中这三种情况
使用泛型的好处,可以检测类型,免除了错误,避免使用强制转换
避免了不必要的拆箱封箱,简单的数据类型和Object转换很难
使用泛型就是使用<>尖括号,可以有多个参数,参数之间使用逗号分隔
为了可读性:
规定了泛型的字母:
T 表示java的类
E element重要用于集合中,表示容器中存放的数据类型
K key键
V value标示值
N number表示这是一个是数值2类型
? 表示这是一个不确定的数据类型
alt+shift+r统一改名
泛型通配符
用来解决繁星之间的引用传递之间的特殊语法
主要分为三种情况
? 无边界通配符
? extends类的上边界的通配符 怎么理解上边界?就是都是那一个类的小辈,是一个族的
? super 类 固定下边界通配符 就是说都是谁的长辈,都是长辈
主要作用是让这个泛型能够接受未知类型的数据
例如我这个参数的类型是一个泛型,那么如何描述这个泛型参数呢?
这不是一个类型,而是一个泛型(不确定类型)
此时就不能对数据进行具体的类型操作,而是需要将类型全部看作是Object类型
ArrayList
Java中的集合类框架
其实就是Collection 和 Map 迭代器 排序接口 工具类这五个
Map是关系数据,键值映射这样的数据
所以主要就是Collection
从Collection下来就是list和set两类
其实Collection是一个接口继承与Iterable接口迭代器
接口里面定义了需要实现的方法
list中有一个ArrayList类这个就是一个类,不是一个接口或者抽象类;
重点就是讲解我们的一个ArrayList
打印ArrayList其实就是在调用一个toString方法,记得打印数组的需要使用到java.util的Arrays中的toArrayString方法来显示数组
ArrayList重写来自于Collection的toString方法,使用[]来包裹容器中的元素,显示形式和数组很像
ArrayList是一个有顺序的,可以重复的这些和数组很像,但是不同的一点是:大小可变
ArrayList是list的一个实现类,底层使用了Object数组的一个方式
所以特性和数组很像,我们的自定义集合其实就是仿照的ArrayList写的
使用了Arrays.CopyOf()方法
集合的循环遍历方法:
1 从fori形式开始,使用list.size作为条件,list.get(index)方法来通过索引获取
2 foreach 直接遍历
3 迭代器Iterator
这样就得到了我们集合的迭代器,还记得我们的Collection就是继承于我们的Iterable方法,所以所有来自于Collection接口的集合都具有Iterator方法
Interator 接口中具有方法 hasNext()判断是否具有下一元素
next()取出下一个元素
所以使用while循环iterator,hasnext()
每次调用next就是返回指向的下一个元素,所以指针开始指向-1index
也可以使用for循环
for(Iterator )
4 labmda表达式lsit.forEach(System.out::println);
其实就是将方法穿进去了,在函数里面来调用方法
ListIterator 继承与Iterator
除了使用iterator之外,还可以使用ListIterator
其实可以通过Arrays工具类的方法Arrays.asList(“”,可变长参数)来返回一个list接口对象
这个方法创建出来的集合是固定大小的,不能再使用list..add方法来添加数据
既然不能添加,那么自然也不可以删除
讲一下ListIterator和Iterator的区别
1 Iterator可以遍历set和list集合
2 listIterator只能遍历list集合
3 Iterator只能向后遍历而listIterator可以下前后遍历
4 listiterator继承与iterator接口,添加了新的方法
Iterator其实就只有四个方法
一个hasNext,一个nExt一个remove一个foreach方法
而ListIterator具有
如果通过ListIterator执行add方法会在指针后立即插入元素,到对应的集合中(其实此时的迭代器明明就是来自于集合中的方法.所以add方法其实就是插入元素到集合中了,而迭代器中没有立即加入此元素,而是维持原状)
add(E 啊)将执行元素插入到集合中
对于迭代器非常重要的hasNext( )方法,正向迭代判断
nextIndex()返回下一个元素的索引,但是还没有取出,知识peek(毕竟索引只有对数组有用,所以listIterator)
在list的listIterator函数中,可以添加参数,使得指针指向索引index
如果想要反向遍历,所以就需要ListIterator
使用的函数式hasPrevious和previous index
interator.set方法会替换掉指针位置的元素
Remove方法在foreach中使用出错
而是用迭代器不会有这样的情况
报错创建了一个坏指针,但是只有当删除倒数第二个不会报错
在对集合进行循环处理的增加和删除时,不能使用foreach处理方式,使其foreach底层也是通过迭代器iterator实现的
使用迭代器操作有两个步骤,1 使用的hasnext 2 next方法提取出来
其实使用迭代器删除是没有问题的,所以我们到Remove方法中看一看源代码
如果这两个变量不相同!=就会抛出异常这个方法就是checkForcomodification()
当modeCount和exceptionModeCount不相同就会抛出异常
modeCount:记录集合从new到现在被修改的次数再执行add,remove等操作的时候会执行modeCount++
exceptionModeCout:迭代器希望这个集合被修改的次数
要保持至两个变量的一致
迭代器中的remove方法执行了同步操作,而集合中的remove方法就只进行了modeCount++,所以会报错
而倒数第二个可以修改的原因是:对第二删除以后,hasNExt方法会返回false,hasNext方法判断使用Coursor位置和size位置进行等值比较
add方法和add(index,element)
以及size( )方法返回元素个数
isEmpty() return boolean
其实就是判断元素是否为0size这个东西
clear( )从集合中删除所有元素
addALL(Collection 可以添加)也就是说可以将set,list,collection都可以加
要求添加集合之中存储的元素的集合类型是相同的
addALL(index,Collection)
remove可以移除对象,或者索引位置的元素
需要注意的是remove移除对象需要为引用对象
移除只能移除第一个找到的元素,如果想要全部移除需要使用removeALL(Collection)移除子集中的所有包含元素
如果是给索引会返回删除的元素,如果是给的删除对象,会返回boolean类型
所以想要移除所有元素必须使用removeAll其中需要准备参数collection
retainALl(保留Collection)保留子集中包含的元素
contains(Object元素)返回boolean,是否包含
containsAll(collection) 全部包含则返回true
toArray()会转成Object类型的数组
indexof(的对象)相当于就是查找对象的索引值
只能查找找到的第一个index
lastindexof和字符串的是一样的
subList对集合做截取(index,lastindexe)包含起始位置,不包含结束位置,这一点和python那些都一样
\返回的是一个新的集合
iterarot返回集合迭代器
listiterator(可以给一个index,其实就是一个起始的位置开始做迭代器)
set就是改变(index,Object)就像数据库一样
Day18
LInkedList
链表数据结构,这个你知道的
所以这个和list的底层实现是很不同的
因为底层实现不同所以具有很多不同的特点
链表是不连续的,查找需要很多时间
链表分类:
单向链表,双向链表,循环链表(单向循环,双向循环)
单链表就是包含两个部分,数据和节点指针域
LinkedList
很多方法其实和ArrayListhf很像
不同点在于:
addFirst(就是插入到头结点之后)
addLast,add(index,Element)先校验索引
getFirst getLast removeFirst removeLast
作比较和ArrayList与LinkedList
存储结构:底层是数组结构,底层是链表结构
操作性能,ArrayList随机查询
Linkedlist适合元素的插入与删除,不适合查询
Vector向量其实也就是一个List来的,也是Object数组来的
向量和ArrayList都是一样的,但是不同点在哪?
第一个,无参数构造的时候,向量默认10个大小,而ArrayList为空,添加数据需要进行扩容,扩容使用的是Arrays的copyof方法,新大小 = 原有大小+原有大小向右移以为(就是/2 ) 就是1.5倍
每次扩容就是原有大小的1.5倍
我们的向量扩容是如果给定了CpapacityIncrement,那么每次扩容就增加这么大,如果没给那个就扩容为原有大小的两倍
向量是线程安全的,怎么理解呢??就是方法是Sync同步方法
但是在现在我们已经很少使用这个线程安全了,现在主要使用的是Collections.SynchronizedList(list)可以将我们的原有集合做成线程安全的集合
Set
hash表,set其实和Map有很多关联,比如Hashset其实内部有一个HashMap,者HashMap就是我们的map
hash表也叫散列表,其实是根据我们的值与键的映射关系 ,直接进行访问的数据结构,其实就是通过一种函数,算出元素的存取位置和值之间建立一个映射关系,通过这样的方法就能2加快我们的访问速度,这个函数就叫做hash函数存放的记录叫做哈希表
hash表有很多hash冲突,由于hash函数的设计问题,可能会出现hash冲突,常用的hash函数除留取余法(常用),
hash函数设计的越精妙,hash冲突越少,但是hash冲突不可避免
所以有方法来解决hash冲突:
1 闭散列
2 开散列
闭散列也叫做开放定址法,闭散列式神面儿意思呢??就是hash表的位置我们固定封闭,不扩大,直接使用多余的地方
开散列也叫做链地址法,或者开链,其实就是可以存储的大小无线,具体存破除到哪里也不确定,因为是链表,所以不确定
这个链表就像是一个桶,所以也叫hash桶,每一个链表,各个桶里面的通过一个单链表来存储,单链表的头存储在hash表中
如果出现了极端情况,所有数据(大量数据)都到了一个hash桶中,其实就等同于一个单向链表了,这样的效率很低,不希望看到
所以java用了红黑树(评分2二叉树结构)来解决这种问题
将链表改成红黑树
其实hash相当于是链表和数组的折中
hashset通过add加入的数据是通过hash函数计算得到的位置,所以不是顺序的存储
hash set中是不重复的,是一个人数学意义上的集合,重复的值没有继续存储的意义(其实是把原有数据覆盖掉了)
如何判断两个元素重复呢?其实是通过掉用的Equals方法来进行判断的
不不是非要引用相同才判断相同并且,执行这么一句add其实不是没有存储,而是把原有数据覆盖掉了!!!
所以Hashset可以保证的一点是 唯一性,无序
所以为什么在写equals方法的时候要重写hashcode方法,因为hash函数在计算的时候就会调用hashcode
只有当hashcode相同,我们才会进行equals比较!!!!
HashSet方法
其实hashset是从Set来的set其实与Collection差不多
set其实也可以使用foreach和iterator
set的remove方法没有索引,只有给元素
如何判断元素其实就是靠的equals
其他的方法和list很像
hashset下面的LinkedHashSet
构造函数就有讲究,默认构造就给了我们默认的大小,如果小小超标(利用加载因子判断是否该扩容了)默认加载因子是0.75f就是3/4
LinkedHashSet继承于Hashset
底层是LInkedhashMap,维护了一个数组+双向链表的方式
每一个元素都一个亿after和before属性在添加一个元素的时候先计算hashcode再求索引确定这个元素在hashTable中的位置在将元素放在双向链表中,在遍历时可确保和插入的 顺序相同,不能有重复的元素
特性就是有序,唯一
TreeSet
其实是一个二叉树,叫做排序二叉树来存储数据
添加进去2自动排序
输出就是具有顺序的数据,读取采用的是中序遍历
都是以升序排序
其实Tree底层也是用Map实现的,但是需要要求我们存储的对象一定要有排序功能,不然如何排序呢?
如何实现能排序呢?
实现接口Comparable,例如字符串就是能够排序的,按照的是字典顺序
排序接口
Comparable和Comparator接口
所以一个类只要实现implement
这7里面有个老朋友compareTo()方法,非常熟悉
在做对象比较的时候两种方式,1实现Comparable接口重写方法
2 实现Comparator
如果一个类的比较方法不是自己想要的方式,怎么弄?
我们穿一个自己弄得Comparator接口进去,这个就是第三方排序方法,而不是我们元素自带的排序算法,非常的灵活!
Collections工具类
第一个方法sort可以对list进行排序,reverse可以翻转,其实sort还可以传入一个Comparator对象,来进行排序
第二个方法 shuffle打乱List的顺序
第三个方法 Swap方法(list,index1,index2)交换两个元素的位置
max min 获取集合中的最大值和最小值
进行二分查找,必须先进行排序
binarySearch(list.Object),list必须有序
Collections.fill(llist,object),用指定值替换所有元素
Collections.replaceAlL(list,Object1,Object2)
frequency方法用来返回出现的此处
Collections.rotate( list,int)其实就相当前后调换,正负表示前或者后,大小表示哪些元素
Collections.SynchronizedList(list)可以返回一个新的同步list
Collections.SynchronizedSet()…Collection..Map
Day19
Map顶级接口,和Collection都是源头祖师爷
位于Java..util下
Map是一个泛型接口,需要有一个<K,V>
键值对的方式
Map接口有很多不同的实现方式
例如hashMap,Hashtable,SortedMap,
比常用的是HashMap
Map常用方法
put(K,V)
HashMap
我们之前学HashMap,HashMap是不重复的,所以如果有新元素会覆盖
通过hash函数计算之后,两个数据放在一个hash桶下面,进行equals比对,如果相同就覆盖
需要注意的是HashMap是通过键来做为主体数据的的
所以键相同那么数据就是一个数据,会进行覆盖
常用方法:
map.KeySet( )会返回一个Set
如果想要把键值对返回,可以使用map.entrySet ( )会返回一个Set<Map.entry<K,V>>
其中entry就是键值对
这样处理的效率更高一些(比使用map.get(K k))
2 containsKey(K)->boolean
3 containsValue(V)->boolean
4 remove(key) or remove(key,value) 第一步都实现查找是否具有这样的数据
5 map.size()数据的个数
6 isEmpty()其实是通过size==0来实现的
7 putAll(Map) 这个就和addAll差不多
8 replace(key,new value) 有没有这种呢? replace(key,value,new value)
replace和put是一样的,都是覆盖
HashMap初始大小16 负载因子0.75 超过一定是超过就扩容 2倍
调试方法的小技巧:右键选择查看方式->object可以看到map中的对象
LinkedhashMap
继承自HashMap,有顺序
HashMap的特性是:无序,key唯一,value随便
而LinkedHashMap就是有顺序的
TreeMap
按照建升序排列
HashTable
是基于我们陈旧的Dictionary类,实在jdk1.0加入的,HashMap是jdk1,2加入的
都实现了Map接口,但是都不是直接实现的是父接口继承来的
HashTable线程安全,HashMap不是,但是HashTable现在不怎么用的,因为hashMap可以通过Collections工具类有方法可以讲我们的HashMap转为线程安全的集合
HashTable不能给空vaue,会抛出控制异常,而hashMAp可以将空值当做一个值无论是作为键还是值都可以
HashMap初始容量是16,HashTable初始容量是11(有点奇怪),负载因子都是0.75
HashMap扩容就是容量*2 而Table是*2+1
HashMap数据结构2是数组+链表+红黑树,当链表长度大于8时转换为红黑树结构
HashTable就是数组+链表
计算hash的函数不同HashTable直接使用hashcode对table数组的长度进行球磨取余
hashMap计算机hash对key的hashcode进行了二次hash以获取更好的散列值,这样其实更好然后再对长度进行取余
Properties继承与HashTable
主要用来读取一种文件 生成一种文件 .properties文件
这种文件用键值对的方式来进行存储
形如key=value
就是用=和\n换行符来作为分隔符
主要起到的作用是配置,这种相似的形式文件还有json,一般用于http网页的数据传输
new Properties
主要方法:load(文件)
现在可以使用获取类的加载器getRes
获取属性getproperty(“key”)
需要注意的是,这种文件中的键值对默认都是字符串,但是不带””
读出来就是String,程序也全将其类型看做是String来处理
所以在使用getproperty(String key) 这里的key需要将装饰文件中的key加上引号
目的是什么?
将配置信息和运行程序解耦,更加方便快捷的修改配置信息
有一个类和properties具有相同功能
ResourceBundle是处理国际化文件的
不用写后缀名直接可以打开文件
对应方法是:getBUndle()
getString(String key)获取value
Stack
学过数据结构,知道栈这种线性表
Top栈顶 bottom栈顶
先进后出,后进先出LFIO
压栈 入栈 进栈
出栈 弹栈
Stack继承自Vector向量
主要用到的方法
Stack.push
在调用toString方法显示的是栈口在后右
pop弹栈,这种会删除栈顶元素
当然有size()继承自vector
peek( ),不会删除栈顶元素
常用方法还有:
isEmpty() search(Object parms)想要区别于contains,返回了相对于栈顶Top的位置,栈顶元素就是1没有找到就是-1
有一个方法empty和isEmpty区别在哪里呢?
empty是set里面的方法,isEmpty是Vector里的方法,size()==0和ElementNumber==0
递归算法
一种常用的算法
重复将问题分解为子问题的方法
自己方法调用自己,方法就像是一个盒子,打开盒子结果,方法的返回值
递归就是盒子里面再有盒子,一般来将,递归都是调用自己,但是从理论来讲,可以调用其他函数只要其他函数最后也调用到自己形成一个闭环就是递归算法
考虑最简单的情况来理解地递归算法,打开盒子里面有一个盒子,那么就得不到返回值,需要将盒子里的盒子打开才行,最后所有的盒子都打开了,那么我再一层一层的得到盒子的结果,最后得到最开始的盒子的结果
递归有两个特征:
1 终止条件
2 自身调用
终止条件就是我们递归的终止,是调用的出口
自身调用:原问题与子问题的求解方法是一致的,只不过规模有区别
递归的解题思路:
定义方法功能
寻找递归的出口
递归方法的等价关系式
递归的应用场景:
阶乘,累加二叉树遍历
斐波那契数列
快速排序,解析Xml文件
劣势:耗费内存,如果递归次数过多会出现栈溢出问题,递归出口需要好好设计
队列
也是一种线性表,学过数据结构你知道的
就是先进先出,
队头 队尾 断头续尾
队列可以分为:
单向队列和双向队列,双向队列就是两边都可以进出只有中间不可以进出
还可以按照组塞情况:
分为阻塞队列和非阻塞队列
阻塞就是在等,如果队列里面没有元素,我要去一个元素出来,我就等,非阻塞就直接给空值
还可以按照是否有边界分为游街和误解队列
Queue队列
继承自Collection接口,是一个接口,记得我们的Stack直接就是一个类继承于Vector向量类
原因是队列有很多不同的队列,所以需要设计为接口,可以有很多不同的实现
其实我们的lInkedList实现了list接口还有Queue接口,ArrayQueue也实现了Queue接口,具有不同的实现类
底层是不同的实现机制,一个是数组,一个是链表
queue里面提供了方法
add() offer()都是向队列添加元素,但是有差别
如果队列有边界,在超过边界那么就加不进去,会抛出异常
而offer虽然也加不进入,但是加不进去不跑异常返回false
还有差别,remove如果没有元素抛出异常,而poll()会返回null空值,不会异常
element()取值对头元素不会删除,但是如果没有元素也会抛出异常,如果不想要抛出异常,使用peek()
所以Queue提供了两套方法,一种是抛异常,一种是通过返回值来给出信息
抛异常的都是Collection的接口
Deque双向队列
也是一个接口继承自Queue
先进先出或者后进先出综合了两种都可以
对头也是对位,对位也是对头
但是总得分一个方向,所以有一个
addlast add默认就是addlast指的是在对头,其实就相当于单项队列的对头,就是右边那个头添加
addFirst就是在最前面加入(相当于插队哈哈)
offer其实就是和之前的offer一样,区别就那样
获取元素和之前的不一样:
使用getFirst就是get,之前但单向队列就是element()
其实应该抛弃之前单向链表的概念
现在双向链表左边就叫做头first,右边叫作为尾last
所以因此我们getFirst就是左边的,addFirst也是左边的,
需要注意的是,如果我们需要使用栈数据结构的时候,推荐使用Deque而不是Stack
1 因为Deque是接口,而Stack是类,针对接口编程而不针对实现编程,接口可以屏蔽实现的细节
Stack继承自向量Vector,使用Synchronized实现线程安全,使用这种的很少了
Day20
Lambda表达式
其实就是一个语法糖,就是为了让程序更易编写的语法
确实更加简洁了,代码
Stream() filter
Lambda表达式,是java8中的一个重要的特性,允许通过表达式代替接口.Lambda和方法一样提供了一个
正常的参数列表和一个方法主体
其实lambda表达式可以看做是匿名内部类的语法糖也可以称作闭包
优点:代码简洁,开发迅速方便函数式编程,过滤和计算非常容易,改善了集合类的操作
缺点:代码可读性变差,不太容易进行调试
代码的执行效率不一定不传统的for循环要高
基本语法:
(parameters)->expresion
(parameters)->{statements;}
两种方式
三个部分,参数部分,方法中的形参列表,可以声明类型,也可以不声明类型,有JVM自行推断
lambda表达式就是可以直接当做接口的实现实例类对象,其实本质是匿名内部类
第二个部分->可以翻译为”被用于”的意思
第三部分:
可以是表达式也可以是代码块,可以返回值也可以不返回
单个参数其实甚至可以不使用()来表示参数列表,所以lambda表达式的核心标志符是->
函数式接口
顾名思义,一个接口就像一个函数,所以指的就是接口里面有且只有一个抽象方法(抢到抽象方法是因为在jdk1.8之后接口中的default方法不是抽象方法)
这样搜我们就可以使用一个lambda表达式可以给我们的接口作为实现
可以通过注解来验证当前的接口是不是函数式接口@FunctionalInerFace
这个注解主要是用来给我们校验接口是不是函数式接口
lambda表达式的使用
c唯一参数->表达式
(参数列表)->表达式
(参数列表)->{语句 可以有return语句;}
需要注意lambda始终是一个表达式,所以需要在最后添加;表示语句的结束
JDK内置的四个核心函数式接口
接口名 | 类型 | 抽象方法 |
---|---|---|
Consumer |
消费型接口 | void accept(T t) |
Supplier |
供给型接口 | T get() |
Function<T,R> | 函数型接口 | R apply(T t) |
Predicate |
断言型接口 | boolean test(T t) |
消费型接口:
意思是有进无出,有参数,没有返回值
很多类都是使用的Cosumer
供给型接口:
意思就是提供,提供就是返回值,从无参数,平白无故给你一个数据,这就是提供!
函数型接口:
就是工厂,有进有出
断言型接口:
判断机器!
扩展的函数接口:
有些时候需要我们的Function接口函数的参数和返回值类型相同,
使用UnaryOperator来表示
BiFunction可以传两个参数,但是如果也想要参数类型和返回值相同的话,也可使用BinaryOperator三个参数的类型都相同
ToIntFunction接口函数型接口,此方法规定了返回值的l类型为int
还有很多情况的接口,如果你想到这些情况,那么其实参数情况的接口都存在,你只需要简单了解一下就可以利用了
To类型Function就是将to后面类型作为返回值类型固定
Bi就是Binary两个参数的意思
如果没有to直接就是类型Function接口那么这种就是指定了参数的类型,
方法引用
如果lambda表达式中如果有其他方法以及实现了,参数列表,返回值都相同的方法
就可以使用方法引用
语法格式:
1 静态方法引用 类名::方法名
2 实例方法
3 有一种需要特殊条件才能使用的语法 类名::实例方法
4 构造方法引用 构造器的引用 类名::new
特殊条件:
当我们的参数,由第一个参数来调用方法就可以使用类名来知名实例方法
因为如果此时用参数来指明方法,可是参数需要用来作为参数传入函数,所以自己指明方法由将自己作为参数这样不明确
所以使用类名来指明参数所包含的引用函数,这样参数来
Day20
Stream API
集合主要是对于数据结构的封装,存储数据.如果我们要对于是数据进行多次加工,比如filter,排序,聚合,就可使使用stream API
将要处理的元素集合看是一种流,在流的过程中进行数据处理,就像是2传送带,到出口数据已经面目全非
借助StreamAPI对流中的数据进行操作
Stream是一个接口
流操作之后返回的也是一个流,所以可以继续调用方法
最后再将流转换为集合
这种叫做链式编程,java特有的….一直.下去
1 Stream创建
通过Collection接口的stream()方法来返回一个stream
stream也是一个泛型类
其实stream接口是Collection接口里面新添加进来的接口
jdk1.8之后
2 使用Arrays工具类,将数组转为stream
arrays.stream(T[] 数组)
3 使用stream自己的静态方法来创建stream.of(多个参数/…同一类型)
无限流,使用流的方法iterate(种子,fuction)
返回一个无限流就是首尾相接了,回路流,一直路过这些方法
使用limit限制无限流循环的次数
4 使用Files类创建流
Files.lines()
FIles.list()
5 合并流
Stream.concat()
stream的操作分类
1 中间操作
会返回一个新的流,可以有多个中间操作
2 终端操作
不会再返回一个流了,只能有一个终端操作,其实就是不再处理了
你就通过看操作的返回值来看是什么操作
终端操作一般会产生一个新的值或者集合
要吗就是forEach()方法 要么就是收集方法collect()
stream的特性:
1 不存储数据,都是在最后看你收不收集 就是用来按照给定流程对税数据进行加工,
2 不会改变数据源
通常会产生新的集合或值
3 stream具有延时执行的特性
就是懒执行,只有调用了终端操作中间操作才会执行
就像是电线一样,如果你没有搞通,他是不会过来的
与集合之间的差别
1 计算的时机
2 集合框架,里面包含了所有得值,可以对集合进行怎加删除检索…
流是按需计算,只在用户有要求的时候
其实就是想到与迭代,一个一个得来
外部迭代和内部迭代
集合使用外部迭代的方式
流使用内部迭代的方式
Optional类
其实是为了解决一个空指针的问题java8版本引入的新的容器类,代表一个值是否存在,可以有值,也可以为空,不会抛出NullPointerException
常见的方法empty:创建一个 空的Optional
of(T value) 创建一个Optional 如果为空,抛出空指针异常
Ofnullable(T value) 只不过抛出的异常不同是NoSuchElementExceptin
get()获取Optional中的value值
isPresent()判断Optional的值是否存在,返回一个boolean值
还有一个isempy判断是否存在,不存在返回true,jdk11以上才有这个方法
orElse(Tvalue)如果optinal的值为空则返回value作为默认值
orelseget(供给型接口)
这两种的区别有一点比较重要,就是如果为空orelseget中的参数才会执行,而orelse作为默认值无论是否为空都会执行
使用方法:
如果明确对象不能为空,那么使用of方法
如果都有可能,使用ofNullable方法
解决问题:
1 解决控制判断异常,简化控空值判断
2 不能解决的问题
其实不能避免所有的空指针问题
主要用途是作为2返回值类型可以提取里面的值,也可以提起其他的值
与stream API联合使用
3 不能使用的情况
不能序列化,没有实现序列化(用来存储数据不好)
不要将其用在类中的字段类型(就作为返回值)
不用将其用在构造函数和方法参数上.会导致复杂代码
流操作
findFirst查找第一个,返回一个Optional
finalAny()查找任意元素返回Optional,如果是Stream返回第一个元素,相当于findFirst()
如果是parllelStream,返回一个随机元素
anyMatch(断言接口) 如果有一个匹配就返回true
noneMatch(断言接口)如果有一个匹配就返回false
allMatch(如果有一个不匹配就返回false)
过滤和切片
也称作筛选,就是按照一定规则
filter
切片:
其实在Python中就有切片的操作,就是将集合中的一部分拿出来作为一个新的集合
在python中使用的是[::]这种形式,这种叫做顾前不顾后,startIndex到EndIndex之前,正好切片长度就是endIndex-startIndex,如果到startindex那么就需要+1了,这个容易和lenght那种方法搞混
切片有这个几种操作:
1 截断集合
2 在集合中指定跳过某些元素
3 数据去重
截断用到方法limit()这个方法我们在流中用到过
skip(int )跳过多少个数据 如果跳过了流中所有的,返回一个空的流
去重操作distinct(),原理是hashcode的比较+equals,所以你需要先将所存储对象的hashcode方法和equals方法重写
聚合操作:
利用流中所有的数据,聚合成一个或者更少的数据
例如取最大值,取最小值,统计个数count()终端操作
都是终端操作,返回一个值
Day22
映射操作
1 使用map进行映射操作,属于中间操作返回流
接受一个函数作为参数,这个函数会被应用到每个元素上,并即将器映射成一个新的元素
用于转化或用来提取信息
其实map就是一个工厂,会对流中的数据进行改变
但是使用map会改变原有输数据源,如果不想改变只能创建新的对象
flatMap
扁平化
也就是原来我们的流中的元素,也通过map映射成为一个流了,
此时如果要以流中的流中的元素作为单位,俺么你不得不循环两次调用流
所以能不能直接将流扩大,元素缩小到流中流的元素?
这就是扁平化
其实是将流展开到外层流了,相当于合并了
MapTo函数
可以将流转换为具体的类型流,这些预设流,我们提供了一些常用的函数.例如sum
,一般的流不会有这个函数
这种返回值一般都是Optinoal+T类型,使用的时候是getasT函数
比如SUmmaryStatics函数
还有range函数rangeClosed\
想要将特别的流转到流,使用方法boxed
规约,缩减
就是将一个流变成一个值
这不是聚合的功能吗?
其实这种功能就相当于我之前想的那种,就像收盘子一样,流水线上的盘子+我收起来的盘子,最后盘子都被收起来了
当然是属于终端操作
Sorted排序操作
直接使用sorted()函数要求我们流中的元素实现了compateable接口
说sorted()排序是一个自然排序,应该就是升序
其实我们之前使用的collections工具也可以实现排序功能,但是为什么要使用流来排序呢?
我们可以再进行各种操作之后再来筛选,可以省去很多排序的功夫
自定义排序的实现:
排序器:原来自然排序就是利用类中自然地实现的comparable接口来进行排序
那么我们的自定义排列就是非自然排序喽
向sorted函数里面传入一个compartor接口,其实如果真的不是很有自信的话,还是可以使用new Comparaor接口这种方式,再通过IDE提示的方法来简化
comparator接口中的comparing好有一个方法,thenComapring,可以添加如果比较相同的继续比较方法
Collect收集流的终端方法
这个方法就是把流收集起来,最终可以是一个值,也可以是一个集合,注意我们最后是个集合接口的结果
可是调用toList,toSet,toMap,toCollectiono等方法
在使用的Collect(函数中)传入参数Collector.toList()一个list,传入了一个list,所以最后用list来收集数据
在使用toMap的时候需要指明,key和value,使用的是两个函数式接口
再使用collector.toCollection()需要在函数中传入一个构造方法引用,用来作为容器,当然这个new必定是Collection的实现类
很多方法都被取代了:
例如maxBy,minBy,summing,
分组与分区
分区使用partitioningBy
这种方式只能分成两组,符合条件的是一组,不符合条件的是一组
其实实现的方法是,通过收集器,将数据保存为一个Map,以boolea值为键将数据分为true list 和false list两套数据
分组使用groupingBy
知道我们的collect函数中使用Collectors.方法来调用
拼接操作joining
我们知道在python中join就是将数组join为一个用分隔符分隔的字符串
在java中就是将stream中的元素进行拼接
第一种直接使用Collect(Collectors.joining())这种就是直接拼接可以给一个参数作为连接符号
可以给三个参数,分别是,连接符号,前缀后缀
Day23
文件管理:
对于文件的管理而不是读写这一方面.,java使用java.io.File这样一个类来进行管理
三种构造FIle类的方法:
1 直接使用目录或者文件名
2 使用父目录路径+子目录或者文件名
3 使用FIle类+子文件名或者子目录名的形式
常用的方法:
1 创建新文件
这个方法会抛出异常,因为可能路径有问题,此时抛出IOException
如果路径没有问题,当创建文件失败是,会返回false,成功时会返回true
这个操作并不能覆盖掉我们原有的文件
2 创建文件,没错创建目录和创建文件是两种方法
mkdir( )也就是makeDirectory的意思
还是返回一个boolean类型的值
mkdir并不会抛出异常,并且只能创建当前一级目录,如果父目录不存在,那么创建一定不成功
如果想要将所有不存在的目录一并创建,使用方法 mkdirs()多了个s
文件目录的删除:
file.delete方法
返回值是boolean
还有一个deleteOnExit()只有当虚拟机退出了才delete
这种一般用来操作我们的临时文件
一般用createTempFile(prefix+shuffix);创建在系统的临时目录里面,
目录的删除:
也是使用delete,但是如果目录中含有文件或者子目录,那么文件就无法删除
只能删除空目录
可以先获取目录下的所有文件
file.listFIles()可以返回File[]返回当前目录下所有文件和文件夹,如果file是一个文件,那么会返回一个null值
file.getname()方法获取文件名
所以我们先要将文件目录中的所有文件删除,但是如果有子文件夹,也必须先把子文件夹中的所有东西删除
这就是一个递归
先判断file是一个文件还是一个目录:
isFIile()和isDIrectory()
所以设计一个递归删除的deleteDir函数,这种了就是强制删除
其他相关操作:
1 获取父目录 (其实就是就是文件或者目录所在目录)
getParent() 返回的是一个String 路径
如果想要返回FIle类型
使用getParentFile()
2 获取文件的大小
file.length(),单位是字节
3 文件是否可读,可写,可执行
可执行就是canExecute()
可读canRead 可写canWrite()
我们可以通过set来设置文件的状态
例如1 setWritable(boolean)
获取文件路径的操作
1 getPath() 返会String
但是需要注意的是,他拿到的是构造FIle类所使用的参数,如果你使用的相对路径,那么这种方法拿到的也是相对路径
2 getAbsolutePath()
返回的是绝对路径,但是如果你在参数里给的相对路径,会出现bug,其实就是当前路径直接与相对路径来进行连接
3 getCanonicalPath()
获取规范的绝对和唯一路径!!!一般人还不知道
可以去除我们在构造时给相对路径出现的bug
文件最后的修改时间
lastModified() 返回一个long值 其实是一个时间戳
获取磁盘空间的操作:
可以获取当前磁盘所在分区的 磁盘大小:
getTotalSpace() 返回long,给的是字节大小
可用存储大小:
getFreeSpace()
获取虚拟机JVM可用空间大小:
getUsableSpace()
获取所有的磁盘分区:
listRoots() 返回File[]
对文件的重命名:
renameTo(File) 还是会返回一个boolean值
可是实现文件的剪切工作,,但是如果文件名一样那就是只是移动,这样是不行的
这个涉及到操作系统,
产看文件是不是隐藏文件:
ishidden()->boolean
输入输出流
主要是站在我们程序的角度,输入程序的数据流就是输入流input程序产出的数据就是输出流output
有两个概念:
源和目标
源:键盘.文件,网络URL地址,扫描仪,物联网设备
目标:屏幕,文件,网络地址URL,物联网设备
这些是数据传输都是通过流的形式
划分:
- 方向上时是输入流和输出流
- 字节流和字符流
- 按照功能不同节点流,处理流
如果说我们的管道是专门连接,那么就是节点流
如果我们基于节点流,加上处理,那就是处理流
四个大类:
InputStream outPutStream
所有后面是Stream的都是字节流
reader 和 writer
都是面向字符
这四个都是抽象类
其实本质上都是基于字节流来实现的,但是字符流他自带处理字节为字符,这样你就不用关心字转字符的事情了
文件的操作流:
FileinputStream,FIleOutPutStream,FileReader,Filewriter
字符流实现原理:
字节流+编码表
编码表就是常见的字符集:unicode utf-8 utf-16 -32,gbk,gb2312,gb18030最新中文
在进行读写的时候,一部分数据会缓存在内存当中
在关闭的时候自动flush缓存,数据就全部写在容器里了
flush()
有一部分数据,缓存不下就执行写入
try with resource这种写法,会自动帮你关闭resource
write的五种用法:
int c ,stringh,cahr[],chr[] int int,string int int
构造方法FileWriter 是否可追加boolean参数 也有charset选项,参数是StandardCharsets里的
1 String 文件名
2 file File
BufferWirter
缓冲流,效率更高,被称作处理流
需要将节点流作为构造参数
\n这种换行在不同的系统上面可能会有问题
早期的使用\r\n相当于\n
所以bufferWriter有一个方法newLine就是\n但是不受系统影响
FileWriter里面有默认的缓冲区8192个字节
BufferWriter内部有8192个字符缓冲区相当于*2个字节
其实FileWriter的缓冲也是足够用的,为什么用使用bufferWriter呢
我们使用FileWriter每次来一个字符都要去查编码表
bufferwriter自由在缓冲满了或者刷新的时候才回去一次性查编码表,效率更高一些
如果频繁地去写一个文件最好使用Bufferwriter,如果一次使用使用2FileWriter差不多
bufferWriter可以指定缓冲区
在构造方法里面指定
read方法如果到文件结尾返回-1
read(char[])返回读取到的字符数
可使用skip跳过字符数
Day24
文件字节流
FIleinputStream和FileoutputStream
也是read方法,读取的返回一个字节,读到文件尾返回-1
可以把字节流转化为字符流
这样的类就是不单单是字符流Reader了,而是inputStreamReader
将字节流作为参数,例外加上一个StandardCharsets.编码
在将其构造为一个buffer类
对象序列化和反序列化
我们的所有对象都是存储在Java内存当中,这种就是临时存储,下次没有了
如果我们想下次还能使用这个对象,怎么办?
序列化就是将对象按照字节序列的方式去存储
其实有两种方法序列化,一种是Java序列化,一种是Json序列化
后面再讲Json
对象序列化的使用:
流ObjectOutputStream
我们把一个文件输出流作为参数传入ObjectOutputStream
然后Write(一个对象就写进去了
但是对象需要实现序列化,不然会爆不能序列化错误
接口Serializable接口
还有一个Externalizable接口
其实这种接口里面什么都没有,这种其实是一个标识接口,JVM帮你实现了这个
反序列化,你序列化存储了,读取的时候就要还原回来对象
将字节序列重新恢复一个java对象
ObjectInputStream
完成对象反序列化的操作
readObject方法
在反序列化的时候可能会遇到一个问题
序列化版本号出问题
versionUID
每次调用ObjectutputSteam的时候都会有一个序列化编号,如果没有显式的生成 系统会自动生成一个
(用于标识唯一的一个类的版本)
在做反序列化的时候系统重新生成一个新的版本号,和之前的比较就会出现版本号不相同,
所以为了避免这种问题,需要显示的生成一个版本号
版本号就是在实现序列化的类里面一个private static final long serialversionUID = L;的一个long型的值
其实之前你实现序列化如果没有写这一项编译器会提示你的,但是现在不会提示了,你可以在检查里面搜一下UID,会有JVM不带序列化版本号的勾选,就会提示你没有写版本号了
到时候你点一下,自动生成
这种对象的存储,例如游戏存档就是这种
如果你是以文本文件的形式来进行存储,那么很容易被外界修改
现在生成的序列化文件,是一种字节流文件,你打开完全不知道里面是什么东西,正能通过程序来进行识别
这种文件你改过之后就无法再读取了,只能再生成一个
如果你的对象里面还有对象,那么你的对象里的对象也需要序列化
序列化注意的问题:
如果你要序列化的对象,里面的具有引用类型的舒属性,那么也需要序列化,否则这个对象不可以被序列化
如果你的引用属性类型不是你自己的,不能再来写了,该怎么办?
使用关键字transient有选择的序列化
如果加了transient 那么就不会给他序列化,基本数据类型会给默认值,引用类型会给null空值
对象的类名,属性名,属性值都会被序列化,但是类的方法和static属性,transient属性都不会背序列化,也就是不会存储起来
使用Externalizable接口来实现序列化
序列化的过程是一个递归的过程,相对是一个比较缓慢地,对于需要禁止序列化的变量,需要使用transient,如果属性较多不需要序列化,那么你就比较复杂,无法控制字段的序列化和反序列化的方式
序列化过程不会调用构造方法因此会导致方法内的逻辑丢失
Externalizable其实是serializable的子接口,如果要使用Externalizable来实现序列化,只需要实现这个接口就试了
我们就可以自定义我们的序列化过程
反序列化时会调用无参构造方法
此时transient关键字没用了,主要依赖于自定义处理,而不是JVM的处理
而且也可以使用static属性,都可以序列化
标准输入输出流
有三种:
标准输入
标准输出
标准错误输出
其实标准输入stream.in
是一个字节输入流inputStream
可以扭转输入流setIn(传一个inputStream的子类就行了)
标准错误输出和标准输出是一个printstream,其实就是一个outputStream的子类
也可以扭转
字节数组
在内存中,传输或者存储
可以进行标记处理,然后复位
mark(int)里面的参数是标记,位置就是当前位置
reset()恢复到标记位置
如果没有标记就是回到最开始
toBytes()
toString
等等
read
DateInputStream…
对数据进行处理的
有些时候不需要进行对象序列化
就像是Externalizable里面的付姐方法
你怎么写进去的,你就怎么读出来
如果你的写入对象是一个基本数据类型或者字符串,就不需要使用对象序列化技术,直接使用dataInout’Sream或者..
你需要自行规定顺序,只要使用对应方法就可以读取
Day25
java多线程
其实都是多任务系统,但是linux里面其实只有任务的概念,没有线程和进程概念
程序其实就是指令和数据组成
要运行程序需要将指令加载到CPU,数据加载到内存里面
指令在运行过程中,需要访问磁盘文件,网络设等等
进程就是用来加载指令,管理内存,管理Io操作
进程具有读来创尔功能程序,可以申请使用系统资源,是一个动态概念
当一个程序被运行,从磁盘加载到内存中 ,那么一个进程就开始了
进程可以视作一个程序的实例,有的程序只能同时存在一个实例,有的可以有多个实例
线程:
一个进程里面可以分为一到多个线程,一个线程就是一个指令流,指令流就是指令按照一定的顺序交给CPU来执行
java中线程就是作为最小的调度单位,进程是资源分配的最小单位
在Windows中进程是不活动的只是作为线程的容器
区别:
进程就是负责加载管理资源的 ,线程是负责执行指令的
进程基本是相互独立的,线程存在与进程中是进程的是个子集
进程拥有共享的资源,供我们的进的中的内部线程共享
进程之间的通信较为复杂和困难
同一台计算机的进程通信IPC
不同的计算机的进程通信,需要通过网络并遵循共同的协议
线程的通信比较简单,因为线程具有共享内存,多个线程可以访问同一项变量
可以实现任务调度
线程更加轻量级,线程上下文切换要比进程低很多
并行合并发
并发其实就是在单核CPU下,线程是以串行的方式来运行的
任务调度器组件,CPU时间片分给不同的程序使用,片很短导致看起来线程可能是同时运行的
有一个程序计数器,每一个程序都有一个独立的程序计数器,CPU
会查看你的指令执行到哪里了
并行
在多核CPU下每个核都可以完成线程的执行
有神多时候并行和并发同时存在
线程很多,比CPU核心数目多
互不干扰才叫并行
线程的创建和运行
两种方式:
继承Tread类
实现接口创建
为什么不直接调用run方法?
如果直接调用Run那么就不是一步执行,都是在主县城里面执行的,主线程的指令流会跑到run里面去执行
是不会开除新的线程和空间地址
先处处Runable接口
可以再传一个名字String
更推荐使用第二种接口方法来创建线程
更容易和线程池高级API配合,并且让类脱离Tread继承体系
多线程使用的场景:
1 耗时较高的任务(大文件件的查找,视频格式转换 ,)应该开启一个新的线程,避免主线程被阻塞
第三种方式:
FutureTask配合Tread
Future未来的意思
Future是jdk1.5引入的一个接口,用于异步获取结果
非阻塞模型
可以用来接收多线程异步执行的结果,毕竟我们的run方法都是void,没有返回值,除非给一个共享变量
Future表示一个可能还没有完成执行的异步任务的结果
针对这个结果,我们可以添加call回调处理,以便我们在任务执行成功或失败的时候做相关处理
使用的场景:
要获取一个长时间运行的任务,可以使用Future来执行
我们就取暂时处理其他任务
包括计算密集,处理大数据,远程方法调用,网络爬虫
FutureTask和Future的关系
Futrue是一个泛型接口
使用到的方法
1 cancel(boolean)取消任务
2 iscanceled()是否取消
3 isDone()是否完成,异常或取消都表示完成
4 get获取计算的结果超时时间设置
FutureTask实现了RunableFuture
而RunableFuture有继承了Runable和Future
callable和Runable接口的差别
1 runable是Run方法作为线程运行的入口,callable方法是call方法
call方法会抛出异常,runable不会
实现callable方法可以对线程运行的异常进行补货从而得知异常的原因
callable配合Future对象,获取一步处理结果
所以异步就是给了一个线程给他,只不过最后我们获取那个值,也有个任务取消,超时时间等方法
查看进程和线程的方法:
可以通过tasklist来取查看所有进程
终端
可以杀死进程
taskkill /?查看帮助
/f /PID pid
使用指令jps
查看所有java进程
jstack:生成jVM当前是可所有线程快照
jconsole以图形界面方式查看java进程中的线程运行状
线程的状态
从操作系统层面,划分了五种状态:
初始状态(新建):创建了线程对象,还没有与操作系统线程关联
可运行状态(就绪):当前线程已经与操作系统线程关联,可以有CPU调度执行
运行状态:获取了CPU时间片在运行状态中
阻塞状态:在读写文件的时候调用了阻塞的API应用例如BIO读写文件,会导致线程上下文切换进入阻塞状态
当BIO完成之后,有操作系统唤醒阻塞线程,进入可执行状态(就绪)
对于阻塞线程来讲只要不被唤醒,那么操作系统调度器是不会考虑去调度她们的
终止状态:
全部结束了线程,不会再转换为其他状态
从编程语言方面,有六中状态:
查看Tread线程类,发现里面有枚举类型State
new runable blocked waiting timed_waiting terminated
new就是新建之后
rubable就是调用了start方法java的runable状态包括了操作系统上的运行状态和可运行状态和阻塞状态
java区分不出来阻塞
blocked waiting timed_waiting其实都是在java层面对阻塞的细分情况
terminated就是线程的结束
getstate()
timed_wating是一个具有时效的等待
而wating没有时效的等待,sleep(time)这个就是有时效
thread.join()这个等待没有时效,没人知道到底要等等多久
blocked锁
线程优先级:
setPriority(1-10)
会提示任务调度器,优先掉线城,但是都会调用的,始终没有调用的操作系统会自动增加优先级
getpriority()
sleep和yield方法
调用sleep是的线程休眠:进入timed_waiting
其他线程可以打断线程休眠:interrupt
在睡眠的线程被打断时会抛出异常,线程被打断线程不一定立刻会被调度
还是需要排队
sleep方法可以防止CPU使用过高的问题,比如你的事件捕获线程,不可能一直占用CPU’大量算力
所以在循环里面sleep(100)也差不多,但是可以大大降低CPU使用频率
但是现在我们基本不适用sleep来完成这一功能
而是用TimeUnit.时间单位.sleep()
这样可读性更强
yield方法
可以让出CPU,进入就绪状态
具体的实现要依赖于操作系统
但是这个方法其实很少使用到,调试的时候用到差不多
线程联合
join方法
等待当前加入的线程调用结束,才能继续向下执行
为了防止联合线程死了,我们传一个timeout时间进去
interrupt方法
打断sleep,wait,join方法,处理阻塞状态的线程,会清空打断的状态(false)
打断正常运行的线程,不会清空打断状态标记是true
isInterrupt()就看这个线程是否中断
这个时候你在睡觉,我打断你,你就运行了,所以interrput返回false,你现在没有中断
如果你线程正在运行,我打断你,你就没有运行了
isInterrupt返回true
守护线程:
其实也是一个线程的方法
java程序中有两种线程,一种是用户线程userThread
一种是守护线程DaemonThread
指的是程序运行时,在后台提供一种通用服务线程,当所有用户线程结束时,守护线程也就终止了,同时会杀死所有进程中的守护线程
setDaemon(bolean)
应用场景:
java垃圾回收线程,
Day26
多线程共享数据问题
例如我们使用两个线程对同一个变量进行自增和自减操作,一万次
结果有时候是0,有时候是正数,有时候是负数
为什么出现现在这种情况呢?
直接反编译得到字节码
x++操作生成的字节码如下:
第一步获取变量值
第二步准备一个常量
自增操作放在常量里面
最后将修改后的值重新存储
进去
所以每一个对于变量的操作都是分为读取操作之后再覆盖这种
临界区:
如果一段代码块设计到多线程的共享变量读写操作,这段代码就称为临界区
当一个对象或者一个不同步的共享状态,被两个或者以上的线程同时修改时,对于访问顺序必须严格执行
否则可能产生竞态条件
问题解决:
使用同步方法
synchronized
这是一种对象锁的概念,互斥锁,在同一时间最多只能有一个线程持有锁,其他线程想要获取这个锁就会阻塞
等到锁释放了才能获取
语法:
1 |
|
这个代码块的代表的操作就是
执行到这里就加锁,代码块结束释放
如果使用synchronized方法就是方法加锁,到时候释放
锁可以锁对象,也可以锁整个类
如果你是静态方法加锁,其实就是锁了整个类
monitor
监视器也可以叫做管程
每个java对象都可以关联一个管程对象
如果使用sybchroonized给对象加锁,对象头的markword中,被设置指向Monitor对象的指针
线程通信wait和notify
针对上面的图发现条件不满足 调用wait方法进入waitset方法,变成wating状态 Bloacked和waiting状态不会占用CPU时间片
非公平竞争
notify或者notifyall
这些方法都属于 Object类方法,需要获取对象锁才能使用
所以这两个状态是不一样的,
获取到锁之后进入waiting和没有获取到锁
可以给wait方法传入时间,自动唤醒
wait和sleep的区别
第一:sleep是Thread类的方法,而Wait是Object的方法
sleep是不需要和synchroized配合使用
sleep再执行期间不会释放对象锁]
wait方法金软双钢带会释放对象锁
不过他们都是Timed_waiting状态
notify方法是从waiting中唤醒一个线程,但是到底是那个县城实在不知道
所以,使用notifyall把所有的线程都唤醒再来
当有多个线程正在等待需要唤醒时,使用notifyall,进行循环判断
死锁
如果你一个线程中需要获取多把锁,就容易出现死锁问题
相当于就是都要统一天下,但是三国鼎立的情况
所以想要统一天下,必须消灭其余的线程
死锁的四个条件:
1 互斥条件:一个资源每次只能被一个进程使用(有锁 )
2 请求与保持条件:一个线程因请求资源而阻塞时,对已有资源保持不放
3 不剥夺条件:当前线程已经获取的资源,在没有使用完之前,不能前行剥夺
4 循环扥带条件等待条件:若干线程之间形成一种头尾相连的循环等待资源关系
死锁的处理:
预防死锁,破坏死锁四个条件中的一个,但是不能破坏互斥条件(这个是保证线程安全的基本),其他三个条件可以破坏
避免死锁,在资源动态分配过程中,使用某种方式阻止系统进入不安全状态.
检测死锁,允许在运行中出现死锁,可以设置检测死锁的发生,并采取相关措施
解除死锁:采用资源剥夺,或者撤销进程法,进程后退法等,将进程从死锁状态中解除出来
JVm内存的可见性
从主内存读取boolean类型的值,到工作内存中
以减少对主内存的访问
所以,想要获取主内存中的变量访问,要么添加代码,使得其主动去找主内存中访问
使用volatile关键字用来修饰变量,表示这个变量不可以被编译器因为一些未知的因素去改变(拒绝优化)
保证访问稳定
ThreadLocal
线程变量,意思是ThreadLocal中填充的变量属于当前线程,当前线程对于其他线程来说是不可见的,是当前线程独有的变量
通常被private static修饰,所有副本都可以被回收
相当于对于每个线程,这个对象都有不同的一面
其实就是里面设置一个判断线程的方法ThreadLocal类的方法自动判断线程号从而给出不同的结果,里面是线程和value的Map
区别:
使用synchronized主要用于线程之间的数据共享
并且在同一个时间段,锁智能杯一个线程所持有
而ThreadLocal为每一个线程都提供了副本,是的每一个线程在同一个时间点访问的不是同一个对象就实现了隔离多个线程
synchronized时间换空间
ThreaadLocal以空间换时间,互不影响
优点:
传递数据 ,减少了参数直接传递带来的代码耦合
线程隔离,数据之间相互隔离,同时又可以并发,避免使用锁机制带来性能损失