其他分享
首页 > 其他分享> > 串讲补充

串讲补充

作者:互联网

串讲补充

单例模式

单例模式的写法

请参考单例模式,内容包括单例的概念、用途、实现方式、如何防止被序列化破坏等。

单例的实际应用

数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。数据库软件系统中使用数据库连接池,主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的,因此用单例模式来维护,就可以大大降低这种损耗。

工厂模式

工厂模式分为3种,我们这里以简单工厂模式来讲解,大家知道这个就行了:

155764447104263

举例如下:(我们举一个发送邮件和短信的例子)
首先,创建二者的共同接口:

public interface Sender {
    public void Send();
}

其次,创建实现类:

public class MailSender implements Sender {
    @Override
    public void Send() {
        System.out.println("this is mailsender!");
    }
}
public class SmsSender implements Sender {
    @Override
    public void Send() {
        System.out.println("this is sms sender!");
    }
}

最后,建工厂类:

public class SendFactory {
    public Sender produce(String type) {
        if ("mail".equals(type)) {
            return new MailSender();
        } else if ("sms".equals(type)) {
            return new SmsSender();
        } else {
            System.out.println("请输入正确的类型!");
            return null;
        }
    }
}

我们来测试下:

public class FactoryTest {
    public static void main(String[] args) {
        SendFactory factory = new SendFactory();
        Sender sender = factory.produce("sms");
        sender.Send();
    }
}

输出:this is sms sender!

枚举

枚举作用

请参考 枚举作用

枚举实现

Java SE5提供了一种新的类型-Java的枚举类型,关键字enum可以将一组具名的值的有限集合创建为一种新的类型,而这些具名的值可以作为常规的程序组件使用,这是一种非常有用的功能。

要想看源码,首先得有一个类吧,那么枚举类型到底是什么类呢?是enum吗?答案很明显不是,enum就和class一样,只是一个关键字,他并不是一个类,那么枚举是由什么类维护的呢,我们简单的写一个枚举:

public enum t {
    SPRING,SUMMER;
}

然后我们使用反编译,看看这段代码到底是怎么实现的,反编译后代码内容如下:

public final class T extends Enum
{
    private T(String s, int i)
    {
        super(s, i);
    }
    public static T[] values()
    {
        T at[];
        int i;
        T at1[];
        System.arraycopy(at = ENUM$VALUES, 0, at1 = new T[i = at.length], 0, i);
        return at1;
    }
    public static T valueOf(String s)
    {
        return (T)Enum.valueOf(demo/T, s);
    }
    public static final T SPRING;
    public static final T SUMMER;
    private static final T ENUM$VALUES[];
    static
    {
        SPRING = new T("SPRING", 0);
        SUMMER = new T("SUMMER", 1);
        ENUM$VALUES = (new T[] {
            SPRING, SUMMER
        });
    }
}

通过反编译后代码我们可以看到,public final class T extends Enum,说明,该类是继承了Enum类的,同时final关键字告诉我们,这个类也是不能被继承的。

当我们使用enum来定义一个枚举类型的时候,编译器会自动帮我们创建一个final类型的类继承Enum类,所以枚举类型不能被继承。

枚举与单例

Joshua Bloch大神在《Effective Java》中明确表达过的观点:

使用枚举实现单例的方法虽然还没有广泛采用,但是单元素的枚举类型已经成为实现Singleton的最佳方法。

如果你真的深入理解了单例的用法以及一些可能存在的坑的话,那么你也许也能得到相同的结论,那就是:使用枚举实现单例是一种很好的方法。

枚举的序列化

我们知道,以前的所有的单例模式都有一个比较大的问题,就是一旦实现了Serializable接口之后,就不再是单例得了,因为,每次调用 readObject()方法返回的都是一个新创建出来的对象,有一种解决办法就是使用readResolve()方法来避免此事发生。但是,为了保证枚举类型像Java规范中所说的那样,每一个枚举类型极其定义的枚举变量在JVM中都是唯一的,在枚举类型的序列化和反序列化上,Java做了特殊的规定。原文如下:

