当前位置 : 首页 » 文章分类 :  开发  »  Java编码基础培训笔记

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 = new 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);
}

以上使用了策略模式


上一篇 Hexo博客(12)使用google-code-prettify代码高亮

下一篇 Hexo博客(11)添加网易云跟帖评论系统