Java基础
Java标识符规则
- 由字母、数字、下划线和美元$组成,其中数字不能打头
- 标识符不能是Java关键字和保留字(const、goto)和特殊的直接量(true、false、null),但可以包含保留关键字和保留字和直接量
- 不能包含空格
- 只能包含美元符号$,不能包含@、#、&等其他特殊字符
Java中四类八种基本数据类型
第一类:整数类型 byte, short, int, long
第二类:浮点型 float, double
第三类:逻辑型 boolean(它只有两个值可以取true, false)
第四类:字符型 char
强制类型转换
char < short < int < float < double 不同类型运算结果类型向右边靠齐。
案例:
int a=10 ; double b=3.14 ; 则表达式 'A'+a+b 值的类型是double.
注意是‘A’,而不是“A”进行的字符串拼接。
判断数据类型的方法:
1 public class test1 2 { 3 public static void main(String[] args){ 4 int a=10 ; 5 double b=3.14; 6 System.out.println(getType('A'+a+b)); 7 } 8 9 public static String getType(Object obj){ 10 return obj.getClass().toString(); 11 } 12 }
servert的生命周期
servlet处于服务器进程中,它通过多线程方式运行其service方法,servlet里的实例变量,是被所有线程共享的,所以不是线程安全的.
1、加载阶段
servlet容器启动时,读取web.xml文件的信息,指定servlet对象,根据配置文件信息创建servletConfig对象,并将参数信息传递给init()对象,
init()方法是servlet生命的起点。一旦加载了某个servlet,服务器将立即调用它的init()方法
3、响应客户请求响应阶段
客户端第一次进行访问servlet进行执行 ----- doget dopost,在调用doGet和doPost方法时会构造servletRequest和servletResponse请求和响应对象作为参数。
4、终止阶段destroy
servlet的生命周期,由Tomcat容器进行管理 Tomcat停止session绘画结束,但是servlet并未销毁。
关于异常
1.若try代码块内含有return,同时存在finally代码块(代码块内无return值)时,先执行finally函数的值。
2.若try代码块内含有return,同时存在finally代码块且代码块内含有return值时,此时finally代码块内的return值将直接返回。
如果try语句里有return,那么代码的行为如下:
1.如果有返回值,就把返回值保存到局部变量中
2.执行jsr指令跳到finally语句里执行
3.执行完finally语句后,返回之前保存在局部变量表里的值如果try,finally语句里均有return,忽略try的return,而使用finally的return.
3.在try语句块或catch语句块中执行到System.exit(0)直接退出程序
四种引用数据类型
强-弱-软-虚,
强:new了一个Object对象,并将其赋值给obj,这个obj就是new Object()的强引用,强引用的特性是只要有强引用存在,被引用的对象就不会被垃圾回收。例如,Integer就是引用数据类型。
软:软引用在java中有个专门的SoftReference类型,软引用的意思是只有在内存不足的情况下,被引用的对象才会被回收。在内存充足的情况下,SoftReference引用的对象是不会被回收的。
弱:weakReference和softReference很类似,不同的是weekReference引用的对象只要垃圾回收执行,就会被回收,而不管是否内存不足。gc过后,弱引用的对象被回收掉了。
虚:PhantomReference的作用是跟踪垃圾回收器收集对象的活动,在GC的过程中,如果发现有PhantomReference,GC则会将引用放到ReferenceQueue中,由程序员自己处理,当程序员调用ReferenceQueue.pull()方法,将引用出ReferenceQueue移除之后,Reference对象会变成Inactive状态,意味着被引用的对象可以被回收了。
重载与重写
方法重载(overload):
1.必须是同一个类;
2.方法名(也可以叫函数)一样;
3.参数类型不一样或参数数量不一样;
4.final修饰的方法能被重载,不能被重写。
方法重写(override):
两同两小一大原则:
1.方法名相同,参数类型相同;
2.子类返回类型小于等于父类返回类型;
3.子类抛出异常小于等于父类抛出异常;
4.子类访问权限大于等于父类方法访问权限。
JVM
1.垃圾回收在jvm中优先级相当相当低;
2.垃圾回收器(GC)程序开发者只能推荐JVM进行回收,但何时回收,回收哪些,程序员不能控制;
3.垃圾回收机制只是回收不再使用的JVM内存,如果程序有严重BUG,照样内存溢出;
4.进入DEAD的线程,它还可以恢复,GC不会回收。
jvm内存
pc寄存器又称程序计数器。
Xms 起始内存
Xmx 最大内存
Xmn 新生代内存
Xss 栈大小。 就是创建线程后,分配给每一个线程的内存大小
-XX:NewRatio=n:设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4
-XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5
-XX:MaxPermSize=n:设置持久代大小
收集器设置
-XX:+UseSerialGC:设置串行收集器
-XX:+UseParallelGC:设置并行收集器
-XX:+UseParalledlOldGC:设置并行年老代收集器
-XX:+UseConcMarkSweepGC:设置并发收集器
垃圾回收统计信息
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-Xloggc:filename
并行收集器设置
-XX:ParallelGCThreads=n:设置并行收集器收集时使用的CPU数。并行收集线程数。
-XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间
-XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)
并发收集器设置
-XX:+CMSIncrementalMode:设置为增量模式。适用于单CPU情况。
-XX:ParallelGCThreads=n:设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。并行收集线程数。
四种访问权限
Object类中包含以下方法:
clone(); equals(); finalize();
getClass(); notify(); notifyAll();
hashCode(); toString(); wait();
i++与++i
i++是先执行其他操作然后再执行i+1;
++i是先执行i+1的操作,然后再执行其他的操作;
int a = i++; //a=i,a=0,i+1,i=1
int a = ++i; //i+1,i=1,a=i,a=1
抽象类与接口
abstract class表示的是"is-a"关系,interface表示的是"like-a"关系。
is-a,理解为是一个,代表继承关系。 如果A is-a B,那么B就是A的父类(基类)。
like-a,理解为像一个,代表组合关系。 如果A like a B,那么B就是A的接口。
has-a,理解为有一个,代表从属关系。 如果A has a B,那么B就是A的组成部分,例如,进程与线程的关系。
抽象类
抽象类不能被实例化
抽象类方法默认访问权限都是default
final不能修饰抽象类
可以有私有方法或私有变量的
abstract修饰类:抽象类
此类不能实例化;抽象类中一定有构造器,便于子类实例化时调用(涉及:子类对象实例化的全过程)
开发中,都会提供抽象类的子类,让子类对象实例化,完成相关操作。
抽象类中可以存在构造方法,可以存在普通属性,方法,静态属性。
abstract修饰方法:抽象方法
抽象方法中只有方法的声明,没有方法体;
包含抽象方法的类,一定是一个抽象类;反之,抽象类中可以没有抽象方法。
若子类没有重写父类中的所有的抽象方法,则此子类也是一个抽象类,需要使用abstract修饰。
抽象类的应用场景:
java允许类的设计者指定:超类声明一个方法但不提供实现,该方法的实现由子类提供,这样的方法称为抽象方法,有一个或更多抽象方法的类称为抽象类。
最终类就是被final修饰的类,最终方法就是被final修饰的方法。最终类不能被继承,最终方法不能被重写,最终类只能被实例化。
接口
接口不能被实例化
为了降低模块的耦合性,应优先选用接口,尽量少用抽象类;
接口中的变量默认是public static final,方法默认是public abstract,接口是公开的,里面不能有私有的方法或变量;
java不支持多继承但支持多接口(单继承,多实现),接口是一种特殊的抽象类;
接口中不能定义构造器,意味着接口不可以实例化,java中用类去实现接口;
在接口中只有方法的声明,没有方法体,接口中的方法永远都被public来修饰
接口中没有构造方法,也不能实例化接口的对象;
接口中定义的方法都需要有实现类来实现,如果实现类不能实现接口中的所有方法,则实现类定义为抽象类。
值类型与引用类型
参数为基本类型时是值传递, 参数为封装类型时是引用传递
值传递是将变量的一个副本传递到方法中,方法中如何操作该变量副本,都不会改变原变量的值。
引用传递是将变量的内存地址传递给方法,方法操作变量时会找到保存该地址的变量,对其进行操作,会对原变量造成影响。
值类型的变量赋值只是进行数据复制,创建一个同值的新对象,而引用类型变量赋值,仅仅是把对象的引用的指针赋值给变量,使它们共用一个内存地址。
值类型数据是在栈上分配内存空间,它的变量直接包含变量的实例,使用效率相对较高。而引用类型数据是分配在堆上,引用类型的变量通常包含一个指向实例的指针,变量通过指针来引用实例。
引用类型一般都具有继承性,但是值类型一般都是封装的(基本数据类型不可继承),因此值类型不能作为其他任何类型的基类。
引用类型的变量(作用域)也在栈区,只是其引用的对象在堆区。
在Java中,类,接口,数组都是引用数据类型,所有引用数据类型的默认值为null。
例如:数组是一个引用类型变量,因此使用它定义一个变量时,仅仅定义了一个变量,这个引用变量还未指向任何有效的内存,因此定义数组不能指定数组的长度,像定义String[50] a 就会报错。
==与equals()
==操作符专门用来比较变量的值是否相同
基本数据类型:比较的是他们的值是否相同
引用数据类型:比较的是他们的内存地址是否同一地址
引用类型对象变量其实是一个引用,它们的值是指向对象所在的内存地址,而不是对象本身。
String a= "My field1"; String b= "My field1"; String c=new String("My field1"); String d=new String("My field1"); a==b T a==c F c=d F a.equals(b) T a.equals(c) T
==比较的是地址,但是当为基本类型时,比较的是值,如果两边有包装类型,则先将包装类型转换为基本类型再比较值是否相等,当两边都为包装类型时,即为对象,比较的是地址。
equals()
equals方法是Object类中的方法,只能判断引用类型;
Object默认判断的是地址是否相等,子类中往往重写该方法,用于判断内容是否相等。比如Integer,String.换句话说,equals()没有重写时,Object默认以==来实现,即比较两个对象的内存地址是否相等;重写以后,按照对象的内容进行比较。
关于Socket 通信编程
客户端通过new Socket()方法创建通信的Socket对象;
服务器端通过new ServerSocket()创建TCP连接对象,服务器端通过TCP连接对象调用accept()方法创建通信的Socket对象,accept()接纳客户端请求。
TCP连接的建立步骤
TCP连接的建立步骤:
客户端向服务器端发送连接请求后,就被动地等待服务器的响应。典型的TCP客户端要经过下面三步操作:
1. 创建一个Socket实例:构造函数向指定的远程主机和端口建立一个TCP连接;
2. 通过套接字的I/O流与服务端通信;
3. 使用Socket类的close方法关闭连接。
服务端的工作是建立一个通信终端,并被动地等待客户端的连接。典型的TCP服务端执行如下操作:
1. 创建ServerSocket对象,绑定并监听端口
2. 通过accept监听客户端的请求
3. 建立连接后,通过输出输入流进行读写操作
4. 关闭相关资源
volatile与synchronized的区别
一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:
1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
2)禁止进行指令重排序。
volatile只提供了保证访问该变量时,每次都是从内存中读取最新值,并不会使用寄存器缓存该值——每次都会从内存中读取。
而对该变量的修改,volatile并不提供原子性的保证。
由于及时更新,很可能导致另一线程访问最新变量值,无法跳出循环的情况
多线程下计数器必须使用锁保护。
关于反射
反射就是在程序运行期间动态的获取对象的属性和方法的功能叫做反射。它能够在程序运行期间,对于任意一个类,都能知道它的所有的方法和属性,对于任意一个对象,都能知道它的属性和方法。
获取Class对象的三种方法:
getClass();
xx.Class;
Class.forName("xxx");
反射的优缺点:
优点:运行期间能够动态的获取类,提高代码的灵活性。
缺点:性能比直接的Java代码要慢很多。 应用场景:spring的xml配置模式,以及动态代理模式都用到了反射。
java中 “||” 和“|”
线程安全与不安全
线程安全:
HashTable,SynchronizedMap,ConcurrentHashMap,Vector,Stack,StringBuffer
线程不安全:
HashMap,HashSet,SortMap,TreeMap,TreeSet,ArrayList,LinkedList,Stringbuilder
基本类型装箱/拆箱对象引用(内存地址)是否相同
1 A.a1 == a2 T 2 B.d1 == d2 T 3 C.b1 == b2 F 4 D.c1 == c2 F
Integer a1=17实际上执行了Integer.valueOf(17);
1 public static Integer valueOf(int i) { 2 assert IntegerCache.high >= 127; 3 if (i >= IntegerCache.low && i <= IntegerCache.high) 4 return IntegerCache.***[i + (-IntegerCache.low)]; 5 return new Integer(i); 6 }
- 选项A,a1、a2赋值给Integer类型,自动装箱。对于–128到127(默认是127)之间的值,Integer.valueOf(int i) 返回的是缓存的Integer对象(并不是新建对象),变量所指向的是同一个对象,所以a1==a2返回true。
- 选项B,Integer和int比较会进行自动拆箱,比较的是数值大小,所以d1==d2返回true。
- 选项C,由于超出自动装箱的范围,return返回的是新建的对象,所以对象内存地址不同,b1==b2返回false。
- 选项D,普通new创建对象,两个new创建两个地址不同的对像,所以c1==c2返回false。
s==u F s==t F s.equals(t) T s.equals(9) T s.equals(new Integer(9)) T
(s==u) ,因为, s 是 Integer 类型, u 是 Long 类型,两个不同类型的引用不能进行 == 比较。
(s==t) , s 是指向一个 9 的引用,而 t 也是一个指向 9 的引用,虽然都是指向 9 ,但却是指向不同的 9 ,即是两个不同的引用。因此 == 比较返回的是假。对于这个选项,new出来的 重新开辟空间
(s.equals(t)) , Integer 的 equals 方法如下:
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false ;
}
是 Integer 的实例且 value 值也相等的情况下返回真,其他返回假。
在这里, s 和 t 都是 Integer 类型且值都为 9 ,因此结果为真。
(s.equals(9)) , 在进行 equals 比较之前,会对 9 调用 Integer.valueOf 方法,进行自动装箱 , 由于 IntegerCache 中已经存在 9 ,所以,直接返回其引用,引用相同, equals 就自然相同了。所以结果为真。
(s.equals( new Integer(9)) ,直接创建了一个新的 Integer 实例,但且值也为 9 ,所以,满足条件,返回真。
取模运算
当x和y的正负相同时,取余和取模结果相同;
当x和y的正负不同时,取余结果的符号和x相同,取模结果的符号和y相同。(口诀:取余取头,取模取尾)
例如:-12%-5 = -2.
集合
单例集合
Collection :单例集合的根接口
-------List :如果实现了List接口的集合类,具备的特点:有序,可重复
---------------| ArraryList 底层 是使用了Object数组实现的,特点: 查询速度快,增删慢。
---------------| LinkedList 底层是使用了链表数据结构实现 的, 特点: 查询速度慢,增删快。
---------------| Vector Vector的实现与ArrayList是一致,但是是线程安全 的,操作效率低。 jdk1.0的时候出现的
------Set 如果是实现了Set接口的集合类,具备的特点:无序,不可重复,允许使用null元素。
----------------| HashSet 底层是使用了一个哈希表支持的, 特点:存取速度快。
Collection接口常用的抽象方法
add(Object obj) | 把给定的对象添加到当前集合中 。 |
public void clear() | 清空集合中所有的元素。 |
remove(Object obj) | 把给定的对象在当前集合中删除。 |
public boolean contains(E e) | 判断当前集合中是否包含给定的对象 |
public boolean isEmpty() | 判断当前集合是否为空。 |
public int size() | 返回集合中元素的个数 |
public Object[] toArray() | 把集合中的元素,存储到数组中 |
List, Set是Collection的子接口,意味着所有List,Set的实现类都有上述方法
ArrayList容器类
ArrayList是List接口的实现类。底层是用数组实现的存储。特点:查询效率高,删增效率低,线程不安全
对一维数组按照从小到大的顺序排序
import java.util.*; public class Main{ public static void main(String[] args){ Scanner input = new Scanner(System.in); int n = input.nextInt(); List<Integer> list = new ArrayList<Integer>(); for(int i = 0;i<n;i++){ list.add(input.nextInt()); } //降序 // list.sort(Comparator.reverseOrder()); //升序 list.sort(Comparator.naturalOrder()); for(Integer i : list){ System.out.printf("%d ",i); } } }
添加元素
public class ArrayListTest { public static void main(String[] args) { //ArrayList实例化时,对象引用应用接口类型定义(Collection,List),面向对象编程其实就是面向接口编程 //实例化ArrayList容器,用List接口的原因是List继承了Collection接口。 List<String> list = new ArrayList<>(); //添加元素 //Collection接口中的add方法 boolean flag = list.add("第一句"); boolean flag2 = list.add("第二句"); System.out.println(flag); //List接口中的add方法,索引的位置是不能大于等于元素的个数的,否则报错IndexOutOfBoundsException<数组下标越界错误> list.add(2,"第三句"); for(int i = 0;i<list.size();i++){ System.out.println(list.get(i)); } //将ArrayList转换为Object[] //但是不能将转换的数组做强制类型转化 Object[] arr = list.toArray(); for(int i = 0;i<list.size();i++){ //在java中只能单个对象的强制转换,不能批量的进行强制转换 String str = (String) arr[i]; System.out.println(str); } //将单例集合转换为指定类型的集合 //但是类型需要参考泛型中的类型 String[] arr2 = list.toArray(new String[list.size()]); for(int i = 0;i<arr2.length;i++){ System..out.printn(arr[i]); } } }
Vector容器类
Vector底层是用数组实现的,相关的方法都加了同步检查,因此“线程安全,效率低”。比如,indexOf方法就增加了synchronized同步标记
Vector的使用与ArrayList是相同的,因为他们都实现了List接口,对List接口中的抽象方法做了具体的实现
Stack容器
Stack栈容器,是Vector的一个子类,它实现了一个标准的后进先出(LIFO:Last In Frist Out)的栈
Stack特点是:后进先出。它通过5个操作方法对Vector进行扩展,允许将向量视为堆栈
public class StackTest { public static void main(String[] args) { //实例化栈容器 Stack<String> stack = new Stack<>(); //将元素添加到栈容器当中 stack.push("a"); stack.push("b"); stack.push("c"); //判断栈容器是否为空 System.out.println(stack.empty());//false //查看栈顶元素 System.out.println(stack.peek()); //返回元素在栈容器中的位置 System.out.println(stack.search("c"));//1 //获取栈容器中的容器 String p1 = stack.pop(); System.out.println(p1); String p2 = stack.pop(); System.out.println(p2); String p3 = stack.pop(); System.out.println(p3); System.out.println("--------------------"); StackTest stackTest = new StackTest(); stackTest.symmetry(); } public void symmetry(){ String str = "...(..[...{....}...]..)......(..[...{....}...]..)..."; //实例化Stack Stack<String> stack = new Stack<>(); //假设修正法 boolean flag = true;//假设是匹配的 //拆分字符串获取字符 for(int i = 0;i<str.length();i++){ char c = str.charAt(i); if(c == '{'){ stack.push("}"); } if(c == '['){ stack.push("]"); } if (c == '('){ stack.push(")"); } //判断符号是否匹配 if(c == '}' || c ==']' || c==')'){ if (stack.empty()){ //修正处理 flag = false; break; } String x = stack.pop(); if(x.charAt(0) != c){ //修正处理 flag = false; break; } } } if(!stack.empty()){ //修正处理 flag = false; } System.out.println(flag); } }
LinkedList容器类
LinkedList底层用双向链表实现的存储。特点:查询效率低,增删效率高,线程不安全。
双向链表也叫双链表,是链表的一种,它的每个数据节点中都有两个指针,分别指向前一个结点和后一个结点。所以,从双向链表中的任意一个结点开始,都可以很方便地找到所有结点。LinkedList实现了List接口,所以LinkedList是具备List的存储特征的(有序,元素有重复)
public class LinkedListTest { public static void main(String[] args) { List<String> list = new LinkedList<>(); //添加元素 list.add("a"); list.add("b"); list.add("c"); list.add("a"); //获取元素 for (int i =0;i< list.size();i++){ System.out.println(list.get(i)); } System.out.println("-----------------"); for(String str:list){ System.out.println(str); } } }
Set接口
set接口继承自Collection,Set接口中没有新增方法,方法和Collection保持完全一致。通过List学习的方法,在Set中依然适用。Set特点:无序,不可重复。无序指的是Set中的元素没有索引,我们只能遍历查找;不可重复指的是不允许加入重复的元素。新元素如果和Set中某个元素通过equals()方法对比为true,则只能保留一个。
Set常用的实现类有:HashSet,TreeSet等,我们一般使用HashSet
HashSet容器类
HashSet是一个没有重复元素的集合,不保证元素的顺序。而且HashSet允许有null元素。HashSet是采用哈希算法实现,底层实际是用HashMap实现的(HashSet本质是一个简化版的HashMap),因此,查询效率和增删效率都比较高。
Hash算法原理
Hash算法也称之为散列算法
public class HashSetTest { public static void main(String[] args) { //实例化HashSet Set<String> set = new HashSet<>(); //添加元素 set.add("a"); set.add("b1"); set.add("c2"); set.add("d"); set.add("a"); //获取元素,在set容器中没有索引,所以没有对应的get(int index)方法 for (String str:set){ System.out.println(str); } System.out.println("--------------------"); //删除元素 boolean flag = set.remove("c2"); System.out.println(flag); for (String str:set){ System.out.println(str); } System.out.println("--------------------"); int size = set.size(); System.out.println(size); } }
数组的默认长度为16
package Test_javase; import java.util.Objects; /** * @author 疯了疯了要疯了 * @create 2021-10-12-17:24 **/ public class Users { private String username; private int userage; public Users() { } @Override public boolean equals(Object o) { System.out.println("equals......"); if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Users users = (Users) o; return userage == users.userage && Objects.equals(username, users.username); } @Override public int hashCode() { return Objects.hash(username, userage); } public Users(String username, int userage) { this.username = username; this.userage = userage; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public int getUserage() { return userage; } public void setUserage(int userage) { this.userage = userage; } @Override public String toString() { return "Users{" + "username='" + username + '\'' + ", userage=" + userage + '}'; } }
package Test_javase; import java.util.HashSet; import java.util.Set; /** * @author 疯了疯了要疯了 * @create 2021-10-12-17:05 **/ public class HashSetTest { public static void main(String[] args) { //实例化HashSet Set<String> set = new HashSet<>(); //添加元素 set.add("a"); set.add("b1"); set.add("c2"); set.add("d"); set.add("a"); //获取元素,在set容器中没有索引,所以没有对应的get(int index)方法 for (String str:set){ System.out.println(str); } System.out.println("--------------------"); //删除元素 boolean flag = set.remove("c2"); System.out.println(flag); for (String str:set){ System.out.println(str); } System.out.println("--------------------"); int size = set.size(); System.out.println(size); System.out.println("----------------------"); //实例化HashSet Set<Users> set1 = new HashSet<>(); Users u = new Users("oldlu",18); Users u1 = new Users("oldlu",18); set1.add(u); set1.add(u1); System.out.println(u.hashCode()); System.out.println(u1.hashCode()); for (Users users:set1){ System.out.println(users); } } }
返回结果:
a d b1 c2 -------------------- true a d b1 -------------------- 3 ---------------------- equals...... -1014303773 -1014303773 Users{username='oldlu', userage=18} Process finished with exit code 0
如果用HashSet存储自定义的对象时,想要让它具备不重复的特点一定要重写hashCode()和euqal()
HashSet不重复的特点是要依靠hashCode()和euqal()方法的。
TreeSet容器类
TreeSet是一个可以对元素进行排序的容器。底层实际是用TreeMap实现的,内部维持了一个简化版的TreeMap,通过key来存储Set的元素。TreeSet内部需要对存储的元素进行排序,因此,我们需要给定排序规则:
1.通过元素自身实现比较规则
2.通过比较器指定比较规则
TreeSet通过元素自身实现比较规则
package Test_javase; import java.util.Objects; /** * @author 疯了疯了要疯了 * @create 2021-10-12-17:24 **/ public class Users implements Comparable<Users>{ private String username; private int userage; public Users() { } @Override public boolean equals(Object o) { System.out.println("equals......"); if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Users users = (Users) o; return userage == users.userage && Objects.equals(username, users.username); } @Override public int hashCode() { return Objects.hash(username, userage); } public Users(String username, int userage) { this.username = username; this.userage = userage; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public int getUserage() { return userage; } public void setUserage(int userage) { this.userage = userage; } @Override public String toString() { return "Users{" + "username='" + username + '\'' + ", userage=" + userage + '}'; } //定义比较规则 //正数:大,负数:小,0:相等 @Override //年龄如果相同,就按照姓名排序(按照字典顺序,a在s前,所以admin排在sxt前面) public int compareTo(Users o) { if(this.userage > o.getUserage()){ return 1; } if(this.userage == o.getUserage()){ //字符串的比较 return this.username.compareTo(o.getUsername()); } return -1; } }
package Test_javase; import java.util.Set; import java.util.TreeSet; /** * @author 疯了疯了要疯了 * @create 2021-10-12-17:51 **/ public class TreeSetTest { public static void main(String[] args) { //实例化TreeSet Set<String> set = new TreeSet<>(); //添加元素 set.add("c"); set.add("a"); set.add("d"); set.add("b"); set.add("a"); //获取元素 for(String str:set){ System.out.println(str); } System.out.println("-----------------------"); Set<Users> set1 = new TreeSet<>(); Users u = new Users("oldlu",18); Users u1 = new Users("admin",22); Users u2 = new Users("sxt",22); set1.add(u); set1.add(u1); set1.add(u2); for (Users users:set1){ System.out.println(users); } } }
TreeSet通过比较器实现比较规则
通过比较器定义比较规则时,我们需要单独创建一个比较器,比较器需要实现Comparator接口中的compare方法来定义比较规则。在实例化TreeSet来完成元素的排序处理。此时元素自身就不需要实现比较规则了。
创建比较器
import java.util.Comparator; public class StudentComparator implements Comparator<Student> { //定义比较规则 @Override public int compare(Student o1, Student o2) { if(o1.getAge() > o2.getAge()){ return 1; } if (o1.getAge() == o2.getAge()){ return o1.getName().compareTo(o2.getName()); } return -1; } }
创建Student类
import java.util.Objects; public class Student { private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Student student = (Student) o; return age == student.age && Objects.equals(name, student.name); } @Override public int hashCode() { return Objects.hash(name, age); } @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", age=" + age + '}'; } public Student() { } public Student(String name, int age) { this.name = name; this.age = age; } }
创建TreeSetTest测试
import java.util.Set; import java.util.TreeSet; public class TreeSetTest { public static void main(String[] args) { Set<Student> set2 = new TreeSet<>(new StudentComparator()); Student s = new Student("oldlu",18); Student s1 = new Student("admin",22); Student s2 = new Student("sxt",22); set2.add(s); set2.add(s1); set2.add(s2); for(Student student:set2){ System.out.println(student); } } }
双例集合
Map接口介绍
将键映射到值的对象 ,一个映射不能包含重复的键, 每个键最多只能映射到一个值
存储结构的理解
Map中的key:无序的、不可重复的,使用Set存储所的key ---> key所在的类要重写equals()和hashCode() (以HashMap为例)
Map中的value:无序的、可重复的,使用Collection存储所的value --->value所在的类要重写equals()
一个键值对:key-value构成了一个Entry对象。
Map中的entry:无序的、不可重复的,使用Set存储所的entry
Map(接口)中常用的方法
V put(K key,V value) | 添加键值对,返回值 |
V remove(Object key) | 删除对应键上的值 |
void clear() | 清空集合中的键值对 |
boolean containsKey(Object key) | 判断是否包含指定的键 |
boolean containsValue(Object value) | 判断是否包含指定的值 |
boolean isEmpty() | 判断集合是否为空 |
int size() | 集合的长度,返回int值 |
V get(Object key) | 返回对应键上的值(查询) |
Collection V values() | 单独把值这一列取出 |
Set keySet() | 单独把键这一列取出 |
Set<Map.Entry<K,V>>entrySet() | 把每个元素的<键,值>封装到一个Entry集合中 |
绿色:接口
浅蓝色:抽象类
蓝色:实现类
HashMap容器类
HashMap是Map接口的接口实现类,它采用哈希算法实现,是Map接口最常见的实现类。由于底层采用了哈希表存储数据,所以要求键不能重复,如果发生重复,新的值会替换旧的值。HashMap在查找,删除,修改方面都有非常高的效率。HashMap底层是哈希表结构的,依赖hashCode方法和equals方法保证键的唯一,如果键要存储的是自定义对象,需要重写hashCode和equals方法。
键不允许有重复的,但是value可以重复
public class HashMapTest { public static void main(String[] args) { //实例化HashMap容器 Map<String,String> map = new HashMap<>(); //添加元素 map.put("a", "A"); String value = map.put("a","B"); System.out.println(value);//返回A } }
补充说明:put():当put的key在HashMap中不存在,这时put方法返回的返回值就是一个null值,当put的key与HashMap中的key相同,HashMap中的value会被覆盖掉,这时put方法返回的返回值就是HashMap中的key对应的value值
如何解决hash冲突
参考:https://www.cnblogs.com/lyfstorm/p/11044468.html
获取方法
方法1:get():添加多个元素不方便,查询方便
public class HashMapTest { public static void main(String[] args) { //实例化HashMap容器 Map<String,String> map = new HashMap<>(); //添加元素 map.put("a", "A"); String value = map.put("a","B"); System.out.println(value); map.get("a"); System.out.println("-------------------"); String val = map.get("a"); System.out.println(val);//返回B } }
方法2:keySet()
public class HashMapTest { public static void main(String[] args) { //实例化HashMap容器 Map<String,String> map = new HashMap<>(); //添加元素 map.put("a", "A"); String value = map.put("a","B"); System.out.println(value); map.get("a"); System.out.println("-------------------"); String val = map.get("a"); System.out.println(val); System.out.println("-------------------"); map.put("b","B"); map.put("c","C"); map.put("d","D"); map.put("e","E"); //获取HashMap容器中所有的元素,可以使用keyset方法与get方法一并完成 Set<String> keys = map.keySet(); for(String key:keys){ String v1 = map.get(key); System.out.println(key+"----"+v1); } } } 返回值为: A ------------------- B ------------------- a----B b----B c----C d----D e----E
方式3:通过entrySet()方法获取Map.Entry类型获取元素
public class HashMapTest { public static void main(String[] args) { //实例化HashMap容器 Map<String,String> map = new HashMap<>(); //添加元素 map.put("a", "A"); String value = map.put("a","B"); System.out.println(value); map.get("a"); System.out.println("-------------------"); String val = map.get("a"); System.out.println(val); System.out.println("-------------------"); map.put("b","B"); map.put("c","C"); map.put("d","D"); map.put("e","E"); Set<Map.Entry<String,String>> entrySet = map.entrySet(); for(Map.Entry<String,String> entry:entrySet){ String key = entry.getKey(); String v = entry.getValue(); System.out.println(key+"-------"+v); } } }
并集操作
如果key相同,被并过来的那个容器当中的元素,会把当前容器中的value覆盖掉(原来的听新来的)
public class HashMapTest { public static void main(String[] args) { //实例化HashMap容器 Map<String,String> map = new HashMap<>(); //添加元素 map.put("a", "A"); String value = map.put("a","B"); System.out.println(value); map.get("a"); System.out.println("-------------------"); String val = map.get("a"); System.out.println(val); System.out.println("-------------------"); map.put("b","B"); map.put("c","C"); map.put("d","D"); map.put("e","E"); Map<String,String> map2 = new HashMap<>(); map2.put("f","F"); map2.put("c","cc"); map2.putAll(map); Set<String> keys2 = map2.keySet(); for(String key:keys2){ System.out.println("key: "+key+"value: "+map2.get(key)); } } } A ------------------- B ------------------- key: avalue: B key: bvalue: B key: cvalue: C key: dvalue: D key: evalue: E key: fvalue: F
删除元素
public class HashMapTest { public static void main(String[] args) { //实例化HashMap容器 Map<String,String> map = new HashMap<>(); //添加元素 map.put("a", "A"); String value = map.put("a","B"); System.out.println(value); map.get("a"); System.out.println("-------------------"); String val = map.get("a"); System.out.println(val); System.out.println("-------------------"); map.put("b","B"); map.put("c","C"); map.put("d","D"); map.put("e","E"); String v = map.remove("e"); Set<String> keys3 = map.keySet(); System.out.println(v); for(String key:keys3){ System.out.println("key: "+key+"value: "+map.get(key)); } } } A ------------------- B ------------------- key: avalue: B key: bvalue: B key: cvalue: C key: dvalue: D
判断key和value是否存在
public class HashMapTest { public static void main(String[] args) { //实例化HashMap容器 Map<String,String> map = new HashMap<>(); //添加元素 map.put("a", "A"); String value = map.put("a","B"); System.out.println(value); map.get("a"); System.out.println("-------------------"); String val = map.get("a"); System.out.println(val); System.out.println("-------------------"); map.put("b","B"); map.put("c","C"); map.put("d","D"); map.put("e","E"); boolean flag = map.containsKey("aaaa"); System.out.println(flag); System.out.println("-------------------"); boolean flag2 = map.containsValue("B"); System.out.println(flag2); } } A ------------------- B ------------------- false ------------------- true
TreeMap容器类
TreeMap和HashMap同样实现了Map接口,所以,对于API的用法来说是没有区别的。HashMap效率高于TreeMap;TreeMap是可以对键进行排序的一种容器,在需要对键排序时可选用TreeMap.TreeMap底层是基于红黑树实现的。
在使用TreeMap时需要给定排序规则:
1.元素自身实现比较规则
public class Users implements Comparable<Users>{ private String username; private int userage; public Users() { } @Override public boolean equals(Object o) { System.out.println("equals......"); if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Users users = (Users) o; return userage == users.userage && Objects.equals(username, users.username); } @Override public int hashCode() { return Objects.hash(username, userage); } public Users(String username, int userage) { this.username = username; this.userage = userage; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public int getUserage() { return userage; } public void setUserage(int userage) { this.userage = userage; } @Override public String toString() { return "Users{" + "username='" + username + '\'' + ", userage=" + userage + '}'; } //定义比较规则 //正数:大,负数:小,0:相等 @Override //年龄如果相同,就按照姓名排序(按照字典顺序,a在s前,所以admin排在sxt前面) public int compareTo(Users o) { if(this.userage > o.getUserage()){ return 1; } if(this.userage == o.getUserage()){ //字符串的比较 return this.username.compareTo(o.getUsername()); } return -1; } }
public class TreeMapTest { public static void main(String[] args) { //实例化TreeMap Map<Users,String> map = new TreeMap<>(); Users u1 = new Users("oldlu",18); Users u2 = new Users("admin",22); Users u3 = new Users("sxt",22); map.put(u1,"oldlu"); map.put(u2,"admin"); map.put(u3,"sxt"); Set<Users> keys = map.keySet(); for (Users key:keys){ System.out.println(key+"------"+map.get(key)); } } } Users{username='oldlu', userage=18}------oldlu Users{username='admin', userage=22}------admin Users{username='sxt', userage=22}------sxt
2.通过比较器实现比较规则
public class Student { private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Student student = (Student) o; return age == student.age && Objects.equals(name, student.name); } @Override public int hashCode() { return Objects.hash(name, age); } @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", age=" + age + '}'; } public Student() { } public Student(String name, int age) { this.name = name; this.age = age; } }
public class StudentComparator implements Comparator<Student> { //定义比较规则 @Override public int compare(Student o1, Student o2) { if(o1.getAge() > o2.getAge()){ return 1; } if (o1.getAge() == o2.getAge()){ return o1.getName().compareTo(o2.getName()); } return -1; } }
public class TreeMapTest { public static void main(String[] args) { //实例化TreeMap Map<Student,String> treeMap = new TreeMap<>(new StudentComparator()); Student s1 = new Student("oldlu",18); Student s2 = new Student("admin",22); Student s3 = new Student("sxt",22); treeMap.put(s1,"oldlu"); treeMap.put(s2,"admin"); treeMap.put(s3,"sxt"); Set<Student> keys1 = treeMap.keySet(); for (Student key : keys1){ System.out.println(key+"---------"+treeMap.get(key)); } } } Student{name='oldlu', age=18}---------oldlu Student{name='admin', age=22}---------admin Student{name='sxt', age=22}---------sxt
反射
关于java.lang.Class的理解
1.类的加载过程:
程序经过javac.exe命令以后,会生成一个或多个字节码文件(.class结尾)。接着我们使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件加载到内存中。此过程就称为类的加载。加载到内存中的类,我们就称为运行时类,此运行时类,就作为Class的一个实例。
2.换句话说,Class的实例就对应着一个运行时类
3.加载到内存中的运行时类,会缓存一定的时间。在此时间中,我们可以通过不同的方式来获取此运行时类。
获取Class实例的4种方法(前三种方式要掌握,第四种了解)
public void test01() throws ClassNotFoundException { //方法一:调用运行时类的属性:.class Class<Person> clazz1 = Person.class; System.out.println(clazz1); //方法二:通过运行时类的对象,调用getClass(); Person p1 = new Person(); Class clazz2 = p1.getClass(); System.out.println(clazz2); //方法三:调用Class的静态方法:forName(String classPath);(开发中用的最多) Class clazz3 = Class.forName("text.Person"); System.out.println(clazz3); //方法四:使用类的加载器:ClassLoader; ClassLoader classLoader = ReflectionTest.class.getClassLoader(); Class clazz4 = classLoader.loadClass("text.Person"); System.out.println(clazz4); }
转发(forward)和重定向(redirect)的区别
forward是内部重定向,redirect是外部重定向1、请求次数
重定向是浏览器向服务器发送一个请求并收到响应后再次向一个新地址发出请求,转发是服务器收到请求后为了完成响应跳转到一个新的地址;重定向至少请求两次,转发请求一次;forword效率高,而redirect效率低。
2、地址栏不同
重定向(redirect)地址栏会发生变化,转发(forward)地址栏不会发生变化;重定向是需要response将信息返回给浏览器,而请求转发是request对象的行为
3、是否共享数据
重定向两次请求不共享数据,转发一次请求共享数据(在request级别使用信息共享,使用重定向必然出错);
4、跳转限制
重定向可以跳转到任意URL,转发只能跳转本站点资源;redirect在浏览器中显示的是被请求的URL,forward在浏览器中不显示被请求的URL;
forword 一般用于用户登录的时候,根据角色转发到相应的模块;
redirect一般用于用户注销登录时返回主页面或者跳转到其他网站。
5、发生行为不同
重定向是客户端行为,转发是服务器端行为;换句话说就是,redirect是通过客户端发起的请求,forward是通过服务器端发起的请求
6.二者底层实现的思路不同
Forward(直接转发方式)用的更多一些,一般说的请求转发指的就是直接转发方式。Web应用程序大多会有一个控制器。由控制器来控制请求应该转发给那个信息资源。然后由这些信息资源处理请求,处理完以后还可能转发给另外的信息资源来返回给用户,这个过程就是经典的MVC模式。
Redirect(间接转发方式),有时也叫重定向,它一般用于避免用户的非正常访问。例如:用户在没有登录的情况下访问后台资源,Servlet可以将该HTTP请求重定向到登录页面,让用户登录以后再访问。
7.参数传递不同
redirect:重新开始一个request,原页面的request生命周期结束。forward另一个连接的时候。request变量是在其生命周期内的。另一个页面也可以使用,其实质是把目标地址include。
8.定义不同
直接转发方式(forward):客户端和浏览器只发出一次请求,Servlet、HTML、JSP或其它信息资源,由第二个信息资源响应该请求,在请求对象request中,保存的对象对于每个信息资源是共享的。
间接转发方式(redirect)实际是两次HTTP请求,服务器端在响应第一次请求的时候,让浏览器再向另外一个URL发出请求,从而达到转发的目的。
mybatis中#与$的区别
1 #是将传入的值当做字符串的形式,eg:select id,name,age from student where id =#{id},当前端把id值1,传入到后台的时候,就相当于 select id,name,age from student where id ='1'.
2 $是将传入的数据直接显示生成sql语句,eg:select id,name,age from student where id =${id},当前端把id值1,传入到后台的时候,就相当于 select id,name,age from student where id = 1.
3 使用#可以很大程度上防止sql注入。(语句的拼接)
4 但是如果使用在order by 中就需要使用 $.
5 在大多数情况下还是经常使用#,但在不同情况下必须使用$.
我觉得#与的区别最大在于:#{} 传入值时,sql解析时,参数是带引号的,而${}传入值,sql解析时,参数是不带引号的。
一 : 理解mybatis中 $与#
在mybatis中的$与#都是在sql中动态的传入参数。
eg:select id,name,age from student where name=#{name} 这个name是动态的,可变的。当你传入什么样的值,就会根据你传入的值执行sql语句。
二:使用$与#
#{}: 解析为一个 JDBC 预编译语句(prepared statement)的参数标记符,一个 #{ } 被解析为一个参数占位符 。
${}: 仅仅为一个纯碎的 string 替换,在动态 SQL 解析阶段将会进行变量替换。
name-->dcy
eg: select id,name,age from student where name=#{name} -- name='dcy'
select id,name,age from student where name=${name} -- name=dcy
原文:https://blog.csdn.net/qq_35773649/article/details/89884757
位运算符
&运算符
将两个数转为对应的二进制后,然后对每一位的数进行比较,两个都为1则为1,否则则为0。
示例:2&3
2的二进制:0010
3的二进制:0011
结果是0010
将0010转为10进制之后是2,所以2&3=2;
|运算符
将两个数转为对应的二进制后,然后对每一位的数进行比较,有一个为1则为1,否则则为0。
示例:5|7
5的二进制:0101
7的二进制:0111
结果是0111
将0111转为10进制之后是7,所以5|7=7;
~运算符
将数转为对应的二进制后,如果位为1, 则为0,为0就为1。
示例:~6
6的二进制:00000000 00000000 00000000 00000110
取反后:11111111 11111111 11111111 11111001
因为高位是1,所以原码为负数,负数的补码是其绝对值的原码取反,末尾再加1。
因此,我们可将这个二进制数的补码进行还原:
(1)首先,末尾减1得反码: 11111111 11111111 11111111 11111010
(2)其次,将各位取反得原码:00000000 00000000 00000000 00100101
(3)此时二进制转原码为-7
(4)所以~6 = -7
快速运算:+1取反
^运算符
将数转为对应的二进制后,如果位相同则为0,不同为1
示例:2^3
2的二进制:0010
3的二进制:0011
结果0001
将0001转为10进制之后是1,所以2^3=1;
原文链接:https://blog.csdn.net/ppjsyw/article/details/124971343
sql基础:delete和truncate的区别
delete:
(1) 一行一行的把数据删除,并且同时将该行的删除操作作为事务记录在日志中保存以便进行进行回滚操作。
(2) delete是数据操作语言(DML)命令。
(3)delete命令不会影响表结构
truncate:
(1)不能加where条件。
(2) 先删除表 (drop) ,重新创建(create)表 。因此,若表中有自增长,会把自增长id 重置成1开始。
(3) 速度更快 且 不可回滚。
(4) truncate是数据定义语言(DDL)命令。
(5)truncate命令会从数据库中删除表结构。
@Configuration注解的作用
官方文档描述:
用@Configuration注释类表明其主要目的是作为bean定义的源
@Configuration类允许通过调用同一类中的其他@Bean方法来定义bean之间的依赖关系
比如在学习springCloud分布式微服务时,用RestTemplate实现不同服务之间的相互调用,而RestTemplate没有被自动注入到容器中,所以需要自己注入,这时可以写一个配置类
之后进行自动装配
这就使用了@Configuration注解。同时@Configuration注解和@Component一样可以创建对象,@Configuration注解还可以让@Bean对象依赖于当前配置类的其它Bean。
其他
本文来自博客园,作者:锦此,转载请注明原文链接:https://www.cnblogs.com/jinci2022/p/16482073.html