Enum constants are serialized differently than ordinary serializable or externalizable objects. The serialized form of an enum constant consists solely of its name; field values of the constant are not present in the form. To serialize an enum constant, ObjectOutputStream writes the value returned by the enum constant’s name method. To deserialize an enum constant, ObjectInputStream reads the constant name from the stream; the deserialized constant is then obtained by calling the java.lang.Enum.valueOf method, passing the constant’s enum type along with the received constant name as arguments. Like other serializable or externalizable objects, enum constants can function as the targets of back references appearing subsequently in the serialization stream. The process by which enum constants are serialized cannot be customized: any class-specific writeObject, readObject, readObjectNoData, writeReplace, and readResolve methods defined by enum types are ignored during serialization and deserialization. Similarly, any serialPersistentFields or serialVersionUID field declarations are also ignored—all enum types have a fixedserialVersionUID of 0L. Documenting serializable fields and data for enum types is unnecessary, since there is no variation in the type of data sent.

大概意思就是说,在序列化的时候Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过java.lang.Enum的valueOf方法来根据名字查找枚举对象。同时,编译器是不允许任何对这种序列化机制的定制的,因此禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法。 我们看一下这个valueOf方法:

	public static <T extends Enum<T>> T valueOf(Class<T> enumType,String name) {  
            T result = enumType.enumConstantDirectory().get(name);  
            if (result != null)  
                return result;  
            if (name == null)  
                throw new NullPointerException("Name is null");  
            throw new IllegalArgumentException(  
                "No enum const " + enumType +"." + name);  
     }	

从代码中可以看到,代码会尝试从调用enumType这个Class对象的enumConstantDirectory()方法返回的map中获取名字为name的枚举对象,如果不存在就会抛出异常。再进一步跟到enumConstantDirectory()方法,就会发现到最后会以反射的方式调用enumType这个类型的values()静态方法,也就是上面我们看到的编译器为我们创建的那个方法,然后用返回结果填充enumType这个Class对象中的enumConstantDirectory属性。

所以,JVM对序列化有保证。

枚举的线程安全

当一个Java类第一次被真正使用到的时候静态资源被初始化、Java类的加载和初始化过程都是线程安全的。所以,创建一个enum类型是线程安全的

字节流和字符流有什么区别,如何相互转换呢?

项目的环境为UTF-8,需要读取文件step4_a.txt(编码为GBK)的内容,中文如何正确处理呢

这道题主要考核流里面要带编码,具体如下:
【答案解析】
这道题主要是考核字符流或者字节流,因为涉及编码转换,输入流要有编码,核心代码如下,请参看:
字节流方式

    File file = new File(path);  
    BufferedReader reader = new BufferedReader(new InputStreamReader(  
                    new FileInputStream(file), "GBK"));  
    String line = null;  
    String content = null;
    while ((line = reader.readLine()) != null) { 
        System.out.println(line);
        content += line + "\n"; //\n表示换行符
    }
    reader.close();

字符流读取文件

    InputStreamReader inrr = new InputStreamReader(new FileInputStream(file),"UTF-8");
    char bb [] = new char [(int) file.length()];
    int count =0;
    int temp=0;
    while((temp=inrr.read())!=(-1)){
      bb[count++]=(char)temp;
    }
    inrr.close();
    content = new String(bb);
    System.out.println("char方式二全部读取:\r\n"+content);

什么是序列化和反序列化?

序列化是将对象转换为可传输格式的过程。 是一种数据的持久化手段。一般广泛应用于网络传输,RMI和RPC等场景中。

反序列化是序列化的逆操作。

序列化是将对象的状态信息转换为可存储或传输的形式的过程。一般是以字节码或XML格式传输。而字节码或XML编码格式可以还原为完全相等的对象。这个相反的过程称为反序列化。

常见的序列化方式?

Java原生序列化

Java类通过实现Serializable接口来实现该类对象的序列化,这个接口非常特殊,没有任何方法,只起标识作用.Java序列化保留了对象类的元数据(如类、成员变量、继承类信息等),以及对象数据等,兼容性最好,但不支持跨语言,而且性能一般。

实现Serializable接口的类建议设置serialVersionUID字段值,如果不设置,那么每次运行时,编译器会根据类的内部实现,包括类名、接口名、方法和属性等来自动生成serialVersionUID。如果类的源代码有修改,那么重新编译后serial VersionUID的取值可能会发生变化。因此实现Serializable接口的类一定要显式地定义serialVersionUID属性值。修改类时需要根据兼容性决定是否修改serialVersionUID值:
1.如果是兼容升级,请不要修改serialVersionUID字段,避免反序列化失败。

