Java编码基础培训笔记
公司组织的Java编码基础3天培训课程笔记
一、面向对象设计
AOP思想
面向方面(切面):即专家思想,业务专家(只关心业务方面)、事务专家、权限专家、日志专家
问题:具体业务是变化的,怎么办?提炼出接口Interface
假如你是Sun公司,怎么设计JDBC?
给出一套接口Connection、Statement、ResultSet,具体实现交给数据库厂商,隔离了具体数据库的不同,统一了对外行为。
如何拦截方法执行?
代理模式(静态代理、动态代理)
接口需要MethodRequest类型,已有的是Service类型(真正做事的Service.sayHello()),怎么办?
class X implements MethodRequest{
private Service service;
public void call(){
service.sayHello();
}
}
IoC思想
控制反转
想用一个对象时直接用,不用负责对象的创建。
控制反转是创建对象权利的转移
实现了使用者和创建者分离
一般通过创建型模式实现
依赖注入
一种具体的解耦方式:构造函数的反射、setter方法反射、field变量反射
二、JVM
类的编译、加载、初始化等机制
类加载机制
java中所有的类都是要通过类加载器来加载的
- 根类加载器:加载jdk/jre/lib/rt.jar中的类
- 扩展类加载器:加载jdk/jre/lib/ext/*.jar
- 应用类加载器:加载classpath下的类
总是先从根类加载器加载类,没有才去扩展类加载器,扩展没有才去应用类加载器
自己写一个java.lang.String类有用吗?没有,因为先在根类加载器中加载
自定义类加载器:现实反编译后得到的java文件是乱码
查看当前类的加载器
ClassName.class.getClassLoader().getClass().getName(); //应用类加载器
ClassName.class.getClassLoader().getParent().getClass().getName(); //扩展类加载器
ClassName.class.getClassLoader().getParent().getParent().getClass().getName(); //null
如果将此类打jar包放在jdk/jre/lib/ext/中,则第一条是扩展类加载器,第二条报错
不管在什么时候(编译时、运行时)加载类,都需要类加载器
功能性类建议在运行时加载
编译时加载类又称静态加载类,new对象都是
运行时加载类又称动态加载类,只有class.forName()能实现
当一个类没有构造函数时,jvm会自动给一个无参数构造函数
子类无参数构造函数会默认调用父类无参构造函数
初始化顺序:父类静态、子类静态、父类定义初始化、父类构造函数、子类定义初始化、子类构造函数
内存分配与垃圾回收机制
堆:新生代、老生代
新生代:eden,fr,to
new对象放到eden,eden满了之后垃圾回收:把存活的对象拷贝到fr,fr满了拷贝到to区域,to满了拷贝到old
fr和to称为救助空间
一般-Xmn和-Xmx设为相同,以免频繁的扩展内存和回收内存导致项目运行变慢。
垃圾回收
young垃圾回收,old垃圾回收,串行、并行
full gc(新、老生代全部gc)会导致应用暂停,屏蔽System.gc()
jdk小工具
jdk/bin目录下的小工具:jstack,jps,jmap,jhat,jconsole
三、java集合框架(看源码)
java.util.Collection
java.util.list
ArrayList,
数据结构:数组,
只能连续存放,
要预估大小,避免扩容,初始化不指定大小默认长度为10,扩容要重新申请空间再拷贝原来的,且每次扩容只扩到1.5倍大小,原来的空间变为垃圾需要回收
注意序列化的问题,看源码:关键字transient,不可序列化
方法签名:writeObject(),可自己序列
方法签名:readObject(),反序列化
LinkedList
数据结构:链表结构
有丰富的头尾操作方法
很方便作为队列的容器
java.util.Set
HashSet
会自动过滤重复元素,但当元素类型为类对象时会根据地址判断
Java中默认认为地址不一样就是不同的对象,而实际业务中对象是否重复应该根据业务来定义,java提供了equals()方法和hashCode()方法,告诉java什么样的是重复对象
TreeSet
放入Set中的元素会进行排序,TreeSet同样不能放重复元素
必须对其中的元素进行排序定义:一、实现Comparable接口;二、TreeSet构造的时候使用java.util.Comparator
TreeSet
tu.add(user1); //报错,User对象必须进行排序定义
String类对象可直接放入,说明String类已经实现了Comparable接口,如果想String放入TreeSet时倒序排序,可构造TreeSet对象时定义Comparator
java.util.map
HashMap(key,value)
数据结构:数组+链表
hash算法都是一次性定位到某个位置,把数据放进去,下次再用算法定位到此位置把数据取出来
看源码:
map.put(key,value)
h = hash(key); 求key的hashcode值
h&(length-1)==h%length,当length为2的次方数时此等式成立
冲突处理:数组位置index相同的以链表存储
HashMap存在可能内存泄露的问题:一旦元素放入,和key相关的标识是不能修改的,一修改就内存泄露
User u1 = new User("masi",1000,30);
HashMap<User,String> map = newHashMap<User,String>();
map.put(u1);
u1.setName("masi2");
map.get(u1);结果为空,因为求hash(u1)时hashcode不同,已经找不到放进去的u1了
HashSet的底层就是HashMap,只使用其key,value是定死的常量,所以HashSet也存在内存泄露
关于hashmap的扩展思考
经常修改的列适合做索引吗?不适合
了解一致性哈希算法,搭建内存服务器集群时需要考虑
竞拍活动架构设计
难点:
倒计时最后几秒高并发
不能阻塞任何出价人的请求
架构:
用户->WebServer->服务->数据库
问题:高并发时可能在WebServer阻塞请求,丢失用户请求
改造:
在WebServer增加队列,WebServer是生产者,服务是消费者,将请求先放入队列,保证不丢失,先不处理。
数据库前增加内存服务器redis,放入redis时直接按价格排序,排序后再存入数据库(数据一致性问题)。
四、java.io操作(看设计)
字节流
InputStream:FileInputStream.read()
OutputStream:FileOutputStream.write()
FileOutputStream.write(100000),无法将数字100000写入文件,只能写最低8位,因为只能写一个字节。可以写4次,每次写一个字节,写完右移8位。
其他的字节流都是在原始的字节流上通过装饰器装饰得来的,无需死记,记不住
new D(new C(new B(new A())))
B,C,D,…位置可互换
A有父类X,B,C,D,…需要有共同的父类Y,
请问Y怎么设计?
Y需要继承自X,且:
class Y extends X{
private X x;
void Y(X x){
this.x = x;
}
}
这就是装饰器模式
字符流
字符流是由字节流适配而来
Reader
Writer
编码
练习1
读取文件,先按次数排序,次数相同的按姓名排序
统计次数用HashMap;排序用TreeSet
五、Java反射机制
Class实例获取方式
万事万物皆对象,类也是对象,是java.lang.Class类的实例对象
A类这个实例对象如何表示出来?
Class c1 = A.class; //类名.class
Class c2 = c1.getClass(); //A类对象.getClass
Class c3 = Class.forName(“A”); //Class.forName(“A类的完全限定类名”)
c1==c2,c2==c3 结果为true,因为c1,c2,c3都表示Class的A类实例对象,都是同一个对象
c1,c2,c3称为Class类型,类类型
Class.forName()也是动态加载类的方式
通过c1创建A类的实例对象:A aa = (A)c1.newInstance();
由Class实例获取对应类的信息
首先要获得Class对象
传入一个对象,打印该对象类的详细信息,如类名、方法
void printClassMsg(Object obj){
Class c = obj.getClass();
print: c.getName();
Method[] ms = c.getMethods();//获取所有public方法,包含从父类继承的
//c.getDeclaredMethods();//只获取自己声明的方法,包括公有私有
for(Method m:ms){
Class returnType = m.getReturnType();//返回值的类类型
print: returnType.getSimpleName() + m.getName
Class[] paraTypes = m.getParameterTypes();//参数列表的类类型
for(Class c:paraTypes){
}
Field[] fs = c.getDeclaredFields(); //成员变量
for(Field f:fs){
}
}
}
方法的反射
获取方法getMethod、调用方法Method.invoke
X x = new X();
Class c = x.getClsss();
try{
Method m = c.getMethod("f",int.class,int.class,String.class);
int result = m.invoke(x,10,20,"hhh");//等价于x.f(10,20,"hhh")
}catch(Exception e)
{
e.printStackTrace();
}
根据命令行参数调用对应的同名方法
main(args[0]){
X x = new X();
Class c=x.getClass();
Method m = c.getMethod(args[0]);
m.invoke(x);
成员变量的反射
获取成员变量Class.getDeclaredField(),操作成员变量f.get(obj),f.set(ojb)
Y y = newY();
//打印y的私有变量i,之前需要y.getI();
Class c = y.getClass();
try{
Field f = c.getDeclaredField("i");
f.get(y);//获取y对象的i成员变量信息,但无法获取私有的,报错
f.setAccessible(true);
f.get(y);/
}
class Y{
private int i=11;
}
练习2
写一个方法
public static void changeValue(Object obj){}
将obj中所有的字符串属性全部变为大写,所有int类型属性全部加100
构造函数的反射
获取构造getConstructor(),创建对象Constructor.newInstance()
Class c = Z.class;
Constructor<Z> cst = c.getConstructor();//获取无参构造
Constructor<Z> cst2 = c.getConstructor(int.class,int.class,String.class);
Z zz = cst.newInstance();
Z zz2 = cst2.newInstance(100,123,"sdd");
class Z{
public Z(){}
public Z(int,int,String){}
}
数组的反射
int[] a={1,2,3,4,5};
int[] b={4,5,6,7};
int[][] c={{1,2,3},{4,5,6}}
Class c1 = a.getClass();
Class c2 = b.getClass();
c1==c2,值为true
c1==int[].class,值为true
数组的类类型只和数组的类型与维数相关
java.lang.reflect.Array类操作数组
Z z = new Z();
Class cc = z.getClass()
Method m = cc.getMethod("method1",String.class,int[].class)
m.invoke(zz,"hello",new[]{1,2,3})
Class Z{
public void method1(String a, int[] b);
}
//普通对象打印toString,数组对象打印元素内容
public static void printOjb(Object obj){
Class c = obj.getClass();
if(c.isArray()){ //类类型是否数组
int length = Array.getLength(ojb);//获取数组长度
for(int i=0; i<length; i++){
//print: Array.get(ojb,i);//获取数组对象ojb的第i个元素,若传入一个二维数组,打印的是每个一维数组的地址
Object o = Array.get(ojb,i);
printOjb(o);//递归调用
}
}else{
print: obj.toString();
}
}
六、Java线程
如何创建线程
- 创建一个类,实现Runnable接口,以该类的实例作为创建Thread类对象的构造函数的参数
- 创建一个类,继承Thread类,重写run方法
Thread.start()启动线程后就调用run()方法
实现Runnable接口方式更常用
匿名类方式:
new Thread(new Runnable()
{ public void run(){
print;
}
}
).start();
new Thread(){
public void run(){
print;
};
}.start();
匿名类的对象
什么时候用?已知父类,要获取其子类的对象时 new 父类名(){//子类的实现}
线程的生命周期和常用API
newBorn状态->调用Start()方法->Runnable状态(包括Running和Ready,正在跑的是Running,轮候等待的是Ready)
Pause阻塞状态,sleep,wait,notify
Dead状态,调用stop()进入Dead状态,不建议使用stop()
多线程有不确定性
调度算法:
1、先来后到
2、优先级优先和时间片轮换:有优先级相同的线程时,有不确定性
Thread.sleep(1000);静态方法
不建议用某个线程去调用sleep(),可能有歧义
Thread t1 = new MyThread(new X());
t1.sleep();会被误认为只有t1去sleep,其实是哪个线程执行到sleep,哪个线程就去sleep
join()
Thread t1 = new MyThread(new X());
t1.start();
t1.join();//等待t1线程执行结束
Thread.yield()
线程的同步和互斥
同步块:synchronized(锁){}
执行此代码块的线程会把钥匙拿走,执行完之前所有其他线程都无法执行
java中的任何对象都可以作为一把锁,有且只有一把钥匙
多个线程间要想互斥,必须共享同一个锁对象
public static void main(){
Output o = new Output();
new Thread( new Runnable(){
public void run(){
while(1){o.print("hello");}
}
}
).start();
new Thread( new Runnable(){
public void run(){
while(1){o.print("BYE-BYE");}
}
}
).start();
}
class Output{
public void print(String name){
//synchronized(name){ //synchronized(name)输出还是会乱,name不同
//synchronized(this){ //synchronized(this)可以,this都是调用此方法的o,与print1互斥
//synchronized(Output.class){ //synchronized(Output.class)可以,与print2互斥
synchronized(String.class){ //synchronized(String.class)可以
for(int i=0; i<name.length(); i++)
System.out.println(name.charAt(i));
}
System.out.println();
}
public synchronized void print1(String name){
for(int i=0; i<name.length(); i++)
System.out.println(name.charAt(i));
System.out.println();
}
public synchronized static void print2(String name){
for(int i=0; i<name.length(); i++)
System.out.println(name.charAt(i));
System.out.println();
}
private Lock lock = new Reentrantlock();
public synchronized static void print3(String name){
lock.lock();
try{
for(int i=0; i<name.length(); i++)
System.out.println(name.charAt(i));
System.out.println();
}finally{
lock.unlock();
}
}
}
同步函数
同步函数也是有锁对象的:
非静态的同步函数的锁对象就是调用此函数的当前对象this
静态同步函数的锁就是类对象
锁Lock
java5版本开始还引入了Lock对象
java5版本开始还引入了读写锁,读与读间不互斥,
ReentrantReadWriteLock类,jdk文档中有CacheData示例
线程间通讯
java中的每个对象都拥有一个线程等待池
通过对象的wait()方法就可以把当前线程挂起到该对象的等待池中
必须通过该对象的notify方法或notifyAll方法才能唤醒该对象等待池的中的线程
生产者消费者问题
生产者线程和消费者线程共用同一个对象的等待池即可,就可以通过这个对象挂起、唤醒
Business bus = new Business();
t1:run{bus.send()}
t2:run{bus.rec()}
t2.setDaemon(true);//t2设为守护线程,
t1.start();
t2.start();
class Business{
private int theValue;
private boolean flag;
public void send(){
synchronized(this){
try{
while(flag) this.wait(); //wait()会释放锁上的钥匙,所以必须和synchronized的锁相同
print: theValue = new Random().nextInt(1000);//生产
flag=true;
this.notify();
}
}
public void rec(){
synchronized(this){
try{
while(true){ while(!flag) this.wait(); }
print;
flag=false;
this.notify();
}catch
}
}
}
七、java网络编程(Socket编程)
Server服务端:ServerSocket,Socket
ServerSocket s = new ServerSocket(9090);//开启服务
print: 开启服务…
Socket socket = s.accept();//阻塞等待
print:接收到客户端请求 + socket.get
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String str = br.readLine();
print: str
客户端:Socket
Socket socket = new Socket(“127.0.0.1”,”9090”);
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String str = br.readLine();
PrintWriter pw = PrinitWriter(socket.getOutputStream());
pw.println(str);
可以群聊、私聊的聊天室
客户端做什么?
1、随时接受服务器端发过来的群聊或私聊数据
2、随时可能从键盘读数据发给服务器端
1,2用2个线程实现,2用主线程实现
Client
main(){
Socket s = new Socket("localhost","9090");
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
PrintWriter pw = PrinitWriter(socket.getOutputStream());
while(true){
String str=br.readLine();
if("exit".equals(str)) break;
pw.println(str);
}
new AcceptData(s).start();
}
class AcceptData extends Thread{
p Socket s;
private BufferedReader br;
public AcceptData(Socket s){
//构造br
}
public void run(){
while(true){
String str = br.readLine();
int index = str.indexOf("/");
if(index==-1){print: 群聊+str}
else{//私聊: ip/内容
String[] ss=str.split("/");
print: ss[0]+说+ss[1];
}
}
}
}
服务端要做什么?
1、开启服务,不停的接收客户端的访问
2、
main{
private HashMap<String, Socket> ss = new ()
addClient(String addr,Socket s){
ss.put(addr,s);
}
Socket findByAddr(String addr){
if(ss.containsKey(addr)) return ss.get(addr);
else return null;
}
ServerSocket ss = new ServerSocket(9090);//开启服务
print: 开启服务...
while(true){
Socket s = s.accept();
String addr = s.getInetAddress().get.. ++UUID
addClient();
启动线程
}
}
class ServerThread extends Thread{
p Socket s;
private BufferedReader br;
public ServerThread(Socket s){
//构造br
}
public void run(){
while(true){
String str = br.readLine();
if(str==null || "".equals(str))
continue;
int index = str.indexOf("/");
if(index==-1){//群聊
for(String add:ss.keySet()){//遍历socket
}
}else{
String[] ss=str.split("/");
String msg = add+"/"ss[1];
clients = findByAddr(ss[0]);//目的socket
写入clients
}
}
}
}
Socket深入、扩展
RPC
RPC框架(Thrift, dubbo)
微服务:dubbo+扩展,springcloud
Socket涉及很多问题:
1、IO模型:
同步阻塞IO
同步非阻塞IO
多路复用IO
异步IO
2、线程模型:开线程太多消耗cpu
3、数据协议
netty框架可解决这些问题
Socket学习路线:
Socket通信
Socket通信优化:IO模型,线程模型,数据协议
看netty框架
看RPC框架
看微服务
八、JDBC
jdbc只提供接口,隔离差异,统一行为
具体实现交给各数据库厂商,驱动类
基本的jdbc操作过程
class.forName(“com.mysql.jdbc.Driver”);
Connection conn = DriverManager.getConnection()
PreparedStatement pstmt = conn.prepareStatement(sql);
数据库连接封装
要求:
保证同一线程使用同一个连接,Map<Thread,Connection>
,ThreadLocal
实现
driverClass,url,name,pass支持可配置,使用properties文件,key=value
private static ThreadLocal<Connection> tl = new()
private static Properties prop = new Properties();
static{
try{
prop.load(JdbcUtil.class.getResourceAsStream("path to prop")); }
Connection conn = DriverManager.getConnection(prop.get)
}
}
表的操作封装
写一个类包装对表的操作
把表和类对应,把列和属性对应
实体类
public class Account{
private int id;
private String name;
private int count;
//构造函数
///所有getter,setter方法
}
Dao类
public class AccountDao{
public Account findById(String id){
//查询,包装成一个对象Account
}
public int save(Account a){
//insert入表
}
public List<Account> findAll(){
//查询表中全部数据,包装成对象,放入集合中返回
}
}
抽取通用的DAO操作
再封装,可针对任何表的主键查询
public interface Dao<T>{
public T findById(String id,String sql, RowMapper<T> rm);
public List<T> find(String sql, RowMapper<T> rm, Object ... obj);
int update(String sql, Object ... obj);
}
public class DaoSupport<T> implements Dao<T>{
findById(...){
T t = rm.getRow(rs);
return t;
}
}
public interface RowMapper<T>{
T getRow(Result rs);
}
以上使用了策略模式
页面信息
location:
protocol
: host
: hostname
: origin
: pathname
: href
: document:
referrer
: navigator:
platform
: userAgent
: