红黑树:红黑树是一种自平衡的二叉查找树,是一种高效的查找树。比如 Java 中的 TreeMap,JDK 1.8 中的 HashMap。
红黑树通过如下的性质定义实现自平衡:
1节点是红色或黑色。
2根是黑色。
3每个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!]
4 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点。)
5从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点(简称黑高)。
6节点左边的值一定比节点值小,右边节点的值一定比右边节点值大。
集合(比较)
ArrayList 源码分析
List list=new ArrayList();
list.add("ccc");
list.add(null);
list.add("ccc");
System.out.println(list);//[ccc, null, ccc]
根据源码可知arraylist()默认的容量大小为10,有无参构造,和有参数构造,无参构造为可变长数组,
public boolean add(E e) {//将制定元素添加到末尾
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
/** 在元素序列 index 位置处插入 */
public void add(int index, E element) {
rangeCheckForAdd(index);
// 1. 检测是否需要扩容
ensureCapacityInternal(size + 1); // Increments modCount!!
// 2. 将 index 及其之后的所有元素都向后移一位
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
// 3. 将新元素插入至 index 处
elementData[index] = element;
size++;
}
只能删除指定位置的元素或删除指定元素,源码也是直接找到位置,然后向前移动一位,或者遍历找到值,然后
向前移动一位,arraylist的随机性比较强,我们不推荐使用foreach进行遍历。
ArrayList 迭代器中的方法都是均具有快速失败的特性,当遇到并发修改的情况时,迭代器会快速失败,
抛出ConcurrentModificationException异常以避免程序在将来不确定的时间里出现不确定的行为。
三种构造函数
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
public ArrayList(Collection<? extends E> c) {//接收一个Collection集合
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
LinkList源码分析
LinkedList 其底层采用的双向链表结构。和 ArrayList 一样,LinkedList 也支持空值和重复值。
由于 LinkedList 基于链表实现,存储元素过程中,无需像 ArrayList 那样进行扩容。但有得必有失,
LinkedList 存储元素的节点需要额外的空间存储前驱和后继的引用。
另一方面,LinkedList 在链表头部和尾部插入效率比较高,但在指定位置进行插入时,效率一般。
原因是,在指定位置插入需要定位到该位置处的节点,此操作的时间复杂度为O(N)。最后,LinkedList 是非线
程安全的集合类,并发环境下,多个线程同时操作 LinkedList,会引发不可预知的错误。
LinkedList linkedList=new LinkedList();
linkedList.add("cc");
linkedList.add("cc");
linkedList.add(null);
System.out.println(linkedList);//[cc, cc, null]
//查找linklist()是链表查找,无法像Arraylist()直接访问指定位置。
hashmap
HashMap 允许 null 键和 null 值,在计算哈键的哈希值时,null 键哈希值为 0。HashMap 并不保证键值对
的顺序,这意味着在进行某些操作后,键值对的顺序可能会发生变化。
TreeMap源码分析
public class TreeMap<K,V>
extends AbstractMap<K,V>
implements NavigableMap<K,V>, Cloneable, java.io.Serializable
public interface NavigableMap<K,V> extends SortedMap<K,V>
public abstract class AbstractMap<K,V> implements Map<K,V>
---------------------------------
Map map=new TreeMap();
map.put("n","n");
map.put("a","a");
map.put("c","c");
System.out.println(map);//{a=a, c=c, n=n}
-----------------------------------------------------------------
//对于treeMap的遍历
Collection values = tm.values();
ArrayList<Double> arrayList = new ArrayList<>(values);
for(double d:arrayList){
System.out.println(d);//12345.0 11.0
}
Set<String> set = tm.keySet();
Iterator<String> iterator = set.iterator();
while (iterator.hasNext()){
String next = iterator.next();
System.out.println("String"+next);
}
Set set1 = tm.entrySet();
System.out.println(values);
Iterator iterator1 = set1.iterator();
while(iterator1.hasNext()){
Map.Entry me = (Map.Entry)iterator1.next();
System.out.print(me.getKey() + ": ");
System.out.println(me.getValue());
}
LinkedHashMap 源码详细分析
LinkedHashMap 继承自 HashMap,在 HashMap 基础上,通过维护一条双向链表,解决了 HashMap
不能随时保持遍历顺序和插入顺序一致的问题。
LinkedHashMap按插入的顺序遍历
Map map=new LinkedHashMap();
map.put("n","n");
map.put("a","a");
map.put("c","c");
System.out.println(map);//{n=aa, a=dff, c=ss}
ConcurrentHashMap
HashMap是我们用得非常频繁的一个集合,但是由于它是非线程安全的,在多线程环境下,put操作是有可能产生死循环的,
导致CPU利用率接近100%。为了解决该问题,提供了Hashtable和Collections.synchronizedMap(hashMap)两种解决方案,
但是这两种方案都是对读写加锁,独占式,
一个线程在读时其他线程必须等待,吞吐量较低,性能较为低下。
故而Doug Lea大神给我们提供了高性能的线程安全HashMap:ConcurrentHashMap。
// 最大容量:2^30=1073741824
private static final int MAXIMUM_CAPACITY = 1 << 30;
// 默认初始值,必须是2的幕数
private static final int DEFAULT_CAPACITY = 16;
Class对象
Class类被创建后的对象就是Class对象.实际上在Java中每个类都有一个Class对象,每当我们编写并且编译一个新创建的类就会产生一个对应Class对象并且这个Class对象会被保存在同名.class文件里(编译后的字节码文件保存的就是Class对象)。那为什么需要这样一个Class对象呢?是这样的,当我们new一个新对象或者引用静态成员变量时,Java虚拟机(JVM)中的类加载器子系统会将对应Class对象加载到JVM中,然后JVM再根据这个类型信息相关的Class对象创建我们需要实例对象或者提供静态变量的引用值。需要特别注意的是,手动编写的每个class类,无论创建多少个实例对象,在JVM中都只有一个Class对象,即在内存中每个类有且只有一个相对应的Class对象。
(1)Class.forName方法可以获得一个Class对象
(2)通过对象实例获得Class对象
Gum gum=new Gum(); Class classc = gum.getClass();
(3)Class字面常量
在Java中存在另一种方式来生成Class对象的引用,它就是Class字面常量,如下:
//字面常量的方式获取Class对象
Class clazz = Gum.class;
没有触发类的初始化
类的加载过程:
编译期常量指的就是程序在编译时就能确定这个常量的具体值,程序在运行时才能确定常量的值,因此也称为运行时常量。
定义上来说,声明为final类型的基本类型或String类型并直接赋值(非运算)的变量就是编译期常量。
编译期常量: final int staticFinal = 47;static final String str = "avb";
运行时常量:final String str1 = new String("abc"); Random rand = new Random(47);
关于类加载的初始化阶段,在虚拟机规范严格规定了有且只有4种场景必须对类进行初始化:
使用new关键字实例化对象时、读取或者设置一个类的静态字段(不包含编译期常量)以及调用静态方法的时候,必
须触发类加载的初始化过程(类加载过程最终阶段)。
使用反射包(java.lang.reflect)的方法对类进行反射调用时,如果类还没有被初始化,则需先进行初始化,这点对反射很重要。
当初始化一个类的时候,如果其父类还没进行初始化则需先触发其父类的初始化。
当Java虚拟机启动时,用户需要指定一个要执行的主类(包含main方法的类),虚拟机会先初始化这个主类
this对象理解:this 表示当前对象的引用
public class Person {
public void aa(){
//表示当前对象实例本身
//比如通过new出 person实例调用这个aa方法,然后this 就是person这个对象
Person p=this;
System.out.println(p);
}
public static void main(String[] args) {
Person person = new Person();
person.aa();//com.ecpss.suanfa.Person@6d6f6e28
System.out.println(person);//com.ecpss.suanfa.Person@6d6f6e28
}
}
理解Java类加载器(ClassLoader)
类加载器的任务是根据一个类的全限定名来读取此类的二进制字节流到JVM中,然后转换为一个与目标类对应的
java.lang.Class对象实例,在虚拟机提供了3种类加载器,引导(Bootstrap)类加载器、扩展(Extension)
类加载器、系统(System)类加载器(也称应用类加载器)
启动(Bootstrap)类加载器
启动类加载器主要加载的是JVM自身需要的类,这个类加载使用C++语言实现的,是虚拟机自身的一部分,它负责
将 <JAVA_HOME>/lib路径下的核心类库或-Xbootclasspath参数指定的路径下的jar包加载到内存中,注意必由
于虚拟机是按照文件名识别加载jar包的,如rt.jar,如果文件名不被虚拟机识别,即使把jar包丢到lib目录下也
是没有作用的(出于安全考虑,Bootstrap启动类加载器只加载包名为java、javax、sun等开头的类)。
扩展(Extension)类加载器
扩展类加载器是指Sun公司(已被Oracle收购)实现的sun.misc.Launcher$ExtClassLoader类,由Java语言实现
的,是Launcher的静态内部类,它负责加载<JAVA_HOME>/lib/ext目录下或者由系统变量-Djava.ext.dir指定
位路径中的类库,开发者可以直接使用标准扩展类加载器。
系统(System)类加载器(AppClassLoader )
也称应用程序加载器是指 Sun公司实现的sun.misc.Launcher$AppClassLoader。它负责加载系统类路径java -
classpath或-D java.class.path 指定路径下的类库,也就是我们经常用到的classpath路径,开发者可以直
接使用系统类加载器,一般情况下该类加载是程序中默认的类加载器,通过
ClassLoader#getSystemClassLoader()方法可以获取到该类加载器。
自定义类加载器
Java虚拟机对class文件采用的是按需加载的方式,也就是说当需要使用该类时才会将它的class文件加载到内存
生成class对象,而且加载某个类的class文件时,Java虚拟机采用的是双亲委派模式即把请求交由父类处理,它
一种任务委派模式。
分析ClassLoader
defineClass()方法是用来将byte字节流解析成JVM能够识别的Class对象(ClassLoader中已实现该方法逻辑)
请记住,如果我们在定义类加载器时选择继承ClassLoader类而非URLClassLoader,必须手动编写
findclass()方法的加载逻辑以及获取字节码流的逻辑。
static class AppClassLoader extends URLClassLoader
static class ExtClassLoader extends URLClassLoader
这两个类都继承自URLClassLoader
ClassLoader是一个抽象类,很多方法是空的没有实现,比如 findClass()、findResource()等。而
URLClassLoader这个实现类为这些方法提供了具体的实现,并新增了URLClassPath类协助取得Class字节码流
等功能,在编写自定义类加载器时,如果没有太过于复杂的需求,可以直接继承URLClassLoader类,这样就可
以避免自己去编写findClass()方法及其获取字节码流的方式,使自定义类加载器编写更加简洁.
defineClass()方法是用来将byte字节流解析成JVM能够识别的Class对象(ClassLoader中已实现该方法逻辑),
通过这个方法不仅能够通过class文件实例化class对象,也可以通过其他方式实例化class对象,如通过网络接收
一个类的字节码,然后转换为byte字节流创建对应的Class对象,defineClass()方法通常与findClass()方法一
起使用,一般情况下,在自定义类加载器时,会直接覆盖ClassLoader的findClass()方法并编写加载规则,取得
要加载类的字节码后转换成流,然后调用defineClass()方法生成类的Class对象
demo:
public class FileLoader extends ClassLoader {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] classData = new byte[0];
try {
classData = getClassData(name);
} catch (Exception e) {
e.printStackTrace();
}
if(classData==null){
throw new ClassNotFoundException(name);
}else {
return defineClass(name,classData,0,classData.length);
}
}
//编写读取字节流的方法
private byte[] getClassData(String className) throws Exception{
InputStream inputStream = getClass().getClassLoader().getResourceAsStream(className);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte b[]=new byte[1024];
int len=0;
while((len=inputStream.read(b))!=-1){
baos.write(b,0,b.length);
}
return baos.toByteArray();
}
public static void main(String[] args) throws Exception{
Class<?> clazz = new FileLoader().loadClass("com.ecpss.ClassLoader.Tclass");
System.out.println(clazz.newInstance().toString());
}
//通过getClassData()方法找到class文件并转换为字节流,并重写findClass()方法,利用defineClass()
方法创建了类的class对象。
public class FileUrlClassLoader extends URLClassLoader {//继承URLClassLoader 更容易
public FileUrlClassLoader(URL[] urls) {
super(urls);
}
public static void main(String[] args) throws Exception{
String rootDir="aa";
File file = new File(rootDir);
URI uri=file.toURI();
URL[] urls={uri.toURL()};
FileUrlClassLoader loader = new FileUrlClassLoader(urls);
Class<?> clazz = loader.loadClass("com.ecpss.ClassLoader.Tclass");
System.out.println(clazz.newInstance().toString());
}
}
类反射技术
反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,
都能够调用它的任意一个方法和属性,这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
在Java中,Class类与java.lang.reflect类库一起对反射技术进行了全力的支持。
Constructor类及其用法:
Constructor类存在于反射包(java.lang.reflect)中,反映的是Class 对象所表示的类的构造方法。获取
Constructor对象是通过Class类中的方法获取的.
//取得指定带int和String参数构造函数,该方法是私有构造private
Constructor cs2=clazz.getDeclaredConstructor(int.class,String.class);
cs2.setAccessible(true);//由于是私有必须设置可以访问
//通过得到构造函数创建实例
User chaoshen = (User) cs2.newInstance(25, "chaoshen");
//获取所有构造函数包含private
Constructor<?>[] constructors = clazz.getDeclaredConstructors();
for(Constructor constructor:constructors){
Class[] types = constructor.getParameterTypes();//获得所有的参数类型
for(Class c:types){
System.out.println(c.getName());
}
}
Field类及其用法:
Field 提供有关类或接口的单个字段的信息,以及对它的动态访问权限。反射的字段可能是一个类(静态)字段
或实例字段。同样的道理,我们可以通过Class类的提供的方法来获取代表字段信息的Field对象.
方法返回值 方法名称 方法说明
Field getDeclaredField(String name) 获取指定name名称的(包含private修饰的)字段,不包括继承的字段
Field[] getDeclaredField() 获取Class对象所表示的类或接口的所有(包含private修饰的)字段,不包括继承的字段
Field getField(String name) 获取指定name名称、具有public修饰的字段,包含继承字段
Field[] getField() 获取修饰符为public的字段,包含继承字段
demo:
Class<?> clazz = Class.forName("com.ecpss.Reflects.Student");//得到Class对象
Student student = (Student) clazz.newInstance();//通过Class对象创建实例
Field desc =clazz.getField("desc");//得到字段
Field score = clazz.getDeclaredField("score");//得到私有字段
score.setAccessible(true);//设置私有字段可以访问
score.set(student,33);//设置值,其中student是创建的对象
int i = (int) score.get(student);//得到student对象score字段的值
Class<?> type = score.getType();//得到score 的类型 int
score.getName();//得到字段的名称
Method类及其用法
Method 提供关于类或接口上单独某个方法(以及如何访问该方法)的信息,所反映的方法可能是类方法或实例方法(包括抽象方法)。
Class clazz=Class.forName("com.ecpss.Reflects.Circle");
Circle circle = (Circle) clazz.newInstance();
Method method = clazz.getMethod("draw",new Class[]{int.class,String.class});//该方法
可以得到父类方法
Method method1=clazz.getDeclaredMethod("drawCircle");//该方法可以得到私有方法,但是不
能得到父类方法
Method method2 = clazz.getMethod("getAllCount");
Method decre = clazz.getMethod("decre", int.class, int.class);
---------------------------------------------------------------------------
Method add = clazz.getMethod("add");
method.invoke(circle,21,"cc");//调用有参方法
----------------------------------------------------------------------------
method1.setAccessible(true);//设置私有可以访问的
method1.invoke(circle);//调用无参方法
----------------------------------------------------------------------------
Integer invoke = (Integer) method2.invoke(circle);//调用有返回值的方法
System.out.println(invoke);
----------------------------------------------------------------------------
add.invoke(null);//调用静态无参方法
----------------------------------------------------------------------------
decre.invoke(null,12,2);//调用静态有参方法
注解
注解是不支持继承的。注解也会被编译成class对象
以test注解为例:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Test {}
@Retention用来约束注解的生命周期,分别有三个值,源码级别(source),类文件级别(class)或者运行时
级别(runtime),其含有如下:
SOURCE:注解将被编译器丢弃(该类型的注解信息只会保留在源码里,源码经过编译后,注解信息会被丢弃,不
会保留在编译好的class文件里)
CLASS:注解在class文件中可用,但会被VM丢弃(该类型的注解信息会保留在源码里和class文件里,在执行的
时候,不会加载到虚拟机中),请注意,当注解未定义Retention值时,默认值是CLASS,如Java内置注解,
@Override、@Deprecated、@SuppressWarnning等
RUNTIME:注解信息将在运行期(JVM)也保留,因此可以通过反射机制读取注解的信息(源码、class文件和执行
的时候都有注解的信息),如SpringMvc中的@Controller、@Autowired、@RequestMapping等。
--------------------------------------------------------------------------------
@Target 用来约束注解可以应用的地方(如方法、类或字段),其中ElementType是枚举类型,其定义如下,也
代表可能的取值范围:
ublic enum ElementType {
/**标明该注解可以用于类、接口(包括注解类型)或enum声明*/
TYPE,
/** 标明该注解可以用于字段(域)声明,包括enum实例 */
FIELD,
/** 标明该注解可以用于方法声明 */
METHOD,
}
--------------------------------------------------------------------------------
@Documented 生成文档
--------------------------------------------------------------------------------
@Inherited 可以让注解被继承,但这并不是真的继承,只是通过使用@Inherited,可以让子类Class对象使
用getAnnotations()获取父类被@Inherited修饰的注解
注解和类反射 用法:
getAnnotation(Class<A> annotationClass),该元素如果存在指定类型的注解,则返回这些注解,否则返回
null。
getAnnotations() 返回此元素上存在的所有注解,包括从父类继承的
isAnnotationPresent(Class<? extends Annotation> annotationClass) 如果指定类型的注解存在于此元
素上,则返回 true,否则返回 false。
getDeclaredAnnotations() 返回直接存在于此元素上的所有注解,注意,不包括父类的注解,调用者可以
随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响,没有则返回长度为0的数组
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD,ElementType.METHOD,ElementType.TYPE})
public @interface ZiDingYi {
String name() default "";//字符类型
//枚举类型
enum Status {FIXED,NORMAL};
//声明枚举
Status status() default Status.FIXED;
Class c() default Integer.class;//class类型
long[] value();//数组类型
}
测试通过Class对象获得标注注解的值
@ZiDingYi(name = "school",value = {0},status = ZiDingYi.Status.FIXED)
public class Tzhujie {
public static void main(String[] args) {
Class<Tzhujie> clazz = Tzhujie.class;
ZiDingYi annotation = clazz.getAnnotation(ZiDingYi.class);//获得指定注解
String name = annotation.name();//获得注解的值
System.out.println(name);
System.out.println(annotation.status());
}
}
枚举
介绍:
枚举类型是Java 5中新增特性的一部分,它是一种特殊的数据类型,之所以特殊是因为它既是一种类(class)类型
却又比类类型多了些特殊的约束,但是这些约束的存在也造就了枚举类型的简洁性、安全性以及便捷性。
这里要注意,值一般是大写的字母,多个值之间以逗号分隔。同时我们应该知道的是枚举类型可以像类(class)类
型一样,定义为一个单独的文件,当然也可以定义在其他类内部
public enum W {//枚举
A,B
}
枚举实现原理:javac 编译后会生成一个class文件,反编译class文件,会发现生成一个类继承Enum,
里面声明的变量实例,都成了final static修饰的类对象实例。
Enum是所有 Java 语言枚举类型的公共基本类(注意Enum是抽象类)方法:
ordinal() 返回枚举常量的序数(它在枚举声明中的位置,其中初始常量序数为零)
compareTo(E o) 比较此枚举与指定对象的顺序。
getDeclaringClass() 返回与此枚举常量的枚举类型相对应的 Class 对象
name() 返回此枚举常量的名称,在其枚举声明中对其进行声明
toString() 返回枚举常量的名称,它包含在声明中
static valueOf(Class<T> enumType, String name) 返回带指定名称的指定枚举类型的枚举常量。
//使用jad.exe(放在jdk 的bin目录下)运行 jad W.class
(打开)
public final class W extends Enum
{
public static W[] values()
{
return (W[])$VALUES.clone();
}
public static W valueOf(String name)
{
return (W)Enum.valueOf(com/ecpss/enum1/W, name);
}
private W(String s, int i)
{
super(s, i);
}
public static final W A;
public static final W B;
private static final W $VALUES[];
static
{
A = new W("A", 0);
B = new W("B", 1);
$VALUES = (new W[] {
A, B
});
}
}
//由于values()方法是由编译器插入到枚举类中的static方法,所以如果我们将枚举实例向上转型为Enum,那
么values()方法将无法被调用,因为Enum类中并没有values()方法
测试:
int i = Day.SATURDAY.ordinal();//5
int i1 = Day.SUNDAY.compareTo(Day.SATURDAY);//相当6-5
Class<Day> clazz = Day.TUESDAY.getDeclaringClass();
String name = Day.SATURDAY.name();
String s = Day.TUESDAY.toString();
Day cc = Day.valueOf(Day.TUESDAY.getDeclaringClass(), Day.FRIDAY.name());
/**
5
1
class com.ecpss.enum1.Day
SATURDAY
TUESDAY
FRIDAY
*/
枚举与Class对象
getEnumConstants()//返回该枚举类型的所有元素,如果Class对象不是枚举类型,则返回null。
Day friday = Day.THURSDAY;
Day[] constants = friday.getDeclaringClass().getEnumConstants();
for(Day d:constants){
System.out.println(d);
}
枚举不能使用继承,是因为编译器会自动为我们继承Enum抽象类而Java只支持单继承。
可以把enum类当成常规类,也就是说我们可以向enum类中添加方法和变量,甚至是main方法
public enum DayT {
MONDAY("星期一"),
TUESDAY("星期二"),
WEDNESDAY("星期三"),
THURSDAY("星期四"),
FRIDAY("星期五"),
SATURDAY("星期六"),
SUNDAY("星期日");//记住要用分号结束
private String desc;
DayT(String desc) {
this.desc = desc;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public static void main(String[] args) {
String desc = DayT.FRIDAY.getDesc();
System.out.println(desc);//星期五
}
反编译:
public final class DayT extends Enum
{
public static DayT[] values()
{
return (DayT[])$VALUES.clone();
}
public static DayT valueOf(String name)
{
return (DayT)Enum.valueOf(com/ecpss/enum1/DayT, name);
}
private DayT(String s, int i, String desc)
{
super(s, i);
this.desc = desc;
}
public static void main(String args1[])
{
}
public static final DayT MONDAY;
public static final DayT TUESDAY;
public static final DayT WEDNESDAY;
public static final DayT THURSDAY;
public static final DayT FRIDAY;
public static final DayT SATURDAY;
public static final DayT SUNDAY;
private String desc;
private static final DayT $VALUES[];
static
{
MONDAY = new DayT("MONDAY", 0, "\u661F\u671F\u4E00");
TUESDAY = new DayT("TUESDAY", 1, "\u661F\u671F\u4E8C");
WEDNESDAY = new DayT("WEDNESDAY", 2, "\u661F\u671F\u4E09");
THURSDAY = new DayT("THURSDAY", 3, "\u661F\u671F\u56DB");
FRIDAY = new DayT("FRIDAY", 4, "\u661F\u671F\u4E94");
SATURDAY = new DayT("SATURDAY", 5, "\u661F\u671F\u516D");
SUNDAY = new DayT("SUNDAY", 6, "\u661F\u671F\u65E5");
$VALUES = (new DayT[] {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
});
}
}
}
覆盖Enum方法
@Override
public String toString() {
return desc;
}
枚举与switch
能用于switch判断的类型有:byte、short、int、char(JDK1.6),还有枚举类型,但是在JDK1.7后添加了对String类型的判断.
switch (color){
case RED:
System.out.println("red");
break;
case BLUE:
System.out.println("blue");
// break;
case YELLOW:
System.out.println("yellow");
// break;
default:
System.out.println("aaa");
}
如果没有匹配就会运行default语句,default并不是必须的,也可以不写。
case语句中少写了break,编译不会报错,但是会一直执行之后所有case条件下的语句而不再判断,直到default
语句
i/o流
计算机内部,所有信息最终都是一个二进制值。每一个二进制位(bit)有0和1两种状态.
ASCII编码
是为了让计算识别英文的26个字母一些特殊字符而设计的。例如A对应的字节就是十进制65对应的二进制0100 0001,
所以所有的英文字符都可以用ascii对应的字节表示,使用 7位表示。
ISO-8859-1编码(其他一些符号不够)
ISO组织在ASCII码基础上又制定了一系列标准用来扩展ASCII编码,它们是ISO-8859-1~ISO-8859-15,其中
ISO-8859-1应用得最广泛。
ISO-8859-1仍然是单字节8位编码,它总共能表示256个字符。ISO-8859-1向下兼容ASCII,其编码范围是0x00-
0xFF,0x00-0x7F之间完全和ASCII一致。
gb2312 (表示中文)
两个字节表示一个汉字
gbk(表示中文中生僻字)
完全兼容gb2312,但是比gb2312表示的字多,因为多了一些生僻字
gb18030
表示汉字最全的(简体中文)
Unicode编码(为了解决全球统一的编码格式)
Unicode是为整合全世界的所有语言文字而诞生的。任何字符在Unicode中都对应一个值。
统一采用了两个字节表示一个字符
使用了Unicode可以不?
编码容量(对于英文国家)存储容量将变大由一个字节变为两个字节
utf-8(unicode translation form)--unicode 转换格式
unicode 编码方式,utf-8 是一种存储方式---是unicode的实现方式之一
UTF-8是一种针对Unicode的可变长度字符编码。
一个utf8英文字母占1个字节
一个utf8数字占1个字节
UTF-8占用3个字节,特别的占用4个字节。
utf-8中使用一到四个字节来表示一个字符,编码规则如下:
1)对于单字节的符号,字节的第一位设为0,后面7位为这个符号的unicode码。因此对于英语字母,UTF-8编码和
ASCII码是相同的。
2)对于n字节的符号(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下
的没有提及的二进制位,全部为这个符号的unicode码。
Unicode | UTF-8 十进制 |
0000 ~007F | 0XXX XXXX 0-127位 |
0080 ~07FF | 110X XXXX 10XX XXXX 128-2047位 |
0800 ~FFFF | 1110XXXX 10XX XXXX 10XX XXXX 2048-65535位 |
1 0000 ~1F FFFF | 1111 0XXX 10XX XXXX 10XX XXXX 10XX XXXX 65536-1114111位 |
举个例子(我们知道一般中文对应的utf-8为三个字节)
‘你’的Unicode(4f60)对应二进制(100 1111 0110 0000),但是根据上表显示早0800-FFFF之内对应utf-8
的格式为1110XXXX 10XX XXXX 10XX XXXX 所以接下来进行填充,从最后一个二进制位开始,依次从后向前填入格式中的x,多出的位补0。这样就得到了,你的UTF-8编码是11100100 10111101 10100000,转换成十六进制就是E6BDA0
java代码测试:
String s="你";
byte[] bs = s.getBytes("utf8");
for(byte b:bs){
System.out.print(b+" ");//-28 -67 -96
}
我们知道:补码就是负数在计算机中的二进制表示方法。
-28 的二进制1110 0100
-67的二进制 1011 1101
-96的二进制 1010 0000
所以合起来就是11100100 10111101 10100000 完全一样
遗忘demo: -5的二进制求法:
5的二进制0101
取反反码:1010
补码=反码+1
-5的二进制表示1011
java字符编码 磁盘上的文件都是通过字节存储的,这句话非常重要
然后文件上通过编码给我们呈现原字符(比如utf-8)
Charset charset = Charset.forName("utf-8");//UTF-8 --定义一个字符集
CharsetDecoder charsetDecoder = charset.newDecoder();//创建解码器
CharsetEncoder charsetEncoder = charset.newEncoder();//创建编码器
解码:就是将磁盘文件转换成字符或者字符串的内容
编码:就是将字符或者字符串转换成字节或者字节数组的内容
磁盘上的文件的存储都是通过字节存储的,一个字节等于8bit,磁盘上的文件都有字符编码
例如打开idea中a.txt 底部有utf-8编码格式
//将磁盘上的文件以utf-8的格式进行解码
CharBuffer charBuffer = charset.decode(byteBuffer);
//将文件内容以utf-8的格式进行编码后输出到对应的磁盘
ByteBuffer byteBuffer1 = charset.encode(charBuffer);
因为是java,所以是java栗子:
char cc=97; System.out.println(cc);//cc=a 并且一个字符两个字节
那么java是如何将字符串转为字节的呢?
可以通过str.getBytes();方法,可以设置指定的编码,通过String的构造参数可以将转为字节的字符串,还原。
new String (bytes,“utf-8”);
System.getProperties().list(System.out);
得到jdk才用的编码是gbk
这时候就要提到了io流的读写文件,以前不明白,为什么将字符串转为字节写入文件中,文件一打开,居然还是原字符串,现在的我认为写入的还是字节,但是文件里是可以设置格式的,比如txt,可以试着utf-8编码,所以通过utf-8写入的字节,打开文件后还是原字符串。所以文件的i/o操作,必须制定charset,否则就会使用使用操作系统默认的编码,可能出现乱码。
来个demo:
RandomAccessFile:构造方法:RandomAccessFile raf = newRandomAccessFile(File file, String
mode);
其中参数 mode 的值可选 "r":可读,"w" :可写,"rw":可读性;
有点:既可以读,也可以写
因为RandomAccessFile可以自由访问文件的任意位置,所以如果我们希望只访问文件的部分内容,那就可以使用
RandomAccessFile类。
getFilePointer() 返回此文件中的当前偏移量。
seek(long pos) 设置文件指针偏移,从该文件的开头测量,发生下一次读取或写入。
RandomAccessFile src = new RandomAccessFile("C:\\Users\\Raytine\\Desktop\\a.txt", "rw");
RandomAccessFile desc = new RandomAccessFile("C:\\Users\\Raytine\\Desktop\\b.txt", "rw");
//获取 RandomAccessFile对象文件指针的位置,初始位置为0
System.out.print("输入内容:"+src.getFilePointer());
byte[] b=new byte[1024];
int len=0;
while((len=src.read(b))!=-1){
System.out.println(new String(b,"utf-8"));
}
src.seek(src.length());//返回文件长度定位到文件的末尾
src.write("好好的".getBytes("utf-8"));//文件的末尾追加内容
desc.write(b);
分析传统的io读写
我们可以通过InputStream从源数据中读取数据流到一个缓冲区里,然后再将它们输入到OutputStream里。
fileInputStream.read(b);
public int read(byte b[]) throws IOException {
return readBytes(b, 0, b.length);
}
private native int readBytes(byte b[], int off, int len) throws IOException;
最终会调用到本地方法
Linux 操作系统和驱动程序运行在内核空间,应用程序运行在用户空间。
实现过程链接
管道流:管道输入/输出流和普通的文件输入/输出流或者网络输入/输出流不同之处在于,它主要
用于线程之间的数据传输,而传输的媒介为内存。
//PipedInputStream.java
public void connect(PipedOutputStream src) throws IOException {
src.connect(this); //用于管道输入流和输出流的连接--连接后才能通信
}
pipedOutputStream.write(b,0,len);//将字节从字节数组中写入到管道
while((len=pipedInputStream.read())!=-1){//将管道中的字节读出来,也可以读到字节数组中
System.out.println((char)len);
}
demo:从a.txt文件中将数据读到管道,在从管道中将数据写道b.txt文件中
public class PipeStreamTest {
public static void main(String[] args) throws Exception{
PipedOutputStream pipedOutputStream=new PipedOutputStream();
PipedInputStream pipedInputStream=new PipedInputStream();
pipedInputStream.connect(pipedOutputStream);
Thread thread1=new Thread(new InR(pipedInputStream));
Thread thread2=new Thread(new OutW(pipedOutputStream)); thread2.start();
thread1.start();
}
}
class OutW implements Runnable{
private PipedOutputStream pipedOutputStream=null;
// FileOutputStream fileOutputStream=new FileOutputStream()
public OutW(PipedOutputStream pipedOutputStream) {
this.pipedOutputStream = pipedOutputStream;
}
@Override
public void run() {
try{
int len=0;
FileInputStream fileInputStream=new FileInputStream(new File("a.txt"));
byte b[]=new byte[1024];
while((len=fileInputStream.read(b))!=-1){
pipedOutputStream.write(b,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
pipedOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
class InR implements Runnable{
private PipedInputStream pipedInputStream=null;
public InR(PipedInputStream pipedInputStream) {
this.pipedInputStream = pipedInputStream;
}
@Override
public void run() {
try {
FileOutputStream fileOutputStream=new FileOutputStream(new File("b.txt"));
byte b[]=new byte[1024];
int len=0;
while((len=pipedInputStream.read(b))!=-1){
fileOutputStream.write(b,0,len);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
节点流和包装(处理)流
1)节点流偏向实现细节,直接与细节打交道,比如FileInputStream,而包装(处理)流偏功能,以目标功能为抽象,比如PrintStream。
2)区分节点流和包装(处理)流最简单的一个方式:处理流的构造方法中需要另一个流作为参数,而节点流构造方法则是具体的物理节点,如上FileInputStream构造法中需要一个文件路径或者File对象,而PrintStream构造方法中则需要一个流对象