2.如果是不兼容升级,需要修改serialVersionUID值,避免反序列化混乱。

使用Java原生序列化需注意,Java反序列化时不会调用类的无参构造方法,而是调用native方法将成员变量赋值为对应类型的初始值。基于性能及兼容性考虑,不推荐使用Java 原生序列化。

157535616348936

Hessian 序列化

Hessian 序列化是一种支持动态类型、跨语言、基于对象
传输的网络协议。 Java 对象序列化的二进制流可以被其他语言 ( 如 C++、 Python )反序列化。 Hessian 协议具有如下特性:
自描述序列化类型。不依赖外部描述文件或接口定义 , 用一个字节表示常用基础类型 , 极大缩短二进制流。

  • 自描述序列化类型。不依赖外部描述文件或接口定义 , 用一个字节表示常用基础类型 , 极大缩短二进制流。
  • 语言无关,支持脚本语言。
  • 协议简单,比 Java 原生序列化高效。

相比 Hessian 1.0, Hess ian 2.0 中增加了压缩编码,其序列化二进制流大小是 Java序列化的 50% , 序列化耗时是 Java 序列化的 30% ,反序列化耗时是 Java 反序列化的20% 。
Hessian 会把复杂对象所有属性存储在一个 Map 申 进行序列化。所以在父类、子类存在同名成员变量的情况下, Hessian 序列化时,先序列化子类 ,然后序列化父类,因此反序列化结果会导致子类同名成员变量被父类的值覆盖。

157535611710832

Json序列化

JSON ( JavaScript O同 ect Notation )是一种轻量级的数据交
换格式。 JSON 序列化就是将数据对象转换为 JSON 字符串。在序列化过程中抛弃了类型信息,所以反序列化时只有提供类型信息才能准确地反序列化。相比前两种方式,JSON 可读性比较好,方便调试。

IO模型

IO

什么是IO? 它是指计算机与外部世界或者一个程序与计算机的其余部分的之间的接口。它对于任何计算机系统都非常关键,因而所有 I/O 的主体实际上是内置在操作系统中的。单独的程序一般是让系统为它们完成大部分的工作。

在 Java 编程中,直到最近一直使用 流 的方式完成 I/O。所有 I/O 都被视为单个的字节的移动,通过一个称为 Stream 的对象一次移动一个字节。流 I/O 用于与外部世界接触。它也在内部使用,用于将对象转换为字节,然后再转换回对象。

BIO

Java BIO即Block I/O , 同步并阻塞的IO。

BIO就是传统的java.io包下面的代码实现。

NIO

什么是NIO? NIO 与原来的 I/O 有同样的作用和目的, 他们之间最重要的区别是数据打包和传输的方式。原来的 I/O 以流的方式处理数据,而 NIO 以块的方式处理数据。

面向流 的 I/O 系统一次一个字节地处理数据。一个输入流产生一个字节的数据,一个输出流消费一个字节的数据。为流式数据创建过滤器非常容易。链接几个过滤器,以便每个过滤器只负责单个复杂处理机制的一部分,这样也是相对简单的。不利的一面是,面向流的 I/O 通常相当慢。

一个 面向块 的 I/O 系统以块的形式处理数据。每一个操作都在一步中产生或者消费一个数据块。按块处理数据比按(流式的)字节处理数据要快得多。但是面向块的 I/O 缺少一些面向流的 I/O 所具有的优雅性和简单性。

AIO

Java AIO即Async非阻塞,是异步非阻塞的IO。

区别和联系

BIO (Blocking I/O):同步阻塞I/O模式,数据的读取写入必须阻塞在一个线程内等待其完成。这里假设一个烧开水的场景,有一排水壶在烧开水,BIO的工作模式就是, 叫一个线程停留在一个水壶那,直到这个水壶烧开,才去处理下一个水壶。但是实际上线程在等待水壶烧开的时间段什么都没有做。

NIO (New I/O):同时支持阻塞与非阻塞模式,但这里我们以其同步非阻塞I/O模式来说明,那么什么叫做同步非阻塞?如果还拿烧开水来说,NIO的做法是叫一个线程不断的轮询每个水壶的状态,看看是否有水壶的状态发生了改变,从而进行下一步的操作。

