1,算法
算法:指解决某个实际问题的过程和方法
(1)冒泡排序
for (int i = 0; i < arr.length-1; i++) {
for (int j = 0; j < arr.length-i-1; j++) {
if (arr[j]>arr[j+1]){
int temp=arr[j+1];
arr[j+1]=arr[j];
arr[j]=temp;
}
}
}
(2)选择排序
for (int i = 0; i < arr.length-1 ; i++) {
for (int j = i+1; j < arr.length; j++) {
if (arr[i]>arr[j]){
int temp=arr[i];
arr[i]=arr[j];
arr[j]=temp;
}
}
}
优化
for (int i = 0; i < arr.length-1 ; i++) {
int minIndex=i;
for (int j = i+1; j < arr.length; j++) {
if (arr[minIndex]>arr[j]){
minIndex=j;
}
}
if (i!=minIndex){
int temp=arr[i];
arr[i]=arr[minIndex];
arr[minIndex]=temp;
}
}
(3)二分查找法
前提条件:数组中的数据必须是有序的
核心思想:每次排除一半的数据,查询数据的性能明显提高极多
二分查找正常的折半条件应该是开始位置left<结束位置right
public static int binarySearch(int[]arr,int data){
int left=0;
int right=arr.length-1;
while(left<=right){
int middle=(left+right)/2;
if(data<arr[middle]){
right=middle-1;
}else if (data>arr[middle]){
left=middle+1;
}else {
return middle;
}
}
return -1;
}
Arrays:
Arrays.binarySearch(arr,81)
2,正则表达式
正则表达式:
作用一:用来校验数据格式是否合法
作用二:在一段文本中查找满足要求的内容
正则表达式的方法 | 说明 |
public boolean matches(String regex) | 判断字符串是否匹配正则表达式,匹配返回true,不匹配返回false |
(1)书写规则
字符类(只能匹配单个字符) | 说明 |
[abc] | 只能是a, b,或c |
[^abc] | 除了a, b,c之外的任何字符 |
[a-zA-Z] | a到z A到Z,包括〔范围) |
[a-d[m-p]] | a到d,或m到p |
[a-z&&[def]] | d,e,或f(交集) |
[a-z&&[^bc]] | a到z,除了b和c(等同于[ad-z]) |
[a-z&&[^m-p]] | a到z,除了m到p(等同于[a-lq-z]) |
预定义字符(只能匹配单个·字符) | 说明 |
. | 任何字符 |
\d | 一个数字:[0-9] |
\D | 非数字:[^0-9] |
\s | 一个空白字符: |
\S | 非空白字符:[^\s] |
\w | [ a-zA-Z_0-9] |
\W | [^\w]一个非单词字符 |
数量词 | 说明 |
X? | X,一次或零次 |
X* | X,零次或多次 |
X+ | X,一次或多次 |
X{n} | X,正好n次 |
X{n, } | X,至少n次 |
X{n,m} | X,至少n次但不超过m次 |
其他几种 | 说明 |
(?i) | 忽略大小写 |
| | 或 |
() | 分组 |
System.out.println("abc".matches("(?i)abc"));//true
System.out.println("ABC".matches("(?i)abc"));//true
System.out.println("aBc".matches("a(?i)bc"));//true
System.out.println("ABc".matches("a(?i)bc"));//false
System.out.println("123".matches("\\d{3}|[a-z]{3}"));//true
System.out.println("abc".matches("\\d{3}|[a-z]{3}"));//true
System.out.println("aAc".matches("\\d{3}|[a-z]{3}"));//false
System.out.println("我爱编程编程666666".matches("我爱(编程)+(666)+"));//true
System.out.println("我爱编程编程6666666".matches("我爱(编程)+(666)+"));//false
符号 | 含义 | 举例 |
[] | 里面的内容出现一次 | [abc] |
^ | 取反 | [^abc] |
&& | 交集,不能写单个的& | [a-z&&m-p] |
. | 任意字符 | \n回车符号不匹配 |
\ | 转义字符 | \\d |
\\d | 0-9 | \\d+ |
\\D | 非0-9 | \\D+ |
\\s | 空白字符 | |
\\S | 非空白字符 | [^\s] |
\\w | 单词字符 | [a-zA-Z_0-9] |
\\W | 非单词字符 | [^\w] |
() | 分组 | a(bc)+ |
| | 写在方括号外面表示并集 | ab|AB |
(2)搜索替换,分割内容
方法名 | 说明 |
public String replaceAll(String regex,String newStr) | 搜索正则表达式匹配的内容进行替换 |
public String[] split(String regex) | 按照正则表达式匹配的内容进行分割字符串,返回一个字符串数组 |
String s1="我我我喜欢编编编编编编编编编编程程程";
System.out.println(s1.replaceAll("(.)\\1+","$1"));
(.)一组:.匹配任意字符的
\\1:为这个组声明一个组号:1号
+:声明必须是重叠的字
$1:可以去取到第一组代表的那个重复数字
3,异常
异常:就是代表程序出现的问题
Error: 代表的系统级别错误(属于严重问题),也就是说系统一旦出现问题,sun公司会把这些问题封装成Error对象给出来,说白了,Error是给sun公司自己用的,不是给我们程序员用的,因此我们开发人员不用管它
Exception:叫异常,它代表的才是我们程序可能出现的问题,所以,我们程序员通常会用Exception以及它的孩子来封装程序出现的问题
运行时异常:RuntimeException及其子类,编译阶段不会出现错误提醒,运行时出现的异常(如:数组索引越界异常)
编译时异常:编译阶段就会出现错误提醒的 (如:日期解析异常)
(1)输出异常(throw)
在方法上使用throws关键字,可以将方法内部出现的异常抛出去给调用者处理
方法throws异常1,异常2,异常3...{
...
}
(2)捕获异常(try...catch)
直接捕获程序出现的异常
try{
//监控可能出现异常的代码!
}catch(异常类型1 变量){
//处理异常
}catch(异常类型2 变量){
//处理异常
}...
(3)自定义异常
Java无法为这个世界上全部的问题都提供异常类来代表,如果企业自己的某种问题,想通过异常来表示,以便用异常来管理该问题,那就需要自己来定义异常类了
1)自定义运行时异常
定义一个异常类继承RuntimeException. |
重写构造器 |
通过throw new异常美(xxx)来创建异常对象并抛出, 编译阶段不报错,提醒不强烈,运行时才可能出现 |
2)自定义编译时异常
定义一个异常类继承Exception. |
重写构造器 |
通过throw new异常类(xxx)来创建异常对象并抛出, 编译阶段就报错,提醒更加强烈! |
(4)开发中对于异常的常见处理方式
1
public class ExceptionTest2 {
public static void main(String[] args) {
try {
test1();
} catch (FileNotFoundException e) {
System.out.println("文件不存在");
e.printStackTrace();
} catch (ParseException e) {
System.out.println("解析时间有问题");
e.printStackTrace();
}
}
public static void test1() throws FileNotFoundException, ParseException {
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date d=sdf.parse("2028-11-11 10:24:11");
System.out.println(d);
test2();
}
public static void test2() throws FileNotFoundException {
InputStream is=new FileInputStream("D:/nei");
}
}
//简化后
public class ExceptionTest2 {
public static void main(String[] args) {
try {
test1();
}catch (Exception e) {
System.out.println("你当前操作有问题");
e.printStackTrace();
}
}
public static void test1() throws Exception {
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date d=sdf.parse("2028-11-11 10:24:11");
System.out.println(d);
test2();
}
public static void test2() throws Exception{
InputStream is=new FileInputStream("D:/nei");
}
}
2
public class ExceptionTest3 {
public static void main(String[] args) {
while (true) {
try {
System.out.println(getMoney());
break;
} catch (Exception e) {
System.out.println("请您输入合法的数字");
}
}
}
public static double getMoney(){
Scanner sc=new Scanner(System.in);
while (true) {
System.out.println("请输入合适的金额:");
double money=sc.nextDouble();
if (money>=0){
return money;
}else {
System.out.println("您输入的金额不合适");
}
}
}
}
4,集合框架(一)
Collection(单列集合):每个元素(数据)只包含一个值
Map(双列集合):每个元素包含两个值(键值对)
(1)Collection
特点:
1,List系列集合:添加的元素是有序,可重复,有索引
ArrayList,LinekdList:有序,可重复,有索引
2,set系列集合:添加的元素是无序,不重复,无索引
HashSet:无序,不重复,无索引
LinkedHashSet:有序,不重复,无索引
TreeSet:按照大小默认升序排序,不重复,无索引
1)Collection的常用方法
方法 | 说明 |
public boolean add(E e) | 添加元素,添加成功返回true |
public void clear() | 清空集合的元素 |
public boolean isEmpty() | 判断集合是否为空,是空返回true,反之 |
public int size() | 获取集合的大小 |
public boolean contains(Object obj) | 判断集合中是否包含某个元素 |
public boolean remove(E e) | 删除某个元素,如果有多个重复元素默认删除前面的第一个 |
public Object[] toArray() | 把集合转换成数组 |
c1.addAll(c2) | 把c2集合的全部数据倒入到c1集合中去,c2中还有数据 |
2)迭代器
迭代器:迭代器是用来遍历集合的专用方式(数组没有迭代器),在Java中迭代器的代表是lterator
获取迭代器的方法:
方法名称 | 说明 |
Iterator<E> iterator() | 返回集合中的迭代器对象,该迭代器对象默认指向当前集合的第一个元素 |
Iterator迭代器中的常用方法
方法名称 | 说明 |
booleap hasNext( ) | 询问当前位置是否有元素存在,存在返回true ,不存在返回false |
E next( | 获取当前位置的元素,并同时将迭代器对象指向下一个元素处 |
3)增强for循环
1,即可以遍历集合,又可以遍历数组
2,增强for遍历集合,本质就是迭代器遍历集合的简化写法
格式:
for{元素的数据类型 变量名:数组或者集合}{
}
4)lambda表达式
方法名称 | 说明 |
default void forEach(Consumer<? super T> action) | 结合lambda遍历集合 |
//源码
c.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
});
//简化
c.forEach((String s)->{
System.out.println(s);
});
//简化
c.forEach( s->System.out.println(s) );
//简化
c.forEach(System.out::println);
(2)List集合
List集合因为支持索引,所以多了很多与索引相关的方法,当然,Collection的功能List也都继承了
方法名称 | 说明 |
void add(int index,E element) | 在此集合中的指定位置插入指定的元素 |
E remove(int index) | 删除指定索引处的元素,返回被删除的元素 |
E set(int index,E element) | 修改指定索引处的元素,返回被修改的元素 |
E get(int index) | 返回指定索引处的元素 |
List<String> list=new ArrayList<>();//经典代码
1)List支持的遍历方式
(1)for循环
(2)迭代器
Iterator<String> it=list.iterator();
while (it.hasNext()){
}
(3)增强for循环
for (String s:list){
}
(4)Lambda表达式
list.forEach(s->{
});
2)ArrayList集合的底层原理
是基于数组实现的
数组的特点:查询速度快(注意:是根据索引查询数据快):查询数据通过地址值和索引定位,查询任意数据耗时相同
删除效率低:可能需要把后面很多的数据进行前移
添加效率极低:可能需要把后面很多的数据后移,再添加元素;或者也可能需要进行数组的扩容
ArrayList集合的底层原理:
1,利用无参构造器创建的集合,会在底层创建一个默认长度为0的数组
2,添加第一个元素时,底层会创建一个新的长度为10的数组
3,存满时,会扩容1.5倍
4,如果一次添加多个元素,1.5倍还放不下,则新创建数组的长度以实际为准
ArrayList集合应用场景:
1、ArrayList话合:根据索引查询数据, 比如根据随机索引取数据(高效)! 或者数据量不是很大时 !
2、ArrayList不话合:数据量大的同时,又要频繁的进行增删操作的
3) LinkedList集合的底层原理
是基于双链表实现的
单向链表:
链表中的结点是独立的对象,在内存中是不连续的,每个结点包含数据值和下一个结点的地址
特点:1,查询慢,无论查询哪个数据都要从头开始找
2,链表增删相对快
双向链表:
特点:查询慢,增删相对较快,但对首尾元素进行增删改查的速度是极快的
LinkedList新增了很多首尾操作的特有方法
方法名称 | 说明 |
public void addFirst(E e) | 在该列表开头插入指定的元素 |
public void addLast(E e) | 将指定的元素追加到此列表的末尾 |
public E getFirst() | 返回此列表中的第一个元素 |
public E getLast() | 返回此列表中的最后一个元素 |
public E removeFirst() | 从此列表中删除并返回第一个元素 |
public E removeLast() | 从此列表中删除并返回最后一个元素 |
应用场景:可以设计队列
可以设计栈:数据进入栈模型的过程称为:压/进栈(push)
数据离开栈模型的过程称为:弹/出栈(pop)
(3)set集合
特点:无序,添加数据的顺序和获取出的数据顺序不一致,不重复,无索引
HashSet:无序、不重复、无索引
LinkedHashSet:有序、不重复、无索引
TreeSet:排序(默认升序)、不重复、无索引
Set<Integer> set=new HashSet<>();//经典代码
1)HashSet的底层原理
HashSet:无序、不重复、无索引
【1】哈希值
1,就是一个int类型的数值,Java中每个对象都有一个哈希值
2,Java中的所有对象,都可以调用Obejct类提供的hashCode方法,返回该对象自己的哈希值
public int hashCode() | 返回对象的哈希码值 |
特点:
1,同一个对象多次调用hashCode()方法返回的哈希值是相同的
2,不同的对象,它们的哈希值一般不相同,但也有可能会相同(哈希碰撞) 例如abc与acD
HashSet的底层原理:
是基于哈希表实现
哈希表是一种增删改查数据,性能都较好的数据结构
哈希表:
哈希表是一种增删改查数据性能都较好的结构
(1)JDK8之前,哈希表=数组+链表
1,创建一个默认长度16的数组,默认加载因子为0.75,数组名table
2,使用元素的哈希值对数组的长度求余计算出应存入的位置
3,判断当前位置是否为null,如果是null直接存入
4,如果不为null,表示有元素,则调用equals方法比较相等,则不存;不相等,则存入数组
JDK 8之前,新元素存入数组,占老元素位置,老元素挂下面
JDK 8开始之后,新元素直接挂在老元素下面
(2)JDK8开始,哈希表=数组+链表+红黑树
JDK8开始,当链表长度超过8,且数组长度>=64时,自动将链表转成红黑树
【2】数据结构(树)
1,二叉树
二叉查找树存在的问题:
当数据已经是排好序的,导致查询的性能与单链表一样,查询速度变慢!
2,红黑树
红黑树:就是可以自平衡的二叉树
红黑树是一种增删改查数据性能相对都较好的结构
【3】去重复原理
HashSet集合默认不能对内容一样的两个不同对象去重复
比如内容一样的两个学生对象存入到HashSet集合中去,HashSet集合是不能去重复的
如果希望Set集合认为2个内容一样的对象是重复的,必须重写对象的hashCode()和equals()方法
2)LinkedHashSet集合的底层原理
LinkedHashSet:有序、不重复、无索引
1,依然是基于哈希表(数组、链表、红黑树)实现的
2, 但是,它的每个元素都额外的多了一个双链表的机制记录它前后元素的位置
3)TreeSet集合
排序(默认升序)、不重复、无索引
基层基于红黑树实现的
注意:
对于数值类型: Integer , Double,默认按照数值本身的大小进行升序排序
对于字符串类型:默认按照首字符的编号升序排序
对于自定义类型如Student对象,TreeSet默认是无法直接排序的
自定义排序规则:
方法一:让自定义的类(如学生类)实现Comparable接口,重写里面的compareTo方法来指定比较规则
方法二:
通过调用TreeSet集合有参数构造器,可以设置Comparator对象(比较器对象,用于指定比较规则
public TreeSet(Comparator<? super E> comparator)
两种方式中,关于返回值的规则;
如果认为第一个元素>第二个元素返回正整数即可。
如果认为第一个元素<第二个元素返回负整数即可。
如果认为第一个元素=第二个元素返回0即可,此时Treeset集合只会保留一个元素,认为两者重复
注意∶如果类本身有实现Comparable接口,TreeSet集合同时也自带比较器,默认使用集合自带的比较
(4)查询方法
问题 | 方法 |
如果希望记住元素的添加顺序,需要存储重复的元素,又要频繁的根据索引查询数据? | 用ArrayList集合(有序、可重复、有索引),底层基于数组的(常用) |
如果希望记住元素的添加顺序,且增删首尾数据的情况较多? | 用LinkedList集合(有序、可重复、有索引),底层基于双链表实现的 |
如果不在意元素顺序,也没有重复元素需要存储,只希望增删改查都快? | 用HashSet集合(无序,不重复,无索引),底层基于哈希表实现的(常用) |
如果希望记住元素的添加顺序,也没有重复元素需要存储,且希望增删改查都快? | 用LinkedHashSet集合(有序,不重复,无索引),底层基于哈希表和双链表 |
如果要对元素进行排序,也没有重复元素需要存储?且希望增删改查都快? | 用TreeSet集合,基于红黑树实现 |
(5)集合的并发修改异常
使用迭代器遍历集合时,又同时在删除集合中的数据,程序就会出现并发修改异常的错误
由于增强for循环遍历集合就是迭代器遍历集合的简化写法,因此,使用增强for循环遍历集合,又在同时删除集合中的数据时,程序也会出现并发修改异常的错误
怎么保证遍历集合同时删除数据时不出bug?
使用迭代器遍历集合,但用迭代器自己的删除方法删除数据即可
如果能用for循环遍历时:可以倒着遍历并删除;或者从前往后遍历,但删除元素后做i--操作
Iterator<String> it=list.iterator();
while(it.hashNext()){
String name=it.next();
if (name.contains("李")){
it.remove();//删除迭代器当前遍历到的致据,每删除一个数据后,相当于也在底层做了i--
}
}