1、简单易学、有丰富的类库
2、面向对象(Java 最重要的特性,让程序耦合度更低,内聚性更高)
3、与平台无关性(JVM 是 Java 跨平台使用的根本)
4、可靠安全
5、支持多线程
面向过程:是分析解决问题的步骤,然后用函数把这些步骤一步一步地实现,然后在使用的时候一一调用则可。性能较高,所以单片机、嵌入式开发等一般采用面向过程开发。
面向对象:是把构成问题的事务分解成各个对象,而建立对象的目的也不是为了完成一个个步骤,而是为了描述某个事物在解决整个问题的过程中所发生的行为。面向对象具有抽象、封装、继承、多态的特性,易维护、易复用、易扩展,可以设计出低耦合的系统。但是性能上来说,比面向过程要低。
对象有以下特点:
面向对象的特性:
Java 中 8 种基础的数据类型:byte、short、char、int、long、float、double、boolean
但是 String 类型却是最常用到的引用类型。
基本类型都有对应的包装类型,基本类型与其对应的包装类型之间的赋值使用自动装箱与拆箱完成。
Integer x = 2; // 装箱 调用了 Integer.valueOf(2) int y = x; // 拆箱 调用了 X.intValue()
基本类型对应的缓冲池如下:
在使用这些基本类型对应的包装类型时,如果该数值范围在缓冲池范围内,就可以直接使用缓冲池中的对象。
String 被声明为 final,因此它不可被继承。(Integer 等包装类也不能被继承)
在 Java 8 中,String 内部使用 char 数组存储数据。
public final class String implements java.io.Serializable, Comparable<String>, CharSequence { /** The value is used for character storage. */ private final char value[]; }
在 Java 9 之后,String 类的实现改用 byte 数组存储字符串,同时使用 coder 来标识使用了哪种编码。
public final class String implements java.io.Serializable, Comparable<String>, CharSequence { /** The value is used for character storage. */ private final byte[] value; /** The identifier of the encoding used to encode the bytes in {@code value}. */ private final byte coder; }
value 数组被声明为 final,这意味着 value 数组初始化之后就不能再引用其它数组。并且 String 内部没有改变 value 数组的方法,因此可以保证 String 不可变。
1.可变性
String 不可变
StringBuffer 和 StringBuilder 可变
2.线程安全
String 不可变,因此是线程安全的
StringBuilder 不是线程安全的
StringBuffer 是线程安全的,内部使用 synchronized 进行同步
字符串常量池(String Pool)保存着所有字符串字面量(literal strings),这些字面量在编译时期就确定。不仅如此,还可以使用 String 的 intern() 方法在运行过程将字符串添加到 String Pool 中。
在 Java 7 之前,String Pool 被放在运行时常量池中,它属于永久代。而在 Java 7,String Pool 被移到堆中。这是因为永久代的空间有限,在大量使用字符串的场景下会导致 OutOfMemoryError 错误。
使用这种方式一共会创建两个字符串对象(前提是 String Pool 中还没有 “abc” 字符串对象)。
“abc” 属于字符串字面量,因此编译时期会在 String Pool 中创建一个字符串对象,指向这个 “abc” 字符串字面量;
而使用 new 的方式会在堆中创建一个字符串对象。
1个
编译器会优化,相当于直接定义一个 “abcd” 的字符串。
运行结果: -1
JDK 中的 java.lang.Math 类
接口是抽象类的延伸,在 Java8 之前,它可以看成是一个完全抽象的类,也就是说它不能有任何的方法实现。
使用接口:
需要让不相关的类都实现一个方法,例如不相关的类都可以实现 Comparable 接口中的 compareTo() 方法;
需要使用多重继承。
使用抽象类:
需要在几个相关的类中共享代码。
需要能控制继承来的成员的访问权限,而不是都为 public。
需要继承非静态和非常量字段。
在很多情况下,接口优先于抽象类。因为接口没有抽象类严格的类层次结构要求,可以灵活地为一个类添加行为。并且从 Java 8 开始,接口也可以有默认的方法实现,使得修改接口的成本也变的很低。
不一定。如
public abstract class TestAbstractClass { public static void notAbstractMethod() { System.out.println("I am not a abstract method."); } }
不一定。同时反过来 equals() 为true,hashCode() 也不一定相同。
关于 hashCode() 和 equals() 方法是有一些常规协定:
复制一个 Java 对象
浅拷贝实现:
对于 try 和 finally 至少一个语句块包含 return 语句的情况:
JDK 中 java.lang.Class
类,就是为了实现反射提供的核心类之一。
一个 JVM 中一种 Class 只会被加载一次。
Class 和 java.lang.reflect 一起对反射提供了支持,java.lang.reflect 类库主要包含了以下三个类:
优点:
可扩展性 :应用程序可以利用全限定名创建可扩展对象的实例,来使用来自外部的用户自定义类。
类浏览器和可视化开发环境 :一个类浏览器需要可以枚举类的成员。可视化开发环境(如 IDE)可以从利用反射中可用的类型信息中受益,以帮助程序员编写正确的代码。
调试器和测试工具 : 调试器需要能够检查一个类里的私有成员。测试工具可以利用反射来自动地调用类里定义的可被发现的 API 定义,以确保一组测试中有较高的代码覆盖率。
缺点:
尽管反射非常强大,但也不能滥用。如果一个功能可以不用反射完成,那么最好就不用。在我们使用反射技术时,下面几条内容应该牢记于心。
性能开销 :反射涉及了动态类型的解析,所以 JVM 无法对这些代码进行优化。因此,反射操作的效率要比那些非反射操作低得多。我们应该避免在经常被执行的代码或对性能要求很高的程序中使用反射。
安全限制 :使用反射技术要求程序必须在一个没有安全限制的环境中运行。如果一个程序必须在有安全限制的环境中运行,如 Applet,那么这就是个问题了。
内部暴露 :由于反射允许代码执行一些在正常情况下不被允许的操作(比如访问私有的属性和方法),所以使用反射可能会导致意料之外的副作用,这可能导致代码功能失调并破坏可移植性。反射代码破坏了抽象性,因此当平台发生改变的时候,代码的行为就有可能也随着变化。
字节流的操作不会经过缓冲区(内存)而是直接操作文本本身的,而字符流的操作会先经过缓冲区(内存)然后通过缓冲区再操作文件。
缓冲区:
a.缓冲区就是一段特殊的内存区域,很多情况下当程序需要频繁地操作一个资源(如文件或数据库)则性能会很低,所以为了提升性能就可以将一部分数据暂时读写到缓存区,以后直接从此区域中读写数据即可,这样就显著提升了性。
b.对于 Java 字符流的操作都是在缓冲区操作的,所以如果我们想在字符流操作中主动将缓冲区刷新到文件则可以使用 flush() 方法操作。
选择:
a.大多数情况下使用字节流会更好,因为大多数时候 IO 操作都是直接操作磁盘文件,所以这些流在传输时都是以字节的方式进行的
b.如果对于操作需要通过 IO 在内存中频繁处理字符串的情况使用字符流会好些,因为字符流具备缓冲区,提高了性能
序列化就是一种用来处理对象流的机制,将对象的内容进行流化。可以对流化后的对象进行读写操作,可以将流化后的对象传输于网络之间。序列化是为了解决在对象流读写操作时所引发的问题
将需要被序列化的类实现 Serialize 接口,没有需要实现的方法,此接口只是为了标注对象可被序列化的,然后使用一个输出流(如:FileOutputStream)来构造一 个ObjectOutputStream(对象流)对象,再使用 ObjectOutputStream 对象的 write(Object obj) 方法就可以将参数 obj 的对象写出。
Linux中一切类型都被抽象成文件,如:普通文件、目录、字符设备、块设备、套接字等。
文件描述符是内核创建的方便管理已打开文件的索引,指代被打开的文件。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。
所有执行I/O操作的系统调用都通过文件描述符(fd)。在Linux系统中,ssh 方式登录后查看 /proc下信息,可以看到系统为每一个进程默认创建0,1,2 三个 fd,0表示标准输入,1表示标准输出,2表示错误输出。$$ 表示当前进程 ID。
[root@VM-0-16-centos ~]# cd /proc/$$/fd [root@VM-0-16-centos fd]# ll total 0 lrwx------ 1 root root 64 Apr 24 09:01 0 -> /dev/pts/0 lrwx------ 1 root root 64 Apr 24 09:01 1 -> /dev/pts/0 lrwx------ 1 root root 64 Apr 24 09:01 2 -> /dev/pts/0 lrwx------ 1 root root 64 Apr 24 09:01 255 -> /dev/pts/0 [root@VM-0-16-centos fd]#
为了限制不同的程序之间的访问能力, 防止他们获取别的程序的内存数据, 或者获取外围设备的数据, 并发送到网络, 内存被划分为用户态和内核态。内核
内核态:CPU 可以访问内存所有数据,包括外围设备,例如硬盘、网卡等,CPU 也可以将自己从一个程序切换到另一个程序。
用户态:只能受限的访问内存,且不允许访问外围设备,必须通过系统提供的 syscall 方式调用系统函数。占用 CPU 的能力被剥夺,CPU 资源可以被其他程序获取。
举例磁盘和 socket 的 IO 操作:
IO 对文件拷贝操作:硬盘 -->内核空间 -->用户线程空间 -->内核空间 -->硬盘
IO 对 socket 操作: scoket -->内核空间 -->用户线程空间 -->内核空间 -->socket
参考 https://zhuanlan.zhihu.com/p/148673095
传统的 BIO 处理多个客户端的请求或在客户端对多个服务端进行通讯,就必须使用多线程来处理。每一个请求都需要分配一个线程来处理,当有大量请求时线程的上下文切换和内存占用对系统来说都是很大的负担。BIO 的阻塞体现在两个地方:
NIO 需要系统内核支持,而且在不同系统如 Linux 和 Windows 下的实现方式时不一样的。
Java NIO 包为我们提供了一个 selector(多路复用器),然后我们把需要检查的 socket 注册到这个 selector 中,主线程堵塞在 selector 的 select 方法里面。当选择器发现某个 socket 就绪了,它就会唤醒主线程,通过 selector 获取到就绪状态的socket来进行相应的处理。
NIO selector 调用 natice 方法是调用操作系统的系统函数,即 kernel#select 函数,每次调用都涉及用户态/内核态的切换,传递的是 socket 集合,即文件描述符 fd,根据 fd 集合检查 socket 状态,就绪态直接返回,没有就堵塞直到有数据过来。
当进行读写操作时,只须直接调用API的read或write方法即可。这两种方法均为异步的,对于读操作而言,当有流可读取时,操作系统会将可读的流传入read方法的缓冲区,并通知应用程序;对于写操作而言,当操作系统将write方法传递的流写入完毕时,操作系统主动通知应用程序。 即可以理解为,read/write方法都是异步的,完成后会主动调用回调函数。
参考 https://blog.csdn.net/joyblur/article/details/108402364
单抽象方法接口
中间操作:
终端操作: