什么是面向接口编程?
- 面向接口调用,面向接口编写实现类。
- 如果我们对接口进行深一步的刨析的话,可以发现接口是定义(规范,约束)与实现(名实分离的原则)的分离。接口的本身反映了系统设计人员对系统的抽象理解。
- 面向接口编程就是先把客户的业务逻辑线提取出来,作为接口,业务具体实现通过该接口的实现类来完成。这样的话如果需求改变只需要书写新的实现类以及更改配置即可达到要求。
java开发的七大设计原则
设计原则总得来说还是为了让代码尽量的:高内聚、低耦合。提高代码的扩展性,复用性。实际开发中需要平衡开发效率与代码设计的量,设计模式使用过多类和方法细分会越多,导致过于臃肿
1、开闭原则(Open Close Principle)
定义:开闭原则的意思是:对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。
分析:抽象化是开闭原则的关键。想要达到这样的效果,我们需要使用接口和抽象类。
2、里氏代换原则(Liskov Substitution Principle)
定义:所有引用基类(父类)的地方必须能透明地使用其子类的对象。
分析:我的理解就是引用了父类的地方替换成子类不影响代码运行
使用里氏代换原则需要注意:
1)子类的多有方法必须在父类中声明,或者子类必须实现父类中声名的所有方法。
2)尽量把父类设计成抽象类或接口,让子类继承父类或实现父接口。增加一个新功能时,通过增加一个新的子类来实现。
3)java语言编译时会检查一个程序是否符合里氏代换原则,但只是一个语法意义上的检查,有局限性。
3、依赖倒转原则(Dependence Inversion Principle)
定义:这个原则是开闭原则的基础,具体内容:针对接口编程,依赖于抽象而不依赖于具体。
分析:简单来说,依赖倒转原则就是指:代码要依赖于抽象的类,而不要依赖于具体的类;要针对接口或抽象类编程,而不是针对具体类编程。
实现开闭原则的关键是抽象化,并且从抽象化导出具体化实现,如果说开闭原则是面向对象设计的目标的话,那么依赖倒转原则就是面向对象设计的主要手段。
Spring的DI 就是依赖倒转原则的一个具体实现
策略模式也遵循依赖倒转原则
4、接口隔离原则(Interface Segregation Principle)
定义:客户端不应该依赖那些它不需要的接口。
一旦一个接口太大,则需要将它分割成一些更细小的接口,使用该接口的客户端仅需知道与之相关的方法即可。
分析:接口隔离原则针对的是接口类,与单一职责原则的区别就是单一原则针对的是实现类,两者区分的维度不一样
5、迪米特法则,又称最少知道原则(Demeter Principle)
定义:
1)不要和“陌生人”说话。
2)只与你的直接朋友通信。
3)每一个软件单位对其他的单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位。
分析:也是为了降低类之间的耦合,增加局部的内聚,增强扩展性。当一个模块功能独立时,对其进行修改扩展会更加的容易
在迪米特法则中,对于一个对象,其朋友包括以下几类:
1)当前对象本身(this);
2)以参数形式传入到当前对象方法中的对象;
3)当前对象的成员对象;
4)如果当前对象的成员对象是一个集合,那么集合中的元素也都是朋友;
5)当前对象所创建的对象。
任何一个对象,如果满足上面的条件之一,就是当前对象的“朋友”,否则就是“陌生人”。
迪米特法则可分为狭义法则和广义法则。在狭义的迪米特法则中,如果两个类之间不必彼此直接通信,那么这两个类就不应当发生直接的相互作用,如果其中的一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用。
狭义的迪米特法则:可以降低类之间的耦合,但是会在系统中增加大量的小方法并散落在系统的各个角落,它可以使一个系统的局部设计简化,因为每一个局部都不会和远距离的对象有直接的关联,但是也会造成系统的不同模块之间的通信效率降低,使得系统的不同模块之间不容易协调。
广义的迪米特法则:指对对象之间的信息流量、流向以及信息的影响的控制,主要是对信息隐藏的控制。信息的隐藏可以使各个子系统之间脱耦,从而允许它们独立地被开发、优化、使用和修改,同时可以促进软件的复用,由于每一个模块都不依赖于其他模块而存在,因此每一个模块都可以独立地在其他的地方使用。一个系统的规模越大,信息的隐藏就越重要,而信息隐藏的重要性也就越明显。
用途在于控制信息的过载:
1)在类的划分上,应当尽量创建松耦合的类,类之间的耦合度越低,就越有利于复用,一个处在松耦合中的类一旦被修改,不会对关联的类造成太大波及;
2)在类的结构设计上,每一个类都应当尽量降低其成员变量和成员函数的访问权限;
3)在类的设计上,只要有可能,一个类型应当设计成不变类;
4)在对其他类的引用上,一个对象对其他对象的引用应当降到最低。
6、合成复用原则(Composite Reuse Principle)
定义:合成复用原则是指:尽量使用合成/聚合的方式,而不是使用继承。
分析:继承属于高耦合,没有组合/聚合方式灵活。使用继承时需要充分遵循里氏替换原则.
总之就是能用组合/聚合就不用继承
7、单一职责原则(Single Responsibility Principle, SRP)
定义:类的职责要单一,不能将太多的职责放在一个类中
分析:一个类职责越多,被复用的可能性越小,粒度越小越可能被复用。单一原则是针对实现类的设计模式
为什么要使用面对接口编程?
- 降低程序的耦合性。
- 易于程序的扩展。
- 可维护性强(当下层需要改变时,只要接口及接口功能不变,则上层不用做任何的修改)。
什么是多态
一个对象具有多种状态具体表现在父类的引用指向子类的实例。在我看来就是当一个类被多个类去继承后,或者是接口被多个实现类实现后,通过这种用父类或者接口的类型对象去承接他们的子类或者是实现类创建的对象。这么做是为了开放中的解耦合,便于扩展和维护。比如在定义方法的时候使用父类型去作为参数,那么这个方法就可以承接该父类的所有子类类型对象或者是接口的实现类对象。但是!!多态种父类不能使用子类独有的方法,也就是他只能承接到与自身相同的那些方法并使用。所以,就有了类型转换的概念,分为自动类型转换和强制类型转换:
由于在实例化对象的时候,s属于父类类型,因此要将s强制类型转化为子类类型才可以调用子类独有的方法。子类覆写了父类的方法则使用的是子类的方法,如果子类没覆写则调用的是父类的方法。
注意,编译看左,执行看右:引用类型变量发出的方法的调用到底是哪个类中的方法必须在程序运行期间才能确定。
子类 对象变量 = (子类)父类类型变量
People s1 = new Student();
//s1.sick(); ×
Student s2 = (Student)s1;
s2.sick();// √
!!但是如果转型后的类型和对象真实类型不是同一种类型,那么在转换的时候就会出现ClassCastException(类型转换异常)
解决方法!!如下使用 instanceof
People p = new Student();
if (p instanceof Student){
Student s = (Student) p;
}else(p instanceof Teacher){
Teacher t = (Teacher) p;
}
一般有以下常见类型:
- 父类类型 对象名称 = new 子类构造器;
- 接口 对象名称 = new 实现类构造器;
instanceof的使用详解
instanceof是Java的一个保留关键字,左边是对象,右边是类,返回类型是Boolean类型。它的具体作用是测试左边的对象是否是右边类或者该类的子类创建的实例对象,是,则返回true,否则返回false。其实质是看左边的对象是否能转换为右边的类型,且由于java的向上转型是安全的,所以如果左边的对象是右边类的子类,也是可以转的。
instanceof使用注意事项
先有继承关系,再有instanceof的使用。
当该测试对象创建时右边的声明类型和左边的类其中的任意一个跟测试类必须得是继承树的同一分支或存在继承关系,否则编译器会报错。
当你想测试null时,因为null
可以转换成为任何类型,所以不属于任何类型,所以与任何类instanceof
结果会是false
isInstance
isInstance(o)
判断的是o
是否属于当前Class
类的实例.
instanceof
:前面是实例对象,后面是类型isInstance
:调用者(前面)是类型对象,参数(后面)是实例对象isAssignableFrom
:判断的是类和类之间的关系,调用者是否可以由参数中的Class
对象转换而来。
不同!?:isInstance()
这个方法,是可以使用在基本类型上的,其实也不算是用在基本类型,而是自动做了装箱操作。
java中的值传递
方法的定义可能会用到 参数(有参的方法),参数在程序语言中分为:
实参(实际参数) :用于传递给函数/方法的参数,必须有确定的值。
形参(形式参数) :用于定义函数/方法,接收实参,不需要有确定的值。
String hello = "Hello!";
// hello 为实参
sayHello(hello);
// str 为形参
void sayHello(String str) {
System.out.println(str);
}
程序设计语言将实参传递给方法(或函数)的方式分为两种:
**值传递 :**方法接收的是实参值的拷贝,会创建副本。
**引用传递 :**方法接收的直接是实参所引用的对象在堆中的地址,不会创建副本,对形参的修改将影响到实参。
很多程序设计语言(比如 C++、 Pascal )提供了两种参数传递的方式,不过,在 Java 中只有值传递。
所以,在java中将实参传递给方法或者函数的方法是值传递!
- 如果参数是基本的数据类型的话,传递的是基本类型的字面量值的拷贝,会创建副本
- 但是如果参数是引用类型的话传递的就是实参所引用的对象在堆中的地址值的拷贝,同样也会创建副本,但承接的是地址的值,所以注意地址值在函数方法中被承接创建副本后进行的变化并不会影响原来所指向他们的地址,所以他们原来的指向的值还是同一个值。
这边分清楚引用传递和传递引用类型的区别!!!
Math常用方法
Math.abs()求绝对值 Math.pow(n,m)取幂
Math.ceil向上取整
Math.floor向下取整
Math.round()四舍五入
Math.sqrt()开平方
**Math.random()获取0~1之间的随机数(大于等于0小于1)**获取n到m之间的随机数:Math.random()*(m-n)+n;
Math.floor(Math.random() * 总数 + 第一个值)
java中的包装类
基本数据类型 包装类型 父类型
-----------------------------------------------------------------------
byte java.lang.Byte Number
short java.lang.Short Number
int java.lang.Integer Number
long java.lang.Long Number
float java.lang.Float Number
double java.lang.Double Number
boolean java.lang.Boolean Object
char java.lang.Character Object
抽象方法
抽象方法是使用abstract关键字修饰的成员方法,抽象方法在定义时不需要实现方法体。
抽象方法的定义格式如下:
abstract void方法名称 (参数);
当一个类包含了抽象方法,该类必须是抽象类。抽象类和抽象方法一样,必须使用abstract关键字进行修饰。
抽象类的定义规则如下。
(1)包含一个以上抽象方法的类必须是抽象类。
(2)抽象类和抽象方法都要使用abstract关键字声明。
(3)抽象方法只需声明而不需要实现。
(4)如果一个类继承了抽象类,那么该非抽象子类必须实现抽象类中的全部抽象方法。
- 一个类如果定义为抽象类,那么里面可以没有抽象方法
- 抽象类不能被实例化,可以实例化非抽象子类的对象
4 .抽象类的所有非抽象子类必须重写抽象类中的抽象方法 - 抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的。
接口和抽象类有什么共同点和区别?
共同点 :
都不能被实例化。
都可以包含抽象方法。
都可以有默认实现的方法(Java 8 可以用 default 关键字在接口中定义默认方法)实现的子类都可以用。
区别 :
接口主要用于对类的行为进行约束,你实现了某个接口就具有了对应的行为。抽象类主要用于代码复用,强 调的是所属关系。
一个类只能继承一个类,但是可以实现多个接口。
接口中的成员变量只能是 public static final 类型的,不能被修改且必须有初始值,而抽象类的成员变量默认 default,可在子类中被重新定义,也可被重新赋值。
过滤器与拦截器
1.什么是过滤器?
过滤器是一个程序,它先于与之相关的servlet或JSP页面运行在服务器上(服务器启动阶段就进行了对象的实例化)。过滤器可附加到一个或多个servlet或JSP页面上,并且可以检查进入这些资源的请求信息。在这之后,过滤器可以作如下的选择:
①以常规的方式调用资源(即,调用servlet或JSP页面)。
②利用修改过的请求信息调用资源。
③调用资源,但在发送响应到客户机前对其进行修改。
④阻止该资源调用,代之以转到其他的资源,返回一个特定的状态代码或生成替换输出。
2.Servlet过滤器的基本原理:
在Servlet作为过滤器使用时,它可以对客户的请求进行处理。处理完成后,它会交给下一个过滤器处理,这样,客户的请求在过滤链里逐个处理,直到请求发送到目标为止。例如,某网站里有提交“修改的注册信息”的网页,当用户填写完修改信息并提交后,服务器在进行处理时需要做两项工作:判断客户端的会话是否有效;对提交的数据进行统一编码。这两项工作可以在由两个过滤器组成的过滤链里进行处理。当过滤器处理成功后,把提交的数据发送到最终目标;如果过滤器处理不成功,将把视图派发到指定的错误页面。
抽象点就是:
在一群:AABBDADAUWBABCBAUBCAICBAOICAKB 中选出 B 出来。
在web.xml里面配置自定义的过滤器
<filter>
<filter-name>Redirect Filter</filter-name>
<filter-class>com.xx.filter.RedirectFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>Redirect Filter</filter-name>
<url-pattern>/xx/xx/*</url-pattern>
</filter-mapping>
1.什么是拦截器?
拦截器,在AOP(Aspect-Oriented Programming)中用于在某个方法或字段被访问之前,进行拦截然后在之前或之后加入某些操作。拦截是AOP的一种实现策略。
在Webwork的中文文档的解释为——拦截器是动态拦截Action调用的对象。它提供了一种机制可以使开发者可以定义在一个action执行的前后执行的代码,也可以在一个action执行前阻止其执行。同时也是提供了一种可以提取action中可重用的部分的方式。
谈到拦截器,还有一个词大家应该知道——拦截器链(Interceptor Chain,在Struts 2中称为拦截器栈 Interceptor Stack)。拦截器链就是将拦截器按一定的顺序联结成一条链。在访问被拦截的方法或字段时,拦截器链中的拦截器就会按其之前定义的顺序被调用。
2.拦截器的实现原理:
大部分时候,拦截器方法都是通过代理的方式来调用的。Struts 2的拦截器实现相对简单。当请求到达Struts 2的ServletDispatcher时,Struts 2会查找配置文件,并根据其配置实例化相对的拦截器对象,然后串成一个列表(list),最后一个一个地调用列表中的拦截器。
抽象:拦截器:把水流变小点,把鱼都拦住!顺便发个电:
同步请求和异步请求
全局刷新同步,局部ajax异步,两种都可以同步
**异步:**在异步模式下,当我们使用AJAX发送完请求后,可能还有代码需要执行。这个时候可能由于种种原因导致服务器还没有响应我们的请求,但是因为我们采用了异步执行方式,所有包含AJAX请求代码的函数中的剩余代码将继续执行。如果我们是将请求结果交由另外一个JS函数去处理的,那么,这个时候就好比两条线程同时执行一样。
**同步:**在同步模式下,当我们使用AJAX发送完请求后,后续还有代码需要执行,我们同样将服务器响应交由另一个JS函数去处理,但是这时的代码执行情况是:在服务器没有响应或者处理响应结果的JS函数还没有处理完成return时,包含请求代码的函数的剩余代码是不能够执行的。就好比单线程一样,请求发出后就进入阻塞状态,知道接触阻塞余下的代码才会继续执行。
实现同步的方法:ajax中根据async的值不同分为同步和异步两种请求方式,当async的值为true时是异步请求方式,相反的,当async的值为false时是同步请求方式,所以对于实现ajax同步请求只需要将async的值设为false就可以了。
$.ajax(
type:“POST”/“GET”
url:"",
data:{},
dataType:"json",
async:false, //同步 相反,true就是异步请求
success:function(response){
}
);
如何判断使用同步还是异步?
我们在发送AJAX请求后,还需要继续处理服务器的响应结果,如果这时我们使用异步请求模式同时未将结果的处理交由另一个JS函数进行处理。这时就有可能发生这种情况:异步请求的响应还没有到达,函数已经执行完了return语句了,这时将导致return的结果为空字符串。所以我们可以知道,同步是请求发送后一直等待服务器的响应回来后才执行发送下一个请求,而异步则不需要。当我们需要一个请求的响应值来做下一个请求的时候,我们就需要用到同步,同步可以避免出现死锁,读脏数据的发生,一般共享某一资源的时候用,如果每个人都有修改权限,同时修改一个文件,有可能使一个人读取另一个人已经删除的内容,就会出错,同步就会按顺序来修改。
异步则是可以提高效率了,现在cpu都是双核,四核,异步处理的话可以同时做多项工作,当然必须保证是可以并发处理的。
各层对应的职责
Controller层:获取参数,封装参数,处理业务(调用service处理,controller可以选择调用哪个service来处理该请求)
抽象类和接口
1)接口可以继承接口,而且可以继承多个接口,但是不能实现接口,因为接口中的方法全部是抽象的,无法实现;
另外,如果是Java 7以及以前的版本,那么接口中可以包含的内容有:1. 常量;2. 抽象方法
如果是Java 8,还可以额外包含有:3. 默认方法;4. 静态方法
如果是Java 9,还可以额外包含有:5. 私有方法
2)普通类可以实现接口,并且可以实现多个接口,但是只能继承一个类,这个类可以是抽象类也可以是普通类,如果继承抽象类,必须实现抽象类中的所有抽象方法,否则这个普通类必须设置为抽象类;
3)抽象类可以实现接口,可以继承具体类,可以继承抽象类,也可以继承有构造器的实体类。
抽象类中可以有静态main方法;抽象类里可以没有抽象方法,没有抽象方法的抽象类就是不想让别人实例化它;
另外,抽象类可以有构造方法,只是不能直接创建抽象类的实例对象而已。在继承了抽象类的子类中通过super(参数列表)调用抽象类中的构造方法,可以用于实例化抽象类的字段。
下面总结常见的抽象类与接口的区别:
1)抽象类和接口都不能直接实例化,如果要实例化,抽象类变量必须指向实现所有抽象方法的子类对象,接口变量必须指向实现所有接口方法的类对象;
2)接口只能做方法申明,抽象类中可以做方法申明,也可以做方法实现(java8中 接口可以有实现方法 使用default修饰);
3)接口里定义的变量只能是公共的静态的常量,抽象类中的变量是普通变量;
4)抽象类里的抽象方法必须全部被子类所实现,如果子类不能全部实现父类抽象方法,那么该子类只能是抽象类。同样,一个类实现接口的时候,如不能全部实现接口方法,那么该类也只能为抽象类;
5)抽象方法要被实现,所以不能是静态static的,也不能是私有private的,也不能被final修饰(试想一下,静态方法可以被类名直接调用,而类名直接调用一个没有实现的抽象方法没有意义)。
访问权限详解
public > protected > default(同包) > private
包访问权限(default) 比 protected少一个其他包中的子类
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jIOfg953-1665625195436)(C:\Users\86180\AppData\Roaming\Typora\typora-user-images\1661953753907.png)]
Object类中的方法
1.clone方法
保护方法,实现对象的浅复制,只有实现了Cloneable接口才可以调用该方法,否则抛出CloneNotSupportedException异常。
2.getClass方法
final方法,获得运行时类型。
3.toString方法
该方法用得比较多,一般子类都有覆盖。
4.finalize方法
该方法用于释放资源。因为无法确定该方法什么时候被调用,很少使用。
5.equals方法
该方法是非常重要的一个方法。一般equals和==是不一样的,但是在Object中两者是一样的。子类一般都要重写这个方法。
6.hashCode方法
该方法用于哈希查找,重写了equals方法一般都要重写hashCode方法。这个方法在一些具有哈希功能的Collection中用到。
一般必须满足obj1.equals(obj2)==true。可以推出obj1.hash-
Code()==obj2.hashCode(),但是hashCode相等不一定就满足equals。不过为了提高效率,应该尽量使上面两个条件接近等价。
7.wait方法
wait方法就是使当前线程等待该对象的锁,当前线程必须是该对象的拥有者,也就是具有该对象的锁。wait()方法一直等待,直到获得锁或者被中断。wait(long
timeout)设定一个超时间隔,如果在规定时间内没有获得锁就返回。
调用该方法后当前线程进入睡眠状态,直到以下事件发生。
(1)其他线程调用了该对象的notify方法。
(2)其他线程调用了该对象的notifyAll方法。
(3)其他线程调用了interrupt中断该线程。
(4)时间间隔到了。
此时该线程就可以被调度了,如果是被中断的话就抛出一个InterruptedException异常。
8.notify方法
该方法唤醒在该对象上等待的某个线程。
9.notifyAll方法
该方法唤醒在该对象上等待的所有线程。
并发和并行、同步和异步及多线程概念详解
并发的实质是一个物理的CPU(或者是多个CPU)在若干道程序(线程)之间多路复用,并发性可以说是对有限的物理资源强制进行多用户共享提高效率。但其实这种在宏观角度看到的同时处理的情况在微观上看来依旧是有进行排队等候,唤醒,执行等步骤,他们依然是序列被处理的,同一时刻到达的请求(线程)也会根据优先级的不同,而先后进入到队列排队等候执行。线程之间需要竞争得到执行机会。(微观角度)并不是同时在进行的。(宏观角度)但又是同时进行的。
此外还有一种称为并行,指的是两个或者是两个以上的事件或者是进程在同一时刻发生,是真正意义上的不同事件或者线程在同一时刻,在不同的CPU资源上同时执行。
同步:
进程之间的关系不是相互排斥临界资源的关系,而是相互依赖的关系。进一步说明了前一个进程的输出要作为后一个进程的输入,当第一个进程没有输出的时候第二个进程必须等待。这样具有同步关系的一组并发相互发送的信息称为消息或者事件。
异步:
既然同步是顺序的执行,执行完一个再执行下一个,他们需要等待协调运行。异步是彼此独立,在等待某事件的过程中继续做自己的事,并不需要等待这一俄格事情完成后再工作。线程就是实现异步的一个方式。异步是让调用方法的主线程不需要同步扽得改另一个线程的完成,从而可以让主线程干其他的事情。
多线程:
- 在了解线程之前,要先知道进程这个概念。进程是一个具有独立功能的程序关于某个数据集合的一次运行活动。简单点说,进程就是执行中的程序活动,是一个活动的实体。
- 多进程,就好比同时打开了Word,Excel和Visio,他们都是不同的程序运行活动,即多个进程同时启动而已,这个概念比较好理解。
- 线程,是一个执行中的程序活动(即进程)的多个执行路径,执行调度的单位。线程依托于进程存在,在进程之下,可以共享进程的内存,而且还拥有一个属于自己的内存空间,这段内存空间也叫做线程栈,是在建立线程时由系统分配的,主要用来保存线程内部所使用的数据。
- 多线程,指在一个进程下有多个线程。各个线程执行自己的任务,这些线程可以”同时进行“(这里加了双引号,下面会讲述到加双引号的原因)。
- 那多线程有什么好处?多线程应用在生活中随处可见,Word文档就是一个很好的例子。Word有“后台打印”的功能,用户点击打印按钮后,如果发现可以对当前文本进行修改,可以在打印过程中回到主界面进行修改、保存等操作。 如果没有应用多线程,不妨假设用户要打印的文本很长很长,那么用户要等打印操作执行完后,才可以对文本进行修改编辑保存等,这样用户体验就不如多线程的好。还有迅雷,有没有发现迅雷是可以同时下载东西的?例如同时下载A,B,A下载进度到53.4%,B下载进度到47.1%,有时A速度快些,有时B速度快些,反正能确定的是A,B都在下载内容,而不是一定要等A下载完后,B才可以开始下载,这也是多线程的作用。因此,多线程强调”同时,一起进行“,而不是单一的顺下操作。
多线程和异步的区别:
异步是多线程的最终目的,而多线程是实现异步的一种方法。异步是一个进程发送一个请求的时候,可以在没有收到回复之前做其他事情。异步除了用多线程实现外,也可以将一些耗时的操作交给其他进程来处理。
通过多线程实现并发,并行:
- java中的Thread类定义了多线程,通过多线程可以实现并发或并行。
- 在CPU比较繁忙,资源不足的时候(开启了很多进程),操作系统只为一个含有多线程的进程分配仅有的CPU资源,这些线程就会为自己尽量多抢时间片,这就是通过多线程实现并发,线程之间会竞争CPU资源争取执行机会。
- 在CPU资源比较充足的时候,一个进程内的多线程,可以被分配到不同的CPU资源,这就是通过多线程实现并行。
- 至于多线程实现的是并发还是并行?上面所说,所写多线程可能被分配到一个CPU内核中执行,也可能被分配到不同CPU执行,分配过程是操作系统所为,不可人为控制。所有,如果有人问我我所写的多线程是并发还是并行的?我会说,都有可能。
- 不管并发还是并行,都提高了程序对CPU资源的利用率,最大限度地利用CPU资源。
博客:https://blog.csdn.net/woliuyunyicai/article/details/45165869
作用域
pageContext:用来在同一个页面的不同标签之间传递数据。
request:在同一个请求的过程中间传递数据。
session:同一个浏览器窗口的不同请求之间传递数据。(eg:是否登录)
application:所有用户共享的数据,并且长久频繁使用的数据。
java类型转换规则
1、类型转换主要在在 赋值、方法调用、算术运算 三种情况下发生。
a、赋值和方法调用 转换规则:从低位类型到高位类型自动转换;从高位类型到低位类型需要强制类型转换:
(1)布尔型和其它基本数据类型之间不能相互转换;
(2)byte型可以转换为short、int、、long、float和double;
(3)short可转换为int、long、float和double;
(4)char可转换为int、long、float和double;
(5)int可转换为long、float和double;
(6)long可转换为float和double;
(7)float可转换为double;
另外还有是直接数的赋值:先通过直接数判断其类型,然后基本原则和上面谈到的赋值原则基本一致;只是直接数是整数时特殊一点,当在可表示范围内时,可以直接赋值给 byte short char三种类型;
b、算术运算 中的类型转换:1 基本就是先转换为高位数据类型,再参加运算,结果也是最高位的数据类型;2 byte short char运算会转换为Int;
(1)如操作数之一为double,则另一个操作数先被转化为double,再参与算术运算。
(2)如两操作数均不为double,当操作数之一为float,则另一操作数先被转换为float,再参与运算。
(3)如两操作数均不为double或float,当操作数之一为long,、则另一操作数先被转换为long,再参与算术运算。
(4)如两操作数均不为double、float或long,则两操作数先被转换为int,再参与运算。
特殊:
(1)如采用+=、*=等缩略形式的运算符,系统会自动强制将运算结果转换为目标变量(左边)的类型。
(2) 当运算符为自动递增运算符(++)或自动递减运算符(–)时,如果操作数为byte,short或char类型不发生改变;
三、引用类型 转换原则
1、基本类型 与 对应包装类 可自动转换,这是自动装箱和折箱的原理;
Integer c1 = new Integer(1);
Integer c2 = 2;
int cc = new Integer(3);
//另:String 与 基本类型的互转
int i = Integer.parseInt("13");
int ii = Integer.valueOf("12");
String s1 = String.valueOf(123);
String s2 = Integer.toString(i);
String s3 = "" + i;
2、两个引用类型间转换:
1、子类能直接转换为父类 或 接口类型;
2、父类转换为子类要 强制类型转换;且在运行时若实际不是对应的对象,会抛出ClassCastException运行时异常;
数组与链表小总结
数组静态分配内存,链表动态分配内存; 数组在内存中连续,链表不连续; 数组元素在栈区,链表元素在堆区; 数组利用下标定位,时间复杂度为O(1),链表定位元素时间复杂度O(n); 数组插入或删除元素的时间复杂度O(n),链表的时间复杂度O(1) 。如果数组是在尾部进行插入或者删除,时间复杂度为O(1);
java中常用的API方法
Cookie详解
Cookie是可以覆盖的,如果重复写入同名的Cookie,那么将会覆盖之前的Cookie。
cookie和session的区别就是cookie是存储在客户端(浏览器)东西,session是在服务端去存储的。
cookie由服务器生成,发送给浏览器,浏览器把cookie以kv形式保存到某个目录下的文本文件内,下一次请求同一网站时会把该cookie发送给服务器。由于cookie是存在客户端上的,所以浏览器加入了一些限制确保cookie不会被恶意使用,同时不会占据太多磁盘空间,所以每个域的cookie数量是有限的。
Cookie是服务端在HTTP响应中附带给浏览器的一个小文本文件,一旦浏览器保存了某个Cookie,在之后的请求和响应中,会将此Cookie来回传递,这样就可以通过Cookie这个载体完成客户端和服务端的数据交互。
Cookie常用的方法
void setMaxAge(int age) 设置Cookie的有效时间,单位为秒
int getMaxAge() 获取Cookie的有效时间
String getName() 获取Cookie的name
String getValue() 获取Cookie的value
Session和Cookie的区别
session:
保存在服务器
保存的数据是Object
会随着会话的结束而销毁
保存重要信息
cookie:
保存在浏览器
保存的数据是String
可以长期存在浏览器中,与会话无关
保存不重要的信息
请求转发与重定向(javaweb笔记中)
请求转发:是一种在服务器内部的资源跳转方式,可以携带数据,且跳转后并不会改变地址栏处的地址
如何进行请求转发:
1、通过request对象获取请求转发器对象:RequestDispatcher getRequesDipatcher(String path);
2、使用RequestDispatcher对象进行行转发:forward(ServletRequest request,ServletResponse response)
request.getRequestDispatcher("/requestDemo2").forward(request, response);
请求转发特点:
- 浏览器地址栏路径不发生变化
- 只能转发到当前服务器内部资源中。
- 转发是只发出一次请求,收到一次响应。可以使用requset对象来共享数据
java中创建对象的四种方式
1、使用new关键字调用构造函数创建对象。
2、利用反射创建对象:
①使用Class对象的newInstance()方法
Class<Student> c = Student.class;
Student s2 = (Student) c.newInstance();
s2.setName("huiye");
s2.setAge(17);
s2.setWeight(100.00);
System.out.println(s2);
Student{name='huiye', age=17, weight=100.0}
②通过反射先获取Constructor对象,在调用Construtor对象的newInstance方法
Class<Student> c = Student.class;
Constructor<Student> constructor = c.getConstructor();
Student s3 = constructor.newInstance();
s3.setName("huiye");
s3.setAge(18);
s3.setWeight(100.00);
System.out.println(s3);
Student{name='huiye', age=18, weight=100.0}
③使用clone()方法克隆一个对象
此方法需要实现Cloneable接口,重写Object类和clone()方法。
Object clone = s4.clone();
④使用对象流 ObjectInputStream的readObject()方法读取序列化对象
File file = new File("D:\\Files\\student.txt\\");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
Student s5 = new Student("huiye",20,100.00);
oos.writeObject(s5);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
Student s6 = (Student) ois.readObject();
System.out.println(s6);
Student{name='huiye', age=20, weight=100.0}
多线程详解
进程A和进程B内存独立不共享
在java语言中:线程与线程之间的栈内存不共享(一个线程一个栈内存,彼此之间压栈弹栈互不影响),堆内存和方法区内存共享。
使用了多线程后,main方法执行结束后,其实就是主栈空了,其他的栈还在压栈弹栈,所以其实没有结束。
真正的多线程是:
t1线程执行t1的,t2线程执行t2的,t1和t2互相不影响,这叫做真正的多线程并发。
所以单核的CPU是不能做到真正的多线程并发的,但是可以做到给人一种多线程并发的”感觉“。单核的CPU在某一个时间点上只能执行一个事情,但是因为CPU的执行速度快,在线程之间频繁切换,给人的感觉是多件事情同时在做。
线程的创建和启动
Java 虚拟机的主线程入口是 main 方法,用户可以自己创建线程,创建方式有两种(不只):
继承 Thread 类,编写一个类继承java.lang.Thread,覆写run方法,
如何新建一个分支线程对象? MyThread(编写的继承了Thread类的类) myThread = new MyThread();
如何启动线程 ?(此时还是在主栈中) myThread.start();
注意:start()方法在JVM中开辟了一个新的栈空间(任务),这段代码完成任务后瞬间就结束了。
启动成功的线程会自动调用run方法,并且run方法在分支栈的栈底部(压栈),main方法在主栈的底部,run和main是平级的。直接调用run的话不会启动新线程,因为没有开辟新的栈。
实现 Runnable 接口(推荐使用 Runnable 接口)
编写一个类实现java.lang.Runnable接口。但是编写一个类去实现Runable接口的时候,它并不是一个线程类,他只是一个普通的可运行的类。需要new一个实现Runnable的类的对象并且将该对象传入Thread对象参数中。然后再调用start函数。
**为什么建议?**因为实现接口是面向接口编程,一个类实现了接口还可以去继承其他的类,更加的灵活。
PS:采用匿名内部类的方式去实现多线程:
Thread mythread = new Thread(new Runable){
@Override
public void run(){
for(int i = 0 ; i < 1000 ; i ++){
System.out.print("分支线程---->"+i);
}
}
};
mythread.start();//启动线程
......
线程的生命周期
五个状态:新建(刚new出来的线程对象)
,就绪(又称为可运行状态,表示当前线程具有抢占CPU时间片的权力(执行权),当一个线程抢夺到执行权后,他就开始执行run方法,而润方法的执行标志着线程进入了运行状态)
,运行(开始执行run方法表示进入运行状态)
,阻塞(执行了 wait 语句、执行了 sleep 语句和等待某个对象锁,等待输入的场合)
,死亡(退出 run()方法);
在这边,就绪状态和运行状态之间的频繁切换依赖于JVM的调度。
如何获取线程的名字?
Thread t = new Threa();
t.setName("修改的线程名字");//修改线程的名字
String name =t.getName();//获取线程的名字
线程的默认名字是Thread-数字;
如何获取当前线程?
Thread t = Thread.currentThread;//t就是当前的线程,currentThread是静态方法可以直接方法名.函数名
什么叫当前线程,就是谁执行run方法,当前线程就是哪个线程。谁启动我谁就是当前线程。
线程的sleep方法:
static void sleep(long milios)
1、静态方法(所以与调用的对象没关系,只与当前类有关,在哪个线程中就让谁休眠)
2、参数是毫秒(表示睡眠多久,如果要多久执行一次,加个循环。)
3、左右,让当前进程进入休眠,进入”阻塞状态“,放弃占有CPU时间片,让给其他流程使用。出现在哪个线程中哪个线程就sleep。
线程的唤醒/中止正在睡眠的线程
不是中断线程,是中止线程睡眠。
t.interrupt();//干扰 这种中断睡眠的方式依靠了java的异常处理异常。()
强行中止一个线程
在java中如何强行中止一个线程的执行?
线程对象.stop() 可以强制停止线程。但此方法已过时,但不建议使用这种。缺点是会丢失数据。
他是直接将线程杀死,线程没有保存的数据会丢失。
那么如何合理的中止一个线程的执行?
创建一个类去实现Runable接口,在该类的实现中使用一个boolean变量,如果为true则执行覆写的run方法,如果为false则直接return;退出,什么时候需要中止,将他的对象的boolean变量值改为false即为退出。
线程的调度
java采用的是抢占式的调度模型,优先级高的抢到的CPU时间片多一些(抢到时间片的概率是一样的但是优先级高的抢到时间片后执行的时间多一些)。
哪些方法与线程的调度有关系呢?
设置线程的优先级 void setPriority(int newPriority)
获取线程的优先级 int getPrioriry()
最低的优先级1;默认优先级5;最高优先级10;
静态方法:让位
static void yield() 让位方法,暂停当前正在执行的线程对象,并执行其他线程,他不是阻塞方法,他是让当前线程让位给其他线程使用,会让当前线程从运行态转为就绪态。
实例方法:合并线程
在一个线程方法中new一个第二个线程的对象t并调用join方法,表示当前进程进入阻塞,t线程执行,直到t线程结束,当前线程才可以执行。
注意:合并线程并不是说两个线程的栈合并在一起或者说另一个栈消失了,而是两个栈之间协调发生了等待关系,等待另一个栈执行完了才去用时间片。
class mythread_1 extends Thread(){
public void doSome(){
MyThread2 t = new MyThread();
t.join();//当前进程进入阻塞
}
}
class MyThread2 extends Thread(){
}
多线程并发环境下的数据安全问题
服务器已经实现了多线程的机制,我们主要是应该把重点放到关注数据在多线程并发的环境下的安全问题。
什么时候数据在多线程并发环境下会有数据安全问题?
1、多线程并发
2、有共享数据
3、共享数据有修改行为
怎么解决线程安全问题?
线程排队执行(不并发),用排队执行解决线程安全问题,该机制称为:线程同步机制
会牺牲一些效率,这是没办法的,数据安全才是第一位的。
但是线程同步机制会导致用户吞吐量降低,降低用户体验,在不得已的情况下才使用。
- 第一种方案是:尽量使用局部变量代替实例变量和静态变量。
- 第二种方案是:如果必须是实例变量,那么可以考虑多个对象,这样实例变量的内存就不共享了,让一个线 程对应一个对象,100个线程对应100个对象,对象不共享就没有线程安全问题。
- 第三种方案:如果不能使用局部变量,对象也不能创建多个,这个时候就只能使用synchronized了。
同步编程模型和异步编程模型
异步编程模型:线程t1 和 t2各自执行各自的,互不相管,其实是多线程并发
同步编程模型:线程t1 和t2,当一方执行的时候另一方必须等待,直到它执行完。其实是线程排队执行。
使用线程同步解决安全问题
线程同步,指某一个时刻,指允许一个线程来访问共享资源,线程同步其实是对对象加锁,如
果对象中的方法都是同步方法,那么某一时刻只能执行一个方法。以下是同步块。
//public synchronized void run() {
//使用同步块
synchronized (this) {//括号中的应该是需要排队(同步)的线程的共享对象
//只要是共享对象就行,比如说是某个共享类下的一个单独对象,放到这里面的话,线程过来就占有了这个独立对象的锁,而另一个线程占有不了。这样的话就无法进入到同步块中。总之,只要放入一个对象,该对象是需要排队线程的共享对象,这样只要其中一个线程占有,另一个就无法占有。
for (int i=0; i<10; i++) {
s+=i;
}
System.out.println(Thread.currentThread().getName() + ", s=" + s);
s = 0;
}
//........
//........
}
当运行状态的线程遇到了synchronized关键字,它会放弃占有的CPU时间片,然后进入到锁池里去找共享对象的对象锁,如果没找到就一直在锁池等(可以看作是一种阻塞状态),找到的话就马上进入就绪状态,继续抢夺CPU时间片。
synchronized关键字后面括号中的共享对象问题
括号中的应该是需要排队(同步)的线程的共享对象,只要是共享对象就行,比如说是某个共享类下的一个单独对象,放到这里面的话,线程过来就占有了这个独立对象的锁,而另一个线程占有不了。这样的话就无法进入到同步块中。总之,只要放入一个对象,该对象是需要排队线程的共享对象,这样只要其中一个线程占有,另一个就无法占有。比如放入字符串常量“abc”,因为“abc”在字符串常量池中,所有线程共享,所以synchronized对所有线程都实现同步。老杜Java基础P629。
总之,括号里的对象,需要是对于需要令其排队的线程来说只有一把锁,其中一个线程占有其他就不能占有。
synchronized的写法
1、同步代码块:
synchronized(线程共享对象){
同步代码块;
}
2、在实例方法上使用synchronized
这个时候共享的对象一定是this,而且同步代码块是整个方法体。
3、在静态方法上使用synchronized
表示找类锁,类锁永远只有一把,就算是创建了100个对象,对象锁只有一个。
PS:对象锁:一个对象一个锁。
synchronized如果放到实例方法上,那么共享的一定是this,表示整个方法都需要同步,可能会无故扩大范围,导致效率低。优点是代码写的少,节俭了。如果共享的是整个方法,
java中的三大变量
实例变量:在堆中 堆和方法区只有一个,是共享的
静态变量:在方法区中
局部变量:在栈中 永远不会有线程安全问题,因为局部 变量不共享,它在栈中,一个线程一个栈。
如果使用局部变量的话建议使用StringBuilder,因为局部变量不存在线程安全问题。
ArrayList是非线程安全的。
Vector是线程安全的 。
HashMap HashSet是非线程安全的。
HashTable是线程安全的。
死锁:(得会写才会注意)
有一种情况,有两个资源,线程A和B分别锁了一个,它们的执行的时候需要的资源调用顺序相反,所以当他们想要获取下一个资源的时候,无法获取另一个资源的锁。
以下就是死锁的代码。说明synchronized不要嵌套使用,不然很容易发生死锁。
public class DeadLock{
public static void main(String[] args){
Object o1 = new Object;
Object o2 = new Object;
Thread t1 = new MyThread1(o1,o2);
Thread t2 = new MyThread2(o1,o2);
t1.start();
t2.start();
}
class MyThread1 extends Thread(){
Object o1;
Object o2;
public MyThread(Object o1,Object o2){
this.o1= o1;
this.o2= o2;
}
public void run(){
synchronized(o1){
try{
Thread.sleep(1000);
}
catch(InterruptedException e){
e.printStackTrace();
}
synchronized(o2){
}
}
}
}
class MyThread2 extends Thread(){
Object o1;
Object o2;
public MyThread(Object o1,Object o2){
this.o1= o1;
this.o2= o2;
}
public void run(){
synchronized(o2){
try{
Thread.sleep(1000);
}
catch(InterruptedException e){
e.printStackTrace();
}
synchronized(o1){
}
}
}
}
}
线程分为用户线程和守护线程
守护线程的代表是:垃圾回收线程。
特点是,守护线程是一个死循环,所有的用户线程只要结束了,守护线程自动结束。
守护线程在什么地方呢?每天00:00的时候,系统数据自动备份,这个需要定时器,可以将定时器设置为守护线程,如果结束了,守护线程自动退出,没有必要进行数据备份了。
如何设置线程为守护线程:在启动(start)之前,将线程通过 对象.setDaemon(true); 设置为守护线程
定时器概述
作用:间隔特定的时间执行一定的程序。
多种实现:1、使用sleep方法,睡眠,设置睡眠时间,每到时间点就醒来执行任务,这种方式比较原始,要自己写。
2、在java类库中已经写好了一个定时器,java.util.Timer,可以直接拿来用,不过这种方式也很少用,因为高级的框架支持定时任务。
3、spring框架中提供的SpringTask,只需要简单的配置就可以完成定时器的任务。
public static void main(String[] args)throws Exception{
Timer timer = new Timer();
// timer.setDaemon(true);
SimpleDateFormat sdf new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date firstTime = sdf.parse("2020-03-13 09:30:00");
timer.schedule(new LogTimerTask(),firstTime,"1000*10");
}
class LogTimerTask extends TimerTask(){
@Override
public void run(){
//编写需要执行的任务就行了。
SimpleDateFormat sdf new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date strTime = sdf.format(new Date());
System.out.print(strTime+":完成了一次数据备份!");
}
}
实现线程的第三种方式:FutureTask方式,实现Callable接口(JDK8新特性)
这种方式实现的线程可以获取线程的返回值。之前的 两种方式不能获取返回值,因为run方法返回void
系统委派一个线程去执行任务,改线程执行任务之后,可能会有一个执行结果,外面应该怎么能拿到这个执行结果呢?实现Callable接口
import java.util.concurrent.Callable;
import java,util.concurrent.FutureTask;
public class ThreadTest15{
public static void main(String[] args){
//第一步创建未来任务对象
//参数需要给一个Callable接口实现类对象
FutureTask task = new FutureTask(new Callable(){//采用匿名内部类实现
@Override
public Object call() throws Exception{
//call方法相当于run方法,但是它有返回值
System.out.println("call methon begin");
Thread.sleep(1000*10);
System.out.println("call methon end");
int a =100;
int b = 200;
return a+b ;//自动装箱变成Integer
}
});
Thread t = new Thread(task);
t.start();
Object obj = task.get();//这边用get方法返回t线程的返回结果,会导致当前线程阻塞,因为必须要t线程执行完了才会有返回值,所以需要等到t执行完后获取返回值才能继续当前线程。
System.out.println("线程执行结果:"+obj);
}
}
生产者和消费者模式
Object类对象的wait方法和notify方法
首先,wait和notify方法不是线程对象的方法,它是任何java对象都有的方法,因为Object是所有对象的父类。
wait方法的作用是(o.wait()):让正在o对象上活动的线程进入等待的状态,并且是无限期等待,直到被唤醒。他会让线程释放之前占有的o对象的锁。
notify方法的作用是(o.notify()):唤醒正在o对象上等待的线程。只会通知不会释放之前占有的o对象的锁。
notifyAll方法是唤醒o对象上处于等待的所有线程。
生产者和消费者模式是为了专门解决某个特定需求的。
一个线程负责生产(producer),一个线程负责消费(Consumer),并且要达到一个生产/消费的平衡:
生产满了不可继续生产,需要让消费进程消费。
消费完了不可继续消费,需要让生产进程生产。
放东西的是“仓库”,由它调用wait和notify,并且这两个方法的调用是建立在synchronized线程同步基础之上,因为仓库是共享的数据,需要保证数据安全。
匿名内部类
new 接口(抽象类){
接口的实现;
}
以上就是new了一个对象,new 了一个没有名字的类实现了接口。
接口是不可以new对象的,但是以上new的接口其实是一个没有名字的匿名类。
Java反射机制
反射机制有什么作用?
通过java语言中的反射机制可以操作字节码文件,可以读取和修改字节码文件。
在哪个包下?
java.lang.reflect.*
关于反射机制的重要的类有哪些?
java.lang.Class:代表字节码文件
/ java.lang.reflect.Methon :代表字节码中的方法字节码
/ java.lang.reflect.Constructor :戴白哦字节码中的构造方法字节码
/ java.lang.reflect.Field :代表字节码中的属性字节码:成员变量+静态变量+实例变量
如何获取java.lang.Class的实例:
第一种:
Class x = Class.forname(“类名”);
注意:**Class.forname()**是一个静态方法,方法的参数是一个字符串,字符串需要一个完整的类名(带有全部包名的类名)
PS:如果只想让一个类中的静态代码块执行(类中的别的程序不执行),则可以使用Class.forname(“类名”);这种情况下只有静态代码块会执行,因为forname会进行类加载,而静态代码块在类加载时执行且执行一次。
第二种:
Class x = 引用(对象).getClass();
第三种:
java语言中的任意一种类型包括基本数据类型,都有.class属性
Class x = String.Class;
Class x2 = 任何对象.Class;
获取Class实例后有什么用?
如下:
public static void main(String[] args){
try{
Class x = Class.forname("类名");
Object obj = x.newInstance();
System.out.println(obj);
}
catch(...){
...
}
重点是newInstance()调用的是无参构造方法,完成对象的创建,必须保证无参存在。
通过反射机制访问对象属性
Class x = Class.forname(“类名”);这个代码可以获取一个Class对象,该Class对象调用getDeclareField(“属性名”)的方法即可取出属性,此外还有一个getDeclareFields()来获取所有的属性。
Field noFiled = studentClass.getDeclareField(“属性名”);
noFiled.set(obj,2222);//给obj对象的属性赋值
获取类路径下文件的绝对路径
String path = Thread.currentThread().getContextClassLoader().getResource(“资源名”).getPath();
采用以上代码可以获取一个文件的绝对路径;(前提,资源在类路径下)
Thread.currentThread():当前活动的线程对象
getContextClassLoader():线程对象的方法,当前线程的类加载器默认从类的根路径下加载资源
整不明白到时候直接搜。
IO流详解
什么是IO流?
I:Input O:Output
通过IO可以完成硬盘文件的读和写。
IO流的分类:
1、按照流的方向进行分类
以内存为参照物,往内存中去,叫做输入Input,或者叫做读;
从内存中出来,叫做输出Output,叫做写;
2、读取数据方式的不同进行分类
按照字节读取的方式读取数据,一次读取一个byte,等同于一次读取8个二进制位,这种流叫做万能流,什么类型的文件都可以读取(文本文件,图片,声音…)叫做
有的流是按照字符的方式进行读取数据,一次读取一个字符,这种流为了方便读取普通文本文件,不能读取图片,声音,视频…连word文件都不能读取,只能读取纯文本文件。
java IO流这块有四大家族:
四大家族的首领:
java.io.InputStream 字节输入流
java.io.OutputStream 字节输出流
java.io.Reader 字符输入流
java.io.Writer 字符输出流
注意:在java中只要“类名”以Stream结尾的都是字节流。以“Reader/Writer”结尾的都是字符流。
四大家族的首领都是抽象类。(abstract class)
所有的流都实现了:
java.io.Closeable接口,都是可关闭的,都有close()方法。
流毕竟是一个管道,这个是内存和硬盘之间的通道,用完之后一定要关闭,
不然会耗费(占用)很多资源。养成好习惯,用完流一定要关闭。
所有的输出流都实现了:
java.io.Flushable接口,都是可刷新的,都有flush()方法。
养成一个好习惯,输出流在最终输出之后,一定要记得flush()
刷新一下。这个刷新表示将通道/管道当中剩余未输出的数据
强行输出完(清空管道!)刷新的作用就是清空管道。
**注意:**如果没有flush()可能会导致丢失数据。
java.io包下需要掌握的流有16个:
文件专属:
java.io.FileInputStream(掌握)
java.io.FileOutputStream(掌握)
java.io.FileReader
java.io.FileWriter
转换流:(将字节流转换成字符流)
java.io.InputStreamReader
java.io.OutputStreamWriter
缓冲流专属:
java.io.BufferedReader
java.io.BufferedWriter
java.io.BufferedInputStream
java.io.BufferedOutputStream
数据流专属:
java.io.DataInputStream
java.io.DataOutputStream
标准输出流:
java.io.PrintWriter
java.io.PrintStream(掌握)
对象专属流:
java.io.ObjectInputStream(掌握)
java.io.ObjectOutputStream(掌握)
java.io.FileInputStream:文件字节输入流,万能的,任何类型的文件都可以用这个流读,硬盘——>内存
① FileInputStream对象.read();每次只读取一个字节,内存和硬盘的交互太过于频繁。
② FileInputStream对象.read(byte[] b); 每次最多读取b.length个字节 减少内存与硬盘的交互,提高程序执行效率。往byte[] b中读。在IDEA中默认的当前路径是Project的根。**注意:**这个方法的返回值是读取到数组的字节数量(读取不到返回-1)。
String x = new String(b,0,读取的个数);即可将byte数组b转为字符串String,0表示从第一个开始
③FileInputStream对象.available()表示剩下没有读取的数量。这个方式不适合大文件,因为byte数组不能太大。
有什么用?调用该方法可以直接创建足够大的数组一次性承接文件中的字节数。
④FileInputStream对象.skip(int n); 跳跃。跳过几个字节不读取。
public static void main(String[] args){
FileInputStream fis = null;
try{
//括号中的是文件的地址,
fis = new FileInputStream("D:\\course\\javaProject...")
//int readData =fis.read();//读取一个字节,如果是最后,返回-1
while(true){
int readData =fis.read();
if(readData == -1){
break;
}
System.out.println(readData);
}
}
catch(FileNotFoundException e){
e.printStackTrace();
}
catch(IOEception){
e.printStackTrace();
}
finally{
try{
if(fis != null)
}catch(IOEception){
e.printStackTrace();
}
}
}
java.io.FileOutputStream:文件字节输出流,负责写,内存——>硬盘,写完以后一定要刷新flush。
FileOutputStream fos = new FileOutputStream (“文件名”);
byte[] b = {97,98,99,100};
fos.write(b);
fos.write(b,0,长度);写byte数组的一部分。
如果再写的话,会覆盖原来的文件,即从文件的开头开始写,那么如何使用追加的方式写文件,才不会清空原文件的内容?
使用这种方法构造: FileOutputStream fos = new FileOutputStream (“文件名”,true);他就会在末尾追加。
**FileReader:**文件字符输入流,只能读普通文本,读取文本内容时,更加方便快捷。
将接收的byte改为char来承接字符流数据,其他照葫芦画瓢。
**FileWriter:**文件字符输出流,写,只能输出普通文本。末尾追加的构造方法:
FileWrite op = new FileWrite ("文件名",true);其他也是依葫芦画瓢。
BufferedReader:带有缓冲区的字符输入流,使用这个流的时候不需要自定义数组char,或者说不需要自 定义byte数组,自带缓冲。
**构造方法:**BufferedReader(Read in); 创建了一个默认大小的输入缓冲区的缓冲字符流。
BufferedReader(Reader in , int sz); 创建了一个指定大小的输入缓冲区的缓冲字符流。
像上面这种,一个流的构造方法中需要一个流的时候,这个被传进来的流被称为:“节点流”
外部负责包装的流称为:“包装流”,或者称为“处理流”,只需要关闭外部的包装流,内部的节点流会自 动关闭
**方法:**①raedLine():读取一个文本行,但不带换行符(读出来的数据不带换行符),读到最后一行会返回null;
String line_1 = BufferedReader对象.readLine();
InputStreamReader/OutputStreamWriter:
转换流:
FileInputStream in = new FileInputStream("文件名");
InputStreamReader reader = new InputStreamReader(in);
BufferedReader br = new BufferedReader (reader);
注意:因为是BufferedReader的构造方法需要传入的是Reader字符流,如果传入的是字节流,需要 依靠转换流,将字节流转为字符流。
**PrintStream:**标准的字节输出流,默认输出到控制台。标准输出流不需要手动关闭。
可以改变输出方向吗?
之前System类使用的方法和属性:
System.gc();
System.currentTimeMillis();
PrintStream ps2 =System.out;
System.exit(0);
System.arraycopy(...);
PrintStream pr = new PrintStream(new FileOutputStream(“log”));
System.setOut(pr);//改变了输出的方法,传入的是一个OutputStream对象,输出到log日志文件。
eg:
public static void log(String msg){
try{
PrintStream out = new PrintStream(new FileOutputStream("log.txt",true));
System.setOut(out);
Date nowTime = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String strTime = sdf.format(nowTime);
System.out.println(strTime+":"+msg);
}catch(FileNotFoundException e ){
e.printStackTrace();
}
}
File类
文件和目录路径名的抽象表现形式。文件是路径的一部分,代表路径的终点。路径名的抽象表现形式。
需要掌握的file类的常用方法:
File f1 = new File("路径");
System.out.println(f1.exists());//判断路径是否存在,返回true 或者 false
if (!f1.exits()){//如果不存在,
f1.creatNewFile();// 以文件形式新建
f1.mkdir();// 以目录的形式新建
f1.mkdirs(); // 以多重目录形式新建
}
String parentPath = f1.getParent();//获取父路径
File parenFile = f1.getParentFile();//获取父亲,但是是以File对象的形式返回,。
parentFile.getAbsolutePath();//获取绝对路径
f1.getName();//获取文件名
f1.isDirectory();//判断是否是一个目录;
f1.isFile();//判断是不是一个文件
long haomiao = f1.lastModified();//返回最后一次修改的时间,是1970年到现在的总毫秒数。
Date time = new Date(haomiao);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String strTime = sdf.format(time);
f1.length();//获取文件的长度
File[] files = f1.listFiles();//获取当前目录下的所有子文件
for(File file : files){
System.out.println(file.getName());
}
序列化与反序列化序列化与反序列化
**序列化:**Serialize java对象存储到文件钟,将java对象的状态保存下来的过程。
**反序列化:**DeSerialize 将硬盘上的数据重新恢复到内存当中,恢复成java对象。
参与序列化与反序列化的对象必须实现Serializable接口:
public interface Serializable{
}
可以看到这只是一个标志性接口,他没有任何代码,但是他起到标志作用,java虚拟机看到类实现了这个接口,可能会对它进行特殊待遇。
public static void main(String[] args){
//创建java对象
Student s = new Student(111,"zahngsan");//假设有student类
//序列化
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(student));
//序列化对象
oos.writeObject(s);
//刷新
oos.flush();
//关闭
oos.close();
}
instanceof、?:、&、&&各自的功能
instanceof :用来判断某个实例变量是否属于某种类的类型。
? : 三目运算符:
表达式 1?表达式 2:表达式 3
如果表达式 1 为 true,执行表达式 2,否则执行表达式 3
&: 位运算:按位与 |
&&: 逻辑运算:逻辑与
描述>>、<<、>>>的功能
10>>4 :算术或符号右移位运算符
<< :算术或符号左移位运算符
“>>>”:逻辑或无符号右移位运算符
集合概述
1、什么是集合?有什么用?
数组也是一个集合,集合实际上就是一个容器,可以容纳其他类型的数据。
2、集合不能直接存储基本数据类型,另外集合也不能直接存储java对象,集合当中存储的都是java对象的内存地址。或者说集合中存储的是引用。
注意:
集合在java中本身是一个容器,是一个对象。集合中任何时候存储的都是”引用“。
3、在java中每一个不同的集合,底层都对应着不同的数据结构。往不同的集合中存储数据就等同于将数据放到了不同的数据结构中。什么是数据结构?数据存储的结构就是数据结构。不同的数据结构,数据的存储方式不同:
图,二叉树,数组,链表,哈希表…java中已经实现了常用的集合类,我们需要直到怎么用什么时候用。
1.4 集合在java JDK的java.util.*包下
在集合中集合分为两大类:
1、一类是单个方式存储元素
单个方式存储元素,这一类的超级父接口是:java.util.Collection;
2、一类是以键值对的方式存储元素
以键值对的方式存储元素,这一类集合的超级父接口是:java.utils.Map;
所有集合继承Iterable的含义是所有的集合都是可以迭代的。
Iterator it = Collection 对象.iterator(); 迭代器对象。
Iterator对象有以下方法:
hasNext(); next(); remove(); 通过这三个方法完成遍历。
Collection中能存放放什么元素?
①没有使用泛型之前,Collection可以存放Object的所有子类型。
②在使用泛型之后,Collection只能存放某个具体的类型。
集合中不能直接存放基本数据类型,也不能存放java对象,只能存放java对象的内存地址。
List接口中的常用方法。
List接口的特点:有序:有下标,可重复,从0开始,以1递增。 因为有下标,所以List集合也可以不用迭代器输出,可以通过下标遍历。这是List集合特有的遍历方式。
List是Collection接口的子接口。所以List接口中有一些特有的方法。
void add(int index, Object element) //向指定位置添加元素
Object set(int index, Object element) //更改指定位置的元素的值
Object get(int index) 获取指定位置的对象
int indexOf(Object o) 获取指定对象第一次出现处的索引
int lastIndexOf(Object o) 顾名思义,获取指定对象最后一次出现处的索引
Object remove(int index) 可以删除指定下标位置的元素
Collection集合迭代
因为Collection接口继承Iteratable接口,所以也就继承了该接口中的Iterator方法;
下面的集合遍历/迭代,适用于所有Collection,在Map中不能用。
获取迭代器后不能再进行add操作?
存进去是什么类型,取出来是什么类型,只不过输出的时候会调用toString方法变成字符串形式输出。
重点:注意集合结构发生改变,迭代器需要重新获取,,也就是迭代器需要在集合更改后获取,更改前获取迭代器(也就是适用老的迭代器)会发生异常。在迭代集合元素的过程中,不能调用元素的remove方法,否则会发生异常。但是可以适用迭代器的remove方法。所以一定要用迭代器的方法去删除。!!
获取的迭代器对象,迭代器来遍历集合,此时相当于对当前集合的状态拍了一个快照,以后是对这个快照进行迭代。适用迭代器的remove方法是对快照中的元素删除,所以和实际的集合元素是一样的,而如果适用集合对象的remove去删除的话没有通知迭代器导致数据不一致而引发异常。
//首先是创建集合对象
Collection c = new HashSet();//接口指向的实现类是什么不重要,只要是实现Collection,下面迭代都通用
//添加元素
c.add("abc");
c.add("adfa");
c.add(100);
c.add(new Object());
//对集合Collection进行遍历/迭代
//第一步:获取集合对象的迭代器对象Iterator
Iterator it=c.iterator();//集合继承了Iterable接口所以可以使用iterator方法返回一个iterator对象
//迭代器的方法:
//迭代器一开始并没有指向第一个元素。boolean hasNext():判断还有没有元素可以迭代
// Object next();让迭代器前进一位,并将指向的元素的返回。
Collection中的contains方法:
boolean contains(Object o ) 判断集合中是否包含某个元素o,它在底层调用的equals方法进行比较,对于String来说,equals已经重写,比较的是方法去的字符串常量池的内容是否一致。如果有返回true
所以一定要注意,contains底层调用了equals方法。对应的类型重写 了equals方法没?放在集合中的元素需要重写equals方法。没重写的话比较的是地址,重写的话要比较内容。
PS:另外还有remove方法,如果重写了equals方法的话,会依据内容去删除,只要内容一样,就删掉,不管删的在不在集合中,只要集合中和删除的一样,都视为同一个东西。
ArrayList集合初始化容量及其扩容
ArrayList集合初始化容量为10(底层先创建了一个长度为0的数组,当第一次使用了add后默认初始化容量为10),底层是Object数组
构造如下:
public static void main(String[] args){
//默认初始化容量10
List list1 = new ArrayList();
System.out.println(list1.size());
//指定初始化容量
List list2 = new ArrayList(20);
System.out.println(list2.size());
//以上两个结果都是0,由此可知,size()的作用是获取集合中元素的个数,而不是集合的容量
}
对于扩容,底层代码的自动增长的净值是容量值向右移一位,也就是增长的净量相当于容量的一半。
那么,扩容后,总的容量是原容量的3/2,即扩容1.5倍。(底层就是将现容量右移一位(x>>1)得到增长值,而右移两位(x>>2),二进制的右移等于除于2,左移相当于乘以2。)
因为底层是数组,数组扩容的效率低,所以给一个预估计的容量大小,减少扩容次数,这是ArrayList比较只要的优化策略。
此外 PS:那么多集合中,适用ArrayList集合最多,首先因为底层实现是Object数组,数组的优点就是,检索的效率高(因为每个元素所占用的大小相同,内存地址是连续的,知道首元素的内存地址然后知道下标通过数学表达式就可以计算元素的地址),数组的缺点是增删元素的效率低,并且不能存储大数据(很难找到连续的大空间);而数组的的末尾添加元素时效率是不受影响的,另外,在开发的时候,检索和查找某个元素是比较多的操作,所以ArrayList集合用得多,
ArrayList的三种构造方法:
//第一种
List mylist1 = new ArrayList();
//第二章
List mylist2 = new ArrayList(100);
//第三种
Collection c = new HashSet();
c.add(100);
c.add(200);
c.add(300);
List mylist3 = new ArrayList(c);//将c传进去,这个构造方法可以将HashSet集合转换成list集合
LinkList集合
底层是双向链表,LinkList也是有下标的,如get方法,get(i);可以获取第i个元素,但是这并不能提高链表的遍历速度。链表上的元素在空间存储上内存地址不连续,所以链表的优点是随机增删元素的时候不会引起大范围的元素位移,因此随机增删的效率较高。在开发中遇到随机增删集合中元素的业务比较多的时候,建议适用LinkList。缺点在于不可以通过数学表达式计算被查找元素的内存地址,每一次的查找都是从头节点开始遍历,直到找到为止。所以查找的效率不高。
MySQL中的疑难点总结
1、大小写,在使用模糊匹配时,也就是匹配文本时,MySQL默认配置是不区分大小写的。当你使用别人的MySQL数据库时,要注意是否区分大小写,是否区分大小写取决于用户对MySQL的配置方式.
怎么样让模糊查询可以区分大小写?
①在like后面加上binary,即可;
②设置“COLLATE”属性值为“utf8”,mysql采用utf8编码格式,模糊查询不区分大小写
③在创建表的时候,指定表字段COLLATE 为“utf8_bin”,或者修改指定表字段COLLATE 为“utf8_bin”;即,单 独指定所需字段(比如,在此处 我所需要不区分大小写的字段为表 ‘t_blog’ 中的 ‘title’ 字段)为 ‘utf8’ 编码 格式,COLLATE 为“utf8_bin”:
alter table t_blog CHANGE `title` `title` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL;
alter table `t_blog` modify column `title` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL;
以上,选择一种就好。
④修改字段为BINARY:
alter table t_blog CHANGE `title` `title` varchar(255) BINARY NULL DEFAULT NULL;
alter table `t_blog` modify column `title` varchar(255) BINARY NULL DEFAULT NULL;
两种选一种就好。
解决方案地址:https://blog.csdn.net/weixin_44694201/article/details/117792727
2、关于升序和降序
select ename,sal from emp order by sal desc , ename asc;
默认是升序(asc)。怎么指定升序或者降序呢?asc表示升序,desc表示降序。
select ename,sal from emp order by sal desc , ename asc;
注意:越靠前的字段越能起到主导作用。只有当前面的字段无法完成排序的时候,才会启用后面的字段。
3、关于分组函数(多行处理函数)
count 计数
sum 求和
avg 平均值
max 最大值
min 最小值
找出工资总和?
select sum(sal) from emp;
找出最高工资?
select max(sal) from emp;
找出最低工资?
select min(sal) from emp;
找出平均工资?
select avg(sal) from emp;
找出总人数?
select count(*) from emp;
select count(ename) from emp;
所有的分组函数都是对“某一组”数据进行操作的。
特点:输入多行,最终输出的结果是1行。分组函数自动忽略NULL。
注意:SQL语句当中有一个语法规则,分组函数不可直接使用在where子句当中。因为where是在group by之前执行的,分组函数是在group by以后执行的,分组函数在group by之前执行很明显是不对的。
以下是sql语句的执行顺序:
第一步:from 指定要操作的表
第二步:join 连接表生成一个笛卡尔积
第三步:on 对笛卡尔积进行筛选
第四步:where 筛选条件
第五步:group by 对数据进行分组
第六步:max min avg count sum 执行分组函数
第七步:having 对分组后的数据进行过滤
第八步:select 选取结果
第九步:distinct 去除重复结果
第十步:order by 将结果进行排序
第十一步:limit 取结果集中的部分数据
count()和count(具体的某个字段),他们有什么区别?
**count(*)😗*不是统计某个字段中数据的个数,而是统计总记录条数。(和某个字段无关)
count(comm): 表示统计comm字段中不为NULL的数据总数量。
4、单行处理函数
**重点:**所有数据库都是这样规定的,只要有NULL参与的运算结果一定是NULL。所以这个时候就需要用到ifnull函数;
ifnull() 空处理函数格式:ifnull(可能为NULL的数据,被当做什么处理)
select ename,(sal+ifnull(comm,0))*12 as yearsal from emp;
select ename,ifnull(comm,0) as comm from emp;
5、group by : 按照某个字段或者某些字段进行分组。
having : having是对分组之后的数据进行再次过滤。
**注意:**分组函数一般都会和group by联合使用,这也是为什么它被称为分组函数的原因。
并且任何一个分组函数(count sum avg max min)都是在group by语句执行结束之后才会执行的。
当一条sql语句没有group by的话,整张表的数据会自成一组。
select ename,max(sal),job from emp group by job;
以上在mysql当中,查询结果是有的,但是结果没有意义,在Oracle数据库当中会报错。语法错误。
Oracle的语法规则比MySQL语法规则严谨。
记住一个规则:当一条语句中有group by的话,select后面只能跟分组函数和参与分组的字段。
6、去重distinct
重点:distinct只能出现在所有字段的最前面。
7、连接查询
根据表的连接方式来划分,包括:
内连接:
等值连接
非等值连接
自连接
外连接:
左外连接(左连接)
右外连接(右连接)
全连接
笛卡尔积现象:当两张表进行连接查询的时候,没有任何条件进行限制,最终的查询结果条数是两张表记录条数的乘积。
所以我们要加条件过滤部分记录,这边需要注意的是,虽然我们加条件过滤后显现的数据只有一部分,但是实际的匹配(查找)次数是相同的,依旧是笛卡尔积的条数。
①内连接之等值连接
最大特点是:条件是等量关系。
select
e.ename,d.dname
from
emp e
inner join //inner 可以省略
dept d
on
e.deptno = d.deptno;
where
...
8、关于数据的删除和表的删除
①delete from 表名 where ?;
如果没有后面的where条件,将把该表中的所有数据删除,但表依旧存在。可回滚。
②truncate 表名:表被截断,不可回滚,数据永久丢失。注意只能作用于表,不可以针对视图。表结构依旧在。
truncate的作用是清空表或者说是截断表,只能作用于表。truncate的语法很简单,后面直接跟表名即可。执行truncate语句需要拥有表的drop权限,从逻辑上讲,truncate table类似于delete删除所有行的语句或drop table然后再create table语句的组合。为了实现高性能,它绕过了删除数据的DML方法,因此,它不能回滚。此外,它可以重置自增值,而delete不可以重置。
③**drop 表名;**删除表结构,直接将表删掉,不可回滚。直接将表的空间释放掉。
9、表级约束与列级约束
- 表级约束与列级约束
(1)对一个数据列建立的约束,称为列级约束
(2)对多个数据列建立的约束,称为表级约束
(3)列级约束既可以在列定义时声明,也可以在列定以后声明
(4)表级约束只能在列定义后声明
注意:有些既可以去约束一列成为表约束,也可以约束多列成为表级约束。
分别有以下约束:
1、not null:列级约束,所约束的字段不能为空。
2、unique:约束一列的时候表示该列的值不可以有相同的。约束多列的时候表示多列的组合值不可重复。
3、主键约束:约束的字段不可以为空也不可以重复。(相关概念:单一主键(推荐),复合主键,自然主键(推荐),业务主键)Mysql提供主键自增 id int primary key auto_increment,:使用auto_increment
4、外键约束:外键是可以为null的,而且一个表的外键不一定非要某个表的主键,但是外键在另一个表至少有unique的约束。具有唯一性,不可重复。
10、事务
事务是一个完整的业务逻辑单元,不可再分,一般来说事务中有多条DML语句,一条DML语句就可以完成的业务不需要用到事务。事务有以下特性:
①原子性:不可再分。
②一致性:多条DML语句要么都成功,要么都执行失败。
③持久性:操作的最终数据必须持久化到硬盘中,事务才算完成。
④隔离性:
事务有以下隔离级别:
1、读未提交(read uncommit):在对方事务还没有提交的时候,我们就可以读取到对方未提交的数据,这就会造成脏读现象,造成我们读取到脏数据。
2、读已提交(read commit):对方事务提交后我们才可以读取。但是会有不可重复读的问题。
什么是不可重复读:我感觉是,在一个事务的执行期间,如果别的事务对某个数据的修改已经提交,那么,这个事务再次去查询的时候,那个数据就会变成修改后的结果。
3、可重复读:(repeatable read)
这种隔离级别解决了:不可重复读问题。
这种隔离级别存在的问题是:读取到的数据是幻象。
什么是可重复读:我的理解是,在一个事务执行期间,所访问的相当于一个复制件,就是别的事务不管怎么去修改原数据的值对于已经复印出来的数据来说是不变的,所以同一个事务在执行期间对一个数据是可以重复读的。
4、序列化读/串行读
解决了所有问题,但是事务需要排队,效率低。
11、索引
对于查询一张表的时候有两种检索方式:
第一种方式:全表扫描
第二种方式:根据索引检索(效率很高)
索引为什么可以提高检索效率呢?
其实最根本的原理是缩小了扫描的范围。
索引虽然可以提高检索效率,但是不能随意的添加索引,因为索引也是数据库当中的对象,也需要数据库不断的维护。是有维护成本的。
比如:表中的数据经常被修改,这样就不适合添加索引,因为数据一旦修改,索引需要重新排序,进行维护。
添加索引是给某一个字段,或者说某些字段添加索引。
索引的使用:
创建索引对象:
create index 索引名称 on 表名(字段名);
删除索引对象:
drop index 索引名称 on 表名;
索引的实现原理?(索引底层采用的数据结构是:B + Tree)
通过B Tree缩小扫描范围,底层索引进行了排序,分区,索引会携带数据在表中的"物理地址",最终通过索引检索到数据之后,获取到关联的物理地址,
通过物理索引检索到数据之后,获取到关联的物理地址,通过物理地址定位表中的数据,效率是最高的。
select ename from emp where ename = ‘SMITH’;
通过索引转换为:
select ename from emp where 物理地址 = 0x123;
注意:索引在模糊查询的时候失效。
12、视图
**什么是视图?**站在不同的角度去看到数据。(同一张表的数据,通过不同的角度去看待
如何使用视图?
create view myview as select empno,ename from emp;(只有DQL语句才能以试图对象的方式创建出来。)
drop view myview;
对视图进行CRUD操作也会影响到原来的表内容,但不是直接操作原表的数据。
**那为什么要去使用视图?**因为在某些特殊情况,我们不想要别人看到表的所有内容,就可以通过视图的方式让对方只看到其中的一部分字段,试图可以隐藏表的实现细节。保密级别较高的系统,数据库只对外提供相关的视图,java程序员只对视图对象进行CRUD。
13、三范式 :设计表的依据。按照这三个范式设计的表不会出现数据冗余。
第一范式:任何一张表都应该有主键,并且每一个字段原子性不可再分。
第二范式:建立在第一范式的基础上,所有非主键字段完全依赖主键,不能产生部份依赖。
多对多?三张表,关系表两个外键。
t_student学生表
sno(pk) sname
------
1 张三
2 李四
3 王五
t_teacher 讲师表
tno(pk) tname
----------------------
1 王老师
2 张老师
3 李老师
t_student_teacher_relation 学生讲师关系表
id(pk) sno(fk) tno(fk)
-------------------------------------------
1 1 3
2 1 1
3 2 2
4 2 3
5 3 1
6 3 3
第三范式:建立在第二范式的基础上,所有非主键字段直接依赖主键,不能产生传递依赖。
一对多?两张表,多的表加外键。
班级t_class
cno(pk) cname
1 班级1
2 班级2
学生t_student
sno(pk) sname classno(fk)
--------------------------------------------
101 张1 1
102 张2 2
103 张3 2
104 张4 1
105 张5 2
在实际开发的过程中依旧有可能拿冗余换速度。
一对一的表关系如何设计:
①主键共享:一张表的主键去做另一张表的外键,同时也是另外一张表的主键。
②外键唯一:表A的主键去做表B的外键,这个外键保持唯一性。
正则表达式
正则表达式也是一种语言/语法:
可以定义字符串的匹配模式,可以用来判断指定具体字符串是否符合字符串的匹配模式。
常用的正则表达式在桌面txt文件里。具体规则:
Redis详解(Remote Dictionary Server远程字典服务)
Redis是一种数据库,能够存储数据、管理数据的一种软件。Redis中的数据大部分事件都是存储到内存中,适合存储频繁访问,数据量较小的数据。C语言编写,开源的,基于内存运行并支持持久化的(可以持久化到磁盘中)高性能的NoSql。
是一个开源的使用ANSI C语言编写,支持网络,可基于内存亦可持久化的人日志型 ,Key-Value数据库。
关系型数据库的时代演变:
1、单机数据库时代:一个应用一个数据库实例。
2、缓存、水平切分时代:
3、读写分离时代:
4、分表分库时代(集群):
**非关系型数据库(NoSql):**因为表存储有限,表与表之间关系太多太复杂。而非关系型数据库彻底改变了底层的存储机制,不再采用关系数据模型,而采用的是聚合数据结构存储数据。例如:Redis,mogoDB、HBase……
Redis能干吗?
1、内存存储、持久化,内存中是断电即失,所以说持久化是很重要的(rbd,aof)
2、效率高,用于高速缓存
3、发布订阅系统
4、地图信息分析
5、计时器计数器(浏览量)
6、……
特性
多样的数据类型,持久化,集群,事务……
资源
公众号:狂神说
官网:https://redis.io
中文网:http://www.redis.cn/
下载地址:通过官网下载即可
个别注解详解与理解
@RequestMapping
@RequestMapping注解是一个用来处理请求地址映射的注解,可用于映射一个请求或一个方法,可以用在类或方法上。
@ResponseBody
@ResponseBody的作用其实是将java对象转为json格式的数据。
@responseBody注解的作用是将controller的方法返回的对象通过适当的转换器转换为指定的格式之后,写入到response对象的body区,通常用来返回JSON数据或者是XML数据。
注意:在使用此注解之后不会再走视图处理器,而是直接将数据写入到输入流中,他的效果等同于通过response对象输出指定格式的数据。
@ResponseBody是作用在方法上的,@ResponseBody 表示该方法的返回结果直接写入 HTTP response body 中,一般在异步获取数据时使用【也就是AJAX】。
注意:在使用 @RequestMapping后,返回值通常解析为跳转路径,但是加上 @ResponseBody 后返回结果不会被解析为跳转路径,而是直接写入 HTTP response body 中。 比如异步获取 json 数据,加上 @ResponseBody 后,会直接返回 json 数据。@RequestBody 将 HTTP 请求正文插入方法中,使用适合的 HttpMessageConverter 将请求体写入某个对象。
原文链接:https://blog.csdn.net/originations/article/details/89492884
//后台 Controller类中对应的方法:
@RequestMapping("/login.do")
@ResponseBody
public Object login(String name, String password, HttpSession session) {
user = userService.checkLogin(name, password);
session.setAttribute("user", user);
return new JsonResult(user);
}
@RequestBody是作用在形参列表上,用于将前台发送过来固定格式的数据【xml格式 或者 json等】封装为对应的 JavaBean 对象,
//封装时使用到的一个对象是系统默认配置的 HttpMessageConverter进行解析,然后封装到形参上。
//如上面的登录后台代码可以改为:
@RequestMapping("/login.do")
@ResponseBody
public Object login(@RequestBody User loginUuser, HttpSession session) {
user = userService.checkLogin(loginUser);
session.setAttribute("user", user);
return new JsonResult(user);
}
mybatis中resultMap和resultType的详细用法
resultType是直接表示返回类型的,而resultMap则是对外部resultMap的引用,但是resultType跟resultMap不能同时存在。
<resultMap id="BaseResultMap" type="com.bjpowernode.crm.workbench.domain.Activity">
1.resultType
在MyBatis进行查询映射的时候,其实查询出来的每一个属性都是放在一个对应的Map里面的,其中键是属性名,
值则是其对应的值。当提供的返回类型属性是resultType的时候,MyBatis会将Map里面的键值对取出赋给
resultType所指定的对象对应的属性。所以其实MyBatis的每一个查询映射的返回类型都是ResultMap,
只是当我们提供的返回类型属性是resultType的时候,MyBatis对自动的给我们把对应的值赋给resultType
所指定对象的属性.
resultType适用于返回值是简单类型(String,Integer)或者数据库字段与实体类字段完全一致的情况下,所以属性值一般是某一个pojo类或者简单类型。
2、resultMap
如果数据库中的字段在resultType指定的实体类中没有对应的字段名称,那这个值将丢失。如果实例类中的字段名称在数据库中不存在或者不一致,那么最终实体类的这个字段为null。
开发中还会遇到一些情况,比如要实现结果映射成实体类的值,但是数据库改了字段,实体类又因为多处使用不能直接更改,怎么处理??
1.可以通过sql字段设置别名,或者先返回Map类型再进一步转换。
2. 使用resultMap
基于原有的实体类自定义resultMap,重新指定实体字段名与数据库字段名的映射关系,那么在sql方法标签中这要使用resultMap设置返回值类型,属性的值自然就是我们自己定义的resultMap的id值。
eg:
<resultMap id="BaseResultMap" type="com.bjpowernode.crm.workbench.domain.Activity">
<!--
WARNING - @mbg.generated
This element is automatically generated by MyBatis Generator, do not modify.
This element was generated on Sun Sep 11 22:01:00 CST 2022.
-->
<id column="id" jdbcType="CHAR" property="id" />
<result column="owner" jdbcType="CHAR" property="owner" />
<result column="name" jdbcType="VARCHAR" property="name" />
<result column="start_date" jdbcType="CHAR" property="startDate" />
<result column="end_date" jdbcType="CHAR" property="endDate" />
<result column="cost" jdbcType="VARCHAR" property="cost" />
<result column="description" jdbcType="VARCHAR" property="description" />
<result column="create_time" jdbcType="CHAR" property="createTime" />
<result column="create_by" jdbcType="VARCHAR" property="createBy" />
<result column="edit_time" jdbcType="CHAR" property="editTime" />
<result column="edit_by" jdbcType="VARCHAR" property="editBy" />
</resultMap>
所以在开发mybatis的过程中如果返回值类型中字段与数据库字段不一致,又用了resultType的时候,不一致的字段的值为null,即无法显示。
设计模式
apache-poi生成Excel文件
如果要创建或者是操作excel文件,我们不直接去操作,二是通过apache-poi插件,我们操作插件,插件操作excel
基础操作如下:
package com.bjpowernode.crm.poi;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class createExcelText {
public static void main(String[] args) throws IOException {
HSSFWorkbook hssfWorkbook= new HSSFWorkbook();//创建一个Excel文件对象
HSSFSheet sheet = hssfWorkbook.createSheet();//在以上对象下创建一个sheet对象
HSSFRow row = sheet.createRow(0);//在以上sheet对象下创建一个行对象
HSSFCell cell = row.createCell(0);//在这一行创建对应的列对象(这个时候已经确定单元)
cell.setCellValue("学号");//往单元里传值
cell = row.createCell(1);//这边直接改变cell对象的引用,但不会把之前的操作丢到gc
cell.setCellValue("姓名");
cell = row.createCell(2);
cell.setCellValue("班级");
for(int i=1;i<10;i++){
row = sheet.createRow(i);
cell=row.createCell(0);
cell.setCellValue("11613"+i);
cell=row.createCell(1);
cell.setCellValue("NAME"+i);
cell=row.createCell(2);
cell.setCellValue(i);
}
OutputStream out = null;//定义输出流
out = new FileOutputStream("L:\\studentList.xls");//输出流指向文件输出对象
hssfWorkbook.write(out);//向excel对象的write方法中传入文件输出流,得到流的方向与流
out.close();
hssfWorkbook.close();//注意要关闭
System.out.println("===============Excel创建完成!==============");
}
}
注意:首先是row或者是cell对象,后面改变它们的引用不会影响前面已经进行了的操作。其次重要的是一定要关闭文件对象以及创建的文件输出流!
此外,可以创建样式对象HSSFCellStyle
HSSFCellStyle style = new HSSFCellStyle();
style.setAlignment(HorizontalAlignment.CENTER)//这个函数的参数是枚举类型,封装好了,按照固有的位置格式的参数传参:CENTER,LEFT,RIGHT...
//其他样式百度
文件下载:
注意,所有文件下载的请求只能是同步请求。
如果发送异步请求,ajax会将返回值给回调函数,回调函数可以解析json数据,但是不能解析一个文件。
如何发同步请求:地址栏,超链接,form表单
@RequestBody的使用
基础知识介绍:
@RequestBody主要用来接收前端传递给后端的json字符串中的数据的(请求体中的数据的);而最常用的使用请求体传参的无疑是POST请求了,所以使用@RequestBody接收数据时,一般都用POST方式进行提交。在后端的同一个接收方法里,@RequestBody与@RequestParam()可以同时使用,@RequestBody最多只能有一个,而@RequestParam()可以有多个。
注:一个请求,只有一个RequestBody;一个请求,可以有多个RequestParam。
注:当同时使用@RequestParam()和@RequestBody时,@RequestParam()指定的参数可以是普通元素、
数组、集合、对象等等(即:当,@RequestBody 与@RequestParam()可以同时使用时,原SpringMVC接收
参数的机制不变,只不过RequestBody 接收的是请求体里面的数据;而RequestParam接收的是key-value
里面的参数,所以它会被切面进行处理从而可以用普通元素、数组、集合、对象等接收)。
即:如果参数时放在请求体中,application/json传入后台的话,那么后台要用@RequestBody才能接收到;
如果不是放在请求体中的话,那么后台接收前台传过来的参数时,要用@RequestParam来接收,或
则形参前 什么也不写也能接收。
注:如果参数前写了@RequestParam(xxx),那么前端必须有对应的xxx名字才行(不管其是否有值,当然可以通
过设置该注解的required属性来调节是否必须传),如果没有xxx名的话,那么请求会出错,报400。
注:如果参数前不写@RequestParam(xxx)的话,那么就前端可以有可以没有对应的xxx名字才行,如果有xxx名
的话,那么就会自动匹配;没有的话,请求也能正确发送。
追注:这里与feign消费服务时不同;feign消费服务时,如果参数前什么也不写,那么会被默认是
@RequestBody的。
如果后端参数是一个对象,且该参数前是以@RequestBody修饰的,那么前端传递json参数时,必须满足以下要求:
后端@RequestBody注解对应的类在将HTTP的输入流(含请求体)装配到目标类(即:@RequestBody后面的类)时,会根据json字符串中的key来匹配对应实体类的属性,如果匹配一致且json中的该key对应的值符合(或可转换为),这一条我会在下面详细分析,其他的都可简单略过,但是本文末的核心逻辑代码以及几个结论一定要看! 实体类的对应属性的类型要求时,会调用实体类的setter方法将值赋给该属性。
json字符串中,如果value为"“的话,后端对应属性如果是String类型的,那么接受到的就是”",如果是后端属性的类型是Integer、Double等类型,那么接收到的就是null。
json字符串中,如果value为null的话,后端对应收到的就是null。
如果某个参数没有value的话,在传json字符串给后端时,要么干脆就不把该字段写到json字符串中;要么写value时, 必须有值,null 或"“都行。千万不能有类似"stature”:,这样的写法,如:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F6HMxxwp-1665625195445)(C:\Users\86180\AppData\Roaming\Typora\typora-user-images\1664329978813.png)]