AIO ( Asynchronous I/O):异步非阻塞I/O模型。异步非阻塞与同步非阻塞的区别在哪里?异步非阻塞无需一个线程去轮询所有IO操作的状态改变,在相应的状态改变后,系统会通知对应的线程来处理。对应到烧开水中就是,为每个水壶上面装了一个开关,水烧开之后,水壶会自动通知我水烧开了。

各自适用的场景

BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。

NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。

AIO方式适用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。

使用方式

测试环境,代码问题快速解决方法

1.可以用catalina.bat jpda start直接用debug模式启动tomcat,在tomcat的后台可以看到tomcat已经在8000端口进行监听。

157535597764913

2.然后用Eclipse进行远程调试即可

157535596674688

【备注】可以参照这个附件报里面的内容自己操作一遍,实际我们也会演示一下
项目和如何远程调试可以参照这个地址://down.bimowo.com/kejian/java1/sxxm/tsxg.zip

如果连接不上远程tomcat该如何判断问题出在什么地方呢:
一般是未能正确debug打开,要不就是本地有防火墙,不能通过端口访问
首先在本地判断debug端口是否打开 telnet 127.0.0.1 8000
然后首先ping 远程ip看看服务器能ping通么,然后再telnet 远程ip 端口看看能调试端口通么,一般到这步就没有问题了

Java生产环境下问题排查

在生产环境中,我们无法通过断点调试、新增log、可视化工具去立马查看当前的运行状态和拿到错误信息,此时,借助Java自带的命令行工具以及相关dump分析工具以及一些小技巧,可以大大提升我们排查问题的效率

泛型

泛型是什么,简单举例

泛型,即“参数化类型”,简单来说泛型就是函数的参数类型可以变化。

接口、类和方法也都可以使用泛型定义以及相应的使用。在具体使用时,可以分为泛型接口、泛型类和泛型方法,例如:
class Box { private T data; //get和set方法}
由于接收来自外部使用时候传入的类型实参。那么对于不同传入的类型实参,生成的相应对象实例的类型是不是一样的呢?参照如下范例:

	public static void main(String[] args) {
        // TODO Auto-generated method stub
        Box<String> name = new Box<String>("corn");
        Box<Integer> age = new Box<Integer>(712);
        System.out.println("name class:" + name.getClass());      // com.bimowu.showstudy.java3.Step3fan1$Box
        System.out.println("age class:" + age.getClass());        // com.bimowu.showstudy.java3.Step3fan1$Box
        System.out.println(name.getClass() == age.getClass());    // true
    }
    static class Box<T> { 
        private T data; 
         public Box() {
         }
         public Box(T data) {
             this.data = data;
         }
         public T getData() {
            return data;
        }
    }

输出为:

name class:class com.bimowu.showstudy.java3.Step3Fan1$Box
age class:class com.bimowu.showstudy.java3.Step3Fan1$Box
true

对此总结成一句话:泛型类型在逻辑上看以看成是多个不同的类型,实际上都是相同的基本类型。
究其原因,在于Java中的泛型这一概念提出的目的,导致其只是作用于代码编译阶段,在编译过程中,对于正确检验泛型结果后,会将泛型的相关信息擦出,也就是说,成功编译过后的class文件中是不包含任何泛型信息的。泛型信息不会进入到运行时阶段。

泛型举例

  1. 限定通配符,指的泛型类型必须用限定内的类型来初始化,否则会导致编译错误。
    1. 通配符上限,格式为<? extends T>,即类型必须为T类型或者T子类
    2. 通配符上限,格式为:<? super T>,即类型必须为T类型或者T的父类
  2. 非限定通配符,表示可以用任意类型来替代。例如类型为<?>

面试题:List<? extends T>和List <? super T>之间有什么区别 ?

回答:这两个List的声明都是 限定通配符的例子,List<? extends T>可以接受任何继承自T的类型的List,而List<? super T>可以接受任何T的父类构成的List。例如List<? extends Number>可以接受List或List

反射

反射场景,反射作用

标签:Java,串讲,补充,枚举,线程,new,序列化,public
来源: https://www.cnblogs.com/faetbwac/p/16549032.html