Java基础笔记(二)
Object所有类的超类
在Java中,只有基本类型不是对象,所有的数组类型,不管是对象类型数组,还是基本类型数组都扩展了Object类
Object的两个方法
toString() equals(Object o) == 地址检测一个对象是否等于另外一个对象。
子类中覆盖equals时,首先调用父类的equals,检测失败,对象不可能相等,超类中字段都相等,再比较子类中实例字段。
Java语言规范要求equals应具有以下特性:
自反性:非空引用x,x.equals(x)应该为true。 对称性:x,y当且仅当y.equals(x)为true时,x.equals(y)返回true 传递性:对于任何引用xyz,有传递性。 一致性:x,y引用的对象没发生变化,反复调用应该返回同样的结果。 对于任意非空引用,与参数null比较应该返回false.
对于编写出完美的equals方法的建议:P177
1显示参数命名为otherObject,注意参数类型为Object。稍后他将强制转换成另一个名为other的变量。 2检测this与otherObject是否相等,相等返回true,检查身份比逐个比较字段开销小。 3检测otherObject.是否为null.如果为null返回false。 4if(getClass()!=otherObject.getClass()) return false 5将otherObject强制类型转换为相应类型变量,逐字段比较,用==来比较基本类型字段,Objects.equals比较对象字段 如果在子类中重新定义了equals,就要在其中包含super.eauals()。
System.out.println(a.toString()); // System.out.println(a);于上一行输出结果一样,按理说这并不是输出字符串,但是缺省去调用toString,toString挂在Object,让类能够缺省调用
hashCode方法
散列码,有对象导出的整型值,可以是任意整数包括负数,没有规律,两个不同的对象调用该方法,结果基本上不会相同。两个相等的对象要求返回相等的散列码。
Object类散列码根据对象存储的地址得出,而String类中覆盖了此方法,与字符串的散列码由内容(值)导出覆盖了Equals方法呼应。
equals与hashcode内容必须相同,s.equals(y)返回true,那么两个对象的hashCode方法返回必须相同。
如果重新定义了equals方法,就必须为用户可能插入散列表的对象重新定义hashCode 方法(散列表第九章讨论)
toString 随处可见的主要原因是,只要对象与一个字符串通过操作符"+"连接起来,Java编译器就会自动调用toString方法来获得这个对象的字符串描述。
可以不写为x.toString 可以写为" "+x将一个空串与x的字符串相连接,这样的好处是即使是基本类型,这条语句照样执行。
x是任意一个对象:System.out.println(x);println方法会简单调用x.toString()方法,
Object类中的toString方法是打印对象的类名和散列码。
数组调用toString打印的东西看不懂:补救方式:Arrays。
自己定义的每一个类都要添加toString方法。
Objects
Object.equals(a,b)其中一个参数为null返回false,两个参数都为null,返回true。两个参数都不为null,调用,a.equals(b)
hashCode(Object a) a为null返回0,否则返回a.hashCode。
hash(Objects... objects)可变参数。返回一个散列码,有提供的所有对象的组合得到
Date
1 new Date() 给你当前的时间
Date d1 = new Date(); System.out.println(d1);//Sun Jun 05 00:14:45 CST 2022
2 Date里存的是long型整数,从1970年1月1日0时,格林尼治时间 到现在这一时刻毫秒数
Date d2 = new Date(0L); System.out.println(d2);//Thu Jan 01 08:00:00 CST 1970
3 getTime()//get毫秒数
long l1 = d1.getTime(); System.out.println(l1);//1654359285900
4 时间与字符串格式相互转换
DateFormat基类 -> SimpleDateFormat(派生类) // 初始化SimpleDateFormat的实例,格式化字符串 // y 年 // M 月 // d 日 // H 小时 // m 分钟 // s 秒
日期格式化时,yyyy 表示当天所在的年,而大写的 YYYY 代表是 week in which year (JDK7 之后引入的概念),意思是当天所在的周属于的年份,一周从周日开始,周六结束,只要本周跨年,返回的 YYYY 就是下一年。 另外需要注意: 表示月份是大写的 M 表示分钟则是小写的 m 24 小时制的是大写的 H 12 小时制的则是小写的 h 正例:表示日期和时间的格式如下所示: new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
SimpleDateFormat fmt = new SimpleDateFormat("yyyy年M月dd日: HH-mm"); String str = fmt.format(d1);//时间变成字符串,还按照定义的格式 System.out.println(str);按自定义格式显示当前时间,2022年6月05日:00-14
str = new String("2023年5月05日: 17-15");//格式就按定义的来 d1 = fmt.parse(str); System.out.println(d1);//与上面反过来,字符变成时间
5 Calendar <-> Date//date过于简单,需要一些操作时转为calendar
// Calendar -> Date Calendar cld = Calendar.getInstance();//工厂函数;不用new Date d3 = cld.getTime(); System.out.println(d3); //输出当前时间
// Date -> Calendar cld.setTime(d2); System.out.println(cld);//输出:java.util.GregorianCalendar[time=0,areFieldsSet=tr...
6 get/set
System.out.println(cld.get(Calendar.YEAR));//1970 System.out.println(cld.get(Calendar.MONTH) + 1);//0开始 System.out.println(cld.get(Calendar.DAY_OF_MONTH)); System.out.println(cld.get(Calendar.HOUR)); // 12 System.out.println(cld.get(Calendar.HOUR_OF_DAY)); // 24 System.out.println(cld.get(Calendar.MINUTE)); System.out.println(cld.get(Calendar.SECOND)); System.out.println(cld.get(Calendar.DAY_OF_WEEK));
cld.set(Calendar.YEAR, 2003); cld.set(Calendar.MONTH, 8); cld.set(Calendar.DAY_OF_MONTH, 1); cld.set(Calendar.HOUR_OF_DAY, 15); cld.set(Calendar.MINUTE, 15); cld.set(Calendar.SECOND, 15); System.out.println(cld.getTime());//Mon Sep 01 15:15:15 CST 2003
7 add
cld.add(Calendar.YEAR, 1); System.out.println(cld.getTime());//Wed Sep 01 15:15:15 CST 2004 cld.add(Calendar.DAY_OF_MONTH, -4); System.out.println(cld.getTime());//Sat Aug 28 15:15:15 CST 2004
练习,按照格式输入自己的出生年月,然后计算一共活了多少天?有空做一下。没空就记忆上面的用法,基础知识更重要。
Date是一个表示时间点的类,里面的一些方法已经废弃废弃不用的方法虽仍可用但将来的某个类库版本可能会完全删除。
LocalDate
用日历表示法表示日期,用静态工厂方法,代表你调用构造器。
LocalDate.now();构造一个新对象,表示构造时的日期。
对象变量=LocalDate.of(1999,12,31);
int getYear()
int getMonthValue();
int getDayofMonth();得到当前日期的年月日
DayofWeek DayofWeek();得到当前日期是星期几,返回类型时DayofWeek类型实例。
LocalDate plusDays(int n);
LocalDate minusDays(int n);当前日期减n得到的日期数。得到本月第一天:参数n为today-1.
System
-
printf,(printformat)按格式化打印,与C语言中的很像
int i = 50; int i2 = 11150; System.out.printf("i = %d\n", i); System.out.printf("i = %04d\n", i);//宽度四位,不足前面补0,超出四位原样打印 System.out.printf("i = %4d\n", i2);//不足前面补空格 // ("%-5d",XXX),一共五位,左对齐
// 2. 8进制 System.out.printf("i = %o\n", i); // 16进制 System.out.printf("i = %x\n", i); // 3. 浮点数 double d1 = 1.2345; System.out.printf("d1 = %f\n", d1);//d1=1.234500 System.out.printf("d1 = %05.2f\n", d1);//一共五位,小数点后两位,小数点算一位 ,小数点前5-2-1=2位,不足补0.输出:d1=01.23 // 4. 字符串和字符 String str = "abc"; System.out.printf("str = %s\n", str); System.out.printf("str = %7s\n", str);//7位前面补空格 char c = 'A'; System.out.printf("c = %c\n", c); // 5. currentTimeMillis System.out.println(System.currentTimeMillis());//获得1970年到现在的毫秒数 // 6. arraycopy int[] a = {1, 2, 3, 4, 5}; int[] b = {6, 7, 8, 9, 10}; System.arraycopy(b, 1, a, 0, 3); //System.arraycopy(src, srcPos , dest, destPos, length); src源,从谁那copy;srcPos,开始copy的位置,1数组的第二位 ;destPos,放的位置,0,放到开头;lengthcopy长度 for(int v: a) { System.out.printf("%d ", v);//7 8 9 4 5 }
List
List<String> aList = new ArrayList<String>();//List<String>是父类,多态
1 add()
aList.add(new String("abc")); aList.add("bcd"); aList.add(new String("bcd"));//可接受重复 aList.add("cde"); aList.add(null); System.out.println(aList);//[abc, bcd, bcd, cde, null] print(aList);//abcbcdbcdcdenull
2 contains() 是是否包含这个值: 调用的是equals()
System.out.println(aList.contains("abc"));//true,判断abc是否已经存在 ,比的是值,不是地址 System.out.println(aList.contains(null));//true // 作业,自己实现一个contains函数,List内有可能有null,而送进去比较的实例也可能是null
3 remove()
aList.remove("abc");//移除数组中某个值 print(aList); bcdbcdcdenull aList.remove(null); print(aList); bcdbcdcde aList.remove("bcd");//如果有两个值一样的,只拿掉一个 Object[] objs = aList.toArray();//string to数组 for(int i = 0; i < objs.length; i++) { System.out.printf("%s-", objs[i]); bcd-cde- } System.out.println(""); for(String s: aList) { System.out.printf("%s=", s);
4 aList.isEmpty() ,aList.clear()
System.out.println(aList.isEmpty());//判断是否为空 aList.clear();//数组内容全清空 System.out.println(aList.isEmpty());
5 迭代器 Iterator
hasNext()指向数组开头,只要还有东西,返回true;
next(),只要hasNext()返回true,next就能将当前指向值返回,并往下走一格
遍历数组三种方法: for, forEach,迭代器(边用边删)
Iterator<String> it = aList.iterator(); while(it.hasNext()) { // System.out.printf("%s&", it.next()); String s = it.next(); // aList.remove(s);错误删除案例 // 如果用Iterator遍历并且删除,是不能用ArrayList.remove(E e) it.remove(); //先调用it.next,才能使用 } System.out.println(aList);//空了
2.List<String> lList = new LinkedList<String>();由于与ArrayList里面操作方法完全一样,就不复制了
泛型
1 Java中的泛型有三种:泛型类(一个或多个类型变量的类),类型变量用于指定方法返回类型与字段和局部变量类型。
泛型接口,泛型方法
2 Java中的泛型是伪泛型,泛型在代码中存在,编译时做类型检测,但是编译后就去掉了"类型的擦除”,全部用Object代替
了解:P336类型擦除与多态冲突,编译器在子类中生成一个桥方法,解决冲突。(父类方法引用子类类型,父类方法参数是类型参数,子类由于重写了方法不是类型参数而是具体类型,参数类型不同,两个方法就不是同一个,桥方法就是解决这种问题的)。
几个不能用 :
2.1 E e = new E() 禁止使用,非法的语法,P340:类型擦除后变成new Object(),你肯定不希望调用new Object() 2.2 E[] array = new E[5]; 禁止使用,会变成object,不能用泛型创建泛型类型数组 E[] array = (E[])new Object[5],可以这样用 2.3 MyArrayList<Integar>,包装类可用, 泛型(类型参数 )不能用于基本类型,必须用于类 不可以使用instance,不能用泛型类型强制类型转换Pair<String>被擦成Pair。也不能用参数化类型创建数组 new Pair<String>[10] 同样道理,error
泛型类:
自己写的一个可以容纳类类型的数组
public class MyArrayList<E> {}
MyArrayList<String> array = new MyArrayList<String>(); array.add("0abc"); array.add("1abc"); System.out.println(array.get(5));
泛型接口
public interface MyInterface1<E> { public abstract void add(E e); public abstract E get(int index); }
public class MyArrayList2<E> implements MyInterface1<E>{...} public class MyArrayList3 implements MyInterface1<String>{...}//在实现的时候就将泛型用了与【MyArrayList2】对比
MyArrayList3 l3 = new MyArrayList3();//与上面MyArrayList对比 l3.add("0abc");
MyInterface1<String> i = new MyArrayList2<String>(); i.add("0abc"); System.out.println(i.get(5));
泛型方法:普通类,泛型类中都可定义
public class MyClass1 { public <E> void show(E e) {//加<E>,代表这是是泛型方法类型变量,<E>写在修饰符后面,返回类型前面 System.out.println(e.getClass());//得到运行时类, } } 泛型方法尖括号中的类型变量是必须要加的,与返回类型没关系,主要是因为参数中有类型变量返回类型可以任意。 public <E> E show(E e) {} :返回类型是任意的,不做要求。
MyClass1 cls = new MyClass1(); cls.show(123); cls.show("abcd");
类型变量的限定类型
<T extends BoudingType> T应该是限定类型的子类型,T与限定类型可以是类,也可以是接口。 T extends BoudingType&other。 限定类型用“&”分隔,类型变量用逗号分隔。
继承规则:
泛型通配符:
现有静态方法:
public static void printSize(ArrayList<Object> array);{ System.out.println(array.size()); }
public static void main(String[] args) { ArrayList<String> ss = new ArrayList<String>(); ss.add("aaa"); 使用这个方法: printSize(ss);//报错,泛型不支持多态 ,只接受ArrayList<object>,类型实例,不接受ArrayList<string>类型,他们没有关系 }
这时注释掉重新写这个静态类,使用泛型通配符让它能接受其他类类型实例。
public static void printSize(ArrayList<?> array) {//泛型通配符,object不行,换成问号下面就能接受其它类了 System.out.println(array.size()); }
printSize(ss);//不再出现错误。
已知:
public class ClassB extends ClassA public class ClassC extends ClassB ArrayList<ClassB> bs = new ArrayList<ClassB>(); ArrayList<ClassC> cs = new ArrayList<ClassC>(); ArrayList<ClassA> as = new ArrayList<ClassA>();
public static void printSize2(ArrayList<? extends ClassB> array){System.out.println(array.size());}
printSize2(as);报错
printSize2(bs);ClassB与其派生类类型可以
public static void printSize2(ArrayList< ?> array);//都可以
public static void printSize2(ArrayList< ClassB> array);//printSize2(bs);可以其他都不可以
public static void printSize3(ArrayList<? super ClassB> array) { System.out.println(array.size());}
只有printSize3(cs);不可以,因为这种通配符:能接受自己父类与自己类的实例。
只能访问,不能设置:
上图,红色部分会被擦除只剩employee,要知道参数类型不同就是不同的方法,父子类中无相同方法,不可能实现多态。所以只能访问。
了解:
德州扑克的练习,有空可以练习
Collection集合
Java集合类库将接口与实现分离,有一组Abstract开头的类专为类库设计者设计,想实现自己的类(也许不太可能)扩展这些类要容易的多。
迭代器
迭代处理一个ArrayList将从索引0开始,每迭代一次,索引值加一。
方法:
集合框架中的接口
List适合用整数索引访问,链表尽管也是有序的,但随机访问很慢,适合用迭代器访问。
具体接口
AbstractList实现了List接口,AbstractSet实现了Set接口。
1 ArrayList 本质是数组(泛型类) 优点:读取的时间是常数时间 缺点:大小固定,内存空间扩展时的代价非常大,删除的时候需要拷贝大量内存。插入删除平均复杂度O(n) 适用的场景:内存空间增长不多,或者在开始时就能大概确定内存空间大小的应用 2 LinkedList 本质是双向链表 优点:持续的写入,删除,时间复杂度都是常数 缺点:随机读取的时间复杂度是O(n) 使用的场景:大量的持续写入和删除操作,较少的随机读取操作,遍历的影响不大。
LinkedList<String> lList = new LinkedList<String>(); lList.addFirst("abc");//从头部将元素插入 lList.addFirst("bcd"); lList.addLast("aaa");//尾部插入 lList.addLast("bbb"); System.out.println(lList.getFirst()); System.out.println(lList.getLast());//获得当前的尾部值 System.out.println(lList.removeFirst());//删掉头,并且能返回删除掉的内容 System.out.println(lList.removeLast()); lList.push("first");//相当于addFirst,添加到头 System.out.println(lList.pop());// 相当于removeFirst System.out.println(lList.isEmpty());//判断是否空
从数组中间删除一个元素开销很大,数组中位于被删除元素之后的元素要向数组前端移动,插入也是。链表解决了这个问题,数组是在连续位置存放对象引用,链表将每个对象存放在单独的连接中,每个连接中存放着序列中下一个链接的引用
在Java中所有的链表都是双向的。
链表相比集是有序的,因此对象的位置十分重要LinkedList.add方法将数据添加在链表最后尾部,元素添加到集合中间由迭代器负责,有序的集合使用迭代器添加元素才有意义,因此Iterator接口中没有add方法。
ListIterator集合类库中提供的接口
包含了add方法,无返回类型,就是为了添加元素,假定Add操作总会改变列表。
E previous() 返回越过的对象
boolean hasPrevious()这两个方法用来反向遍历链表。
此链表迭代器能发现它的集合被其它迭代器修改,或被集合自身的某个方法修改。被修改时会抛出异常,一个集合可以关联多个迭代器,必须仅读取。
上图显示调用next或previous再调用remove删除的元素位置不同。
下图仅了解:
链表避免随机访问,是用链表唯一理由是减少插入删除开销。否则用ArrayList
3 可以把一个LinkedList当作 栈和队列来用的 栈:removeLast相当于pop, addLast相当于push(入栈) 或者 removeFirst相当于pop(出栈), addFirst相当于push
队列:addLast, removeFirst 或 addFirst, removeLast,
list可放重复内容,set不行 .
4 HashSet: 无顺序,且无重复add相等的item只保存一个
迭代器访问:
HashSet<String> set = new HashSet<String>(); set.add("abc"); set.add(new String("abc")); set.add("bcd"); System.out.println(set);
HashSet底层是一个HashMap:
HashMap JDK 1.8以前:由哈希数组+链表组成
HashMap JDK 1.8以及以后:哈希数组+链表(个数小于8)+红黑树(平衡二叉树,链表长度大于等于8)
《Java8中,桶满时会从链表变成平衡二叉树》
优点:排序的时间复杂度O(N) 缺点:占用几倍于N的空间,同时还要兼顾链表带来的性能损耗
能放到HashSet里的泛型,都要实现这两个方法,object类都有这两个方法:
hashCode:针对一个实例,计算出他的hash值 equals:用于比较两个实例是否相等
往HashSet装元素时会调用hashCode来判断是否有相同的对象。
String s = new String("abc"); System.out.println(s.hashCode()); s = new String("abd"); System.out.println(s.hashCode());
HashSet<Employee> employees = new HashSet<Employee>(); employees.add(new Employee("E00001", "郭德纲", 47)); employees.add(new Employee("E00002", "于谦", 49)); System.out.println(employees); employees.add(new Employee("E00003", "郭麒麟", 22));//ID不一样才能加进去,否则hashcode,equals都相同;
《HashSet类实现了基于散列表的集》
散列表介绍:
5 LinkedHashSet 有顺序,无重复基类是HashSet,在HashSet内部初始化了LinkedHashMap实例,而不是HashMap实例
LinkedHashSet内部的实质,是一个HashMap + 所有实例的双向链表
LinkedHashSet<Employee> employees2 = new LinkedHashSet<Employee>(); employees2.add(new Employee("E00001", "郭德纲", 47)); employees2.add(new Employee("E00002", "于谦", 49)); System.out.println(employees2); employees2.add(new Employee("E00003", "郭麒麟", 22)); System.out.println(employees2);//有顺序
6 TreeSet: 有序,无重复,本质是红黑树:平衡二叉树(解决数组与链表的缺点)
可以按任意顺序将元素插入到集合中,会自动排序。因此迭代器总是以有序的顺序访问每个元素
元素添加到树中比添加到散列表中慢,但是树查找快。如果不需要数据是有序的,没必要付出排序的开销。
必须能够比较元素,能放进树集中使用的元素必须能够比较,也就是实现comparable接口。
有序的,add的过程自动排序,红黑树,first,last
TreeSet<Integer> tree = new TreeSet<Integer>(); tree.add(1); tree.add(1);//无重复,不报错但是无效,看输出结果 tree.add(3); tree.add(2); tree.add(9); tree.add(5);tree.add(6); System.out.println(tree);//输出[1, 2, 3, 5, 6, 9]自动按值排序 System.out.println(tree.first());//最小值1 System.out.println(tree.last()); System.out.println(tree.lower(3));// 小于3的最大值 2 System.out.println(tree.higher(5)); // 大于5的最小值 6 System.out.println(tree.floor(3));// 小于等于3的最大值 3 System.out.println(tree.ceiling(5));// 大于等于5的最小值 5
什么叫有序:是按值排序的。
可变参数:省略号:Java代码一部分,表明这个方法可接受任意数量的对象
public static int sum(int... array) {//可变参数,下面用到。 int s = 0; for(int i: array) { s += i; } return s; }
System.out.println(sum(1, 2, 3, 4));//将(int[] array)改成了 (int... array),方法使用时参数就能直接加数 ,自动收成数组
2printf第二个参数就是可变参数,省略号:Java代码一部分,表明这个方法可接受任意数量的对象。
提供基本类型值,会自动装箱。扫描format字符串,第i个格式说明符与args[i]匹配起来
允许将数组作为第二个参数,参数一有几个格式,参数二数组就要有几个值
3 public static void main(String... args)如果愿意,可以这样声明main方法
7 方法:Collections类 addAll
ArrayList<Integer> iList = new ArrayList<Integer>(); iList.add(1); // 或者 Integer[] is = new Integer[]{2, 3, 4}; // Collections.addAll(iList, is); Collections.addAll(iList, 2, 3, 4);//将234全add到iList 代替了手动每次add
8 混排方法:
Collections .shuffle(List<?> list)
//作用将里面的值顺序打乱,随即混排列表中元素的顺序。
9
一:基于Java容器的排序: Collections.sort(List<T> list): 归并排序 Up2down(从上到下), 稳定的, 在接近有序的情况下, 效率最高, O(NlogN) 二:Collections.sort(List<T> list): 缺省的排序方法是从小到大 这个方法假定列表元素实现了Comparable接口 三:s.compareTo(s2) 负整数:s < s2, 0: s == s2, 正整数 : s > s2 ,要求 T implements Comparable<T>这个方法必须确实能够比较两个对象,并返回比较的结果。方法参数:object。覆盖方法时必须考虑与equals兼容,同一个对象要返回0.翻转比较参数,结果值也应该反转。
String s = new String("abc"); String s2 = new String("abb"); System.out.println(s.compareTo(s2));
ArrayList<Student> students = new ArrayList<Student>(); students.add(new Student("100", "小红", 10)); students.add(new Student("9", "小兰", 12)); students.add(new Student("50", "小明", 11)); System.out.println(students); Collections.sort(students); System.out.println(students);
四:
方法:Collections.sort(List<T> list, Comparator<? super T> comparator)
参数一:数组一,参数二:比较器类(comparator)。比较器类要实现comparator接口,并实现compare方法.
Comparator接口,这个里面的compare方法可以在实现了接口的比较器对象上调用。再本例中仅用一次,考虑匿名。
练习:自己重写Comparator中的方法按自己想法将 list排序,比较的是年龄
// class MyComparator implements Comparator<Student>{ // @Override // public int compare(Student s1, Student s2) {//实现接口要实现这个方法,这个接口里就这一个方法 // if(s1 != null && s2 != null) { // return s1.getAge() > s2.getAge() ? 1 : -1; // }else { // return 0; // }
反正只用一次,干脆将上面注释掉,写成匿名内部类直接用:
Collections.sort(students, new Comparator<Student>() { @Override public int compare(Student s1, Student s2) { if(s1 != null && s2 != null) { return s1.getAge() > s2.getAge() ? 1 : -1; }else { return 0; } } }); System.out.println(students);
练习:Collections的sort,如何实现从大到小的排列,用一个Comparator,把所有的比较倒置,就能变成从大到小
ArrayList<Integer> i5 = new ArrayList<Integer>(); Collections.addAll(i5, 1, 3, 2, 4, 5); // 从小到大 //Collections.sort(i5); // 从大到小 Collections.sort(i5, new Comparator<Integer>() { @Override public int compare(Integer i1, Integer i2) { return -i1.compareTo(i2);//不少基础类里面都有这个方法 } }); System.out.println(i5);
练习:排序Student的学号,从大到小
Collections.sort(students, new Comparator<Student>() { @Override public int compare(Student s1, Student s2) { if(s1 != null) { return -s1.compareTo(s2); }else { return 0;} } }); System.out.println(students); }
Comparable Comparator区别们都是接口,前者实现类必须Override,compareTo方法,后者实现类要Override Compare方法,能根据自己的要求进行排序。
Java中将所有元素转入一个数组,对数组进行排序,然后再将序列复制回列表。
映射Map
Map<K, V> Key, Value Set<T> T, T,Set自己是key自己是value Map方法:1 put(K Key, V Value) 2 V get(Object Key)//用key查返回V类型
散列映射对键进行散列,不需要按有序顺序访问键,最好选散列映射。键是唯一的 ,同一个键使用两次put,第二个值会取代第一个值。
HashMap<Integer, String> hMap = new HashMap<Integer, String>(); hMap.put(1, "abc"); hMap.put(2, "bcd"); hMap.put(5, "ACD"); hMap.put(2, "bdc2");//值覆盖 System.out.println(hMap);//{1=abc, 2=bdc2, 5=ACD} System.out.println(hMap.get(2));
HashMap<MyClass, String> hMap2 = new HashMap<MyClass, String>(); MyClass m2 = new MyClass("222"); MyClass m22 = new MyClass("222"); hMap2.put(new MyClass("444"), "aaa"); hMap2.put(new MyClass("333"), "ACD"); hMap2.put(new MyClass("111"), "abc"); System.out.println(hMap2.put(m2, "bcd"));//null System.out.println(hMap2.put(m2, "bcd2"));//bcd System.out.println(hMap2);//{111=abc, 222=bcd2, 333=ACD, 444=aaa} System.out.println(hMap2.get(m22));//bcd2.与m2结果一样,也就是map认为是同一个key
1 如何判断是相同的key?
第一. hashCode()是否一样 第二. equals() true一样,false不一样
2 如果将equals修改,永远返回true:还是先看hashcode,再看equals
3 put,如果key是相同的,会覆盖;put是有返回值的,如果当前key没有存储过,返回值是null,如果当前key有存储过,会先把上一次存储的Value返回,然后覆盖之。
4 V remove(Object key)
System.out.println(hMap2.remove(m2)); System.out.println(hMap2.remove(new MyClass("4444"))); System.out.println(hMap2); //remove也会返回,将当前key对应的value返回,如果key不存在,返回null
5 Set<K> keySet() - 遍历key
System.out.println(hMap2.keySet());//[111, 333, 444]获得所有的key
6 按转序遍历map --Set<Map.Entry<K,V>> entrySet()
System.out.println(hMap2.entrySet());//[111=abc, 333=ACD, 444=aaa] for(Map.Entry<MyClass, String> entry: hMap2.entrySet()) {//Map.Entry笨拙,可以使用var声明。 System.out.println(entry.getKey()); System.out.println(entry.getValue()); }
7 HashMap遍历的顺序,和hashCode的顺序一致。
8 LinkedHashMap,顺序和加入时的顺序一致。 本质:HashMap + 链表
LinkedHashMap<MyClass, String> lhMap = new LinkedHashMap<MyClass, String>(); lhMap.put(new MyClass("444"), "aaa"); lhMap.put(new MyClass("333"), "bbb"); lhMap.put(new MyClass("111"), "ccc"); lhMap.put(new MyClass("222"), "ddd"); for(Map.Entry<MyClass, String> entry: lhMap.entrySet()) { System.out.println(entry.getKey()); }输出:444 换行333换行 111换行 222
9 TreeMap: 红黑树 有序,基于key排序 根据键的顺序将元素组织为 一个搜索树,比较只应用于键。
TreeMap<Integer, String> tMap = new TreeMap<Integer, String>(); tMap.put(5, "aaa"); tMap.put(3, "bbb"); tMap.put(2, "ccc"); tMap.put(1, "ddd"); tMap.put(7, "eee"); for(Map.Entry<Integer, String> entry: tMap.entrySet()) { System.out.println(entry); }//输出值有序,输出五行:1=ddd 2=ccc 3=bbb 5=aaa 7=eee System.out.println(tMap.firstEntry());//最小的,基与key找 System.out.println(tMap.lastEntry()); System.out.println(tMap.lowerEntry(3)); // 小于 System.out.println(tMap.higherEntry(5)); // 大于 System.out.println(tMap.floorEntry(3)); // 小于等于 System.out.println(tMap.ceilingEntry(5)); // 大于等于 System.out.println(tMap.headMap(3)); // 小于的所有项,排序的,升序 System.out.println(tMap.subMap(3, 6)); // 大于等于fromKey,且小于toKey,排序的,升序
10:Set List Map 是接口JDK9 给以上接口放了静态的构造方法,且集合不可变。重点,这些集合不可修改。试图改变,异常。
Set<String> set = Set.of("a", "b", "c"); System.out.println(set); //set.add("d");//使用构造方法时,不可改变了,运行时错误 List<String> list = List.of("a", "b", "c"); System.out.println(list); Map<Integer, String> map = Map.of(1, "a", 2, "b", 3, "c"); System.out.println(map); Map<Integer, String> scores = Map.ofEntries(entry("xiaoming",2),entry("xiaowang",8));
橙色线代表底层使用,红色框代表可实例化。三角形代表继承。
圆圈是接口,矩形与圆圈连线代表实现了这个接口。
这张图是辅助记忆,主要看《卷I》中的图.
视图
子范围视图:
不可修改视图:
这些视图对集合加了运行时检查,试图对集合进行修改,抛出异常,集合仍保持不变。
同步视图:
检查型视图:
算法
排序与混排shuffle上面记录路过。
二分查找:
批操作:
集合与数组转换:
数组转集合:
集合转数组:
克隆
Cloneable接口,指示一个类提供一个安全的clone方法。
clone方法再Object中是一个protected方法。
如果对象包含子对象的引用
原对象和浅克隆共享的子对象不可变,共享就是安全的
否则需要深拷贝,将对象中的可变实例字段也拷贝。p239
异常
1 Exception的价值,如果不使用Exception,那么函数调用过程中的每一个函数都要告诉上一层,函数调用是否成功,使得代码更加复杂.防御式编程《某个方法不能够采用正常的途径完成它的任务,可通过另外一个路径退出方法,不返回任何值,抛出封装了错误信息的对象,方法论立刻退出》
将正常语义与非正常语义分离。 void doA(): (1):能够完成A (2):(非正常语义)如果一切正常,能够完成A; 如果不正常,Exception
Error类层次结构描述了Java运行时系统的内部错误和资源耗尽错误。这种内部错误,你几乎无能为力,很少出现。
由编程错误导致的异常属于RuntimeException,程序本身没问题,其他错误导致的异常属于其他异常。
派生于Error或RuntimeException类的所有异常称为非检查型异常,其他异常称为检查型异常。
以上两种情况,必须告诉调用这个方法的程序员,如果没有处理器捕获,线程就会终止。
2 捕获异常
try 、catch、 finally 大括号外无分号
try{ f3(); }catch(badArgumentException e){ e.printStackTrace(); }catch(IllegalArgumentException e){ e.printStackTrace(); System.out.println("Illegal"); }catch(Exception e){ Throwable类型实例,用这个保底。不会运行,只要有一个运行,下面的就不会运行了。 e.printStackTrace(); System.out.println("Exception"); }finally{//是否出现异常都运行 System.out.println("do something finally."); }
//如果try里的内容正常运行,就 正常继续往下运行,如果不正常就运行先catch里的内容,再往下运行 //如果try或catch中有return,finally中没有,return会在finally之后执行,但是return的值是执行finally之前的值。(会先执行return前的代码,然后暂时保存需要return的信息,再执行finally中的代码,最后再通过return返回之前保存的信息。)《try中方法返回前,会执行finally语句块,finally中也有return,则会覆盖》
如果finally中也有return,则return会被覆盖最终值一finally中,return为准。
上图品味1:catch中指定的异常类,2子句中的处理器代码
传递
进行处理要比压制这个异常好。
再次抛出异常:可以在catch语句中使用throw再次抛出一个异常。p289
3 如何自己抛出错误
throw new Exception实例(message)
一旦方法抛出异常,这个方法就不会返回到调用者。
4 checked Exception vs Runtime Exception 区别
前者检查型异常继承自 Exception,后者继承自RuntimeException
RuntimeException或者其派生类,不需要写throws,Exception的派生类需要写throws(而且是每个调用错误方法的方法否都要写)(如果是用try接住就可以不用写)
5 当一个方法有可能抛出多个Exception,我们如何处理?
多个Exception,一种处理方法 多个Exception,每一个有自己独立的处理逻辑
6 throws <Exception列表> 应该看作放方法声明的一部分
写在方法名后,异常类之间逗号隔开。
不声明错误类型,以及运行时类型。运行时类型完全在我们的控制之中,自己花时间预防。
基类有一个方法,throws Exception,派生类如果override该方法,需要该方法throw的Exception的范围不能超过基类方法。
如果基类不throws Exception,那么派生类override该方法,也不得throws Exception,只能自己try catch捕获
7创建异常类
王校长例子:
public class badArgumentException extends Exception { public badArgumentException(String message) { super(message); } }
使用异常的技巧:1不要过分细化,这个歌任务包在一个语句块中。2充分利用层次结构,不要仅抛RuntimeException,应该找他合适的子类或自己创建异常类。不要只捕获Throwable异常类,捕获的更详细些,否则难读懂,难维护。3重要的异常不要用捕获方式压制,应该适当进行处理(catch里面写点东西)4检测错误要苛刻。5不要羞于传递。
线程
public class MyClass { public int i; }
public class MyThread extends Thread { public static int II = 10;//方法区 private MyClass cls; public MyThread(String name, MyClass cls) { super(name); this.cls = cls; } @Override public void run() {//启动一个线程都需要有入口函数 String a = "bcd"; for(int i = 0; i < 100; i++) { System.out.printf("%d: %s Thread is run! %s, %d, %d\n", i, this.getName(), a, this.cls.i, MyThread.II); try { Thread.sleep(50); }catch(Exception e) { e.printStackTrace(); }
public class MyRunnable implements Runnable { @Override public void run() { for(int i = 0; i < 100; i++) { System.out.printf("%d: MyRunnable Thread is run!\n", i); } }
1 建立一个新的线程
MyClass cls = new MyClass(); cls.i = 12; MyThread thread = new MyThread("thread_1", cls);/等号前面thread只是个引用(指针,在本地方法栈中),new出来的实例,在堆中 thread.start();//现在不推荐用子类的方式,应该把并行运行的任务与运行机制解耦合。 // thread.run(); MyRunnable mr = new MyRunnable(); Thread t = new Thread(mr, "thread_2"); t.start(); String a = "abc";//只有这里的,两个线程不一样,线程栈自己独享。 cls.i = 15;//共享全部改掉,打印出来都是15 MyThread.II = 25;//方法区,共享,一个地方改了,所有地方打印出来都是25
2 线程的内存模型: // 只有主线程的时候:Method Area(方法区),Heap(堆),Native Method stack(本地方法栈) // 当有多个线程的时候:每个线程有自己的Native Method Stack, 独享; Method Area和Heap,进程内所有的线程共享(两小时二十分钟左右)
3 . 线程的构造
1 java.lang.Thread: 构造方法 // public Thread() // public Thread(String name)
2 java.lang.Runnable: 有的时候,因为Java的单继承,我们不希望我们的类派生于Thread,解决方法是让类实现Runnable接口,然后用下面的方法启动线程 构造方法: // public Thread(Runnable mr) // public Thread(Runnable mr, String name)//同上,任选其一
// MyRunnable mr = new MyRunnable(); // Thread t = new Thread(mr, "thread_2"); // // t.start();
一些方法:
1 getName(), start(), run()
2 static void sleep(long millis)// 毫秒
Thread.sleep()的实质是告诉JVM,我现在不需要时间片了,把CPU让出来给更需要的人。
3 static Thread currentThread()//返回当前正在执行的线程的对象 System.out.println(Thread.currentThread().getName());//静态方法就可以直接类名调用
线程安全
首先:
public class TradingAccount { private String name;//商品名 private int count; //数量 public TradingAccount(String name, int count) { this.name = name; this.count = count;} public String getName() { return this.name; } public int getCount() { return this.count; } public int buyOne() { int theOne = this.count;//当前商品是第几号。先返回当前商品是第几号,再减 if(this.count > 0) {//商品数量大于0 才买 this.count--; return theOne;//返回商品号 }else{ return -1; } } }
public class Channel implements Runnable {//渠道 private int id;//渠道号 private TradingAccount account; public Channel(int id, TradingAccount account) { this.id = id; this.account = account; } @Override public void run() { int id = 0;//商品号,如果将这行放到循环里面,那么成了局部变量,出了循环就没有了,while会自动取this.id.因此逻辑错误 do { id = account.buyOne(); //商品号 if(id > 0) { System.out.printf("Channel %d: buy No. %d\n", this.id, id);//第几个渠道买的,买了第几号商品 } try { Thread.sleep(1);//分配的更均匀 }catch(Exception e) { e.printStackTrace(); } }while(id > 0);//一直买,直到买到0才不循环 } }
然后测试:
TradingAccount account = new TradingAccount("PC", 100); Thread thread = new Thread(new Channel(0, account)); thread.start();
会正常输出:
Channell 0: buy NO.100 ... Channell 0: buy NO.1 共一百行
现在只有一个渠道,想从是个渠道买。
Thread thread = new Thread(new Channel(0, account)); thread.start(); Thread[] threads = new Thread[10]; for(int i = 0; i < 10; i++) { threads[i] = new Thread(new Channel(i, account)); threads[i].start(); }
然后运行,问题出现,发现很多商品号重复,竟然不同的渠道能买到几个一样的商品号
int theOne = this.count; if(this.count > 0) { this.count--; 一个线程运行到这里,减完还没写,然后停了,出去了,时间片片给了其他线程。减完了没往回放,连单句表达式运行都不是原子的,this.count--既要读又要写)然是它已近获得到count的数据了放到自己的寄存器里了,与内存里面的没关系了。下一个线程来,也获得this.count,因为上一个没减。 问题在于读完没往回写就停了/在于读写公共内存 return theOne;
// 1. 线程安全问题:单线程是没有错误,但是多线程情况下就会出错,这种问题就是线程安全问题。第19分中的部分,多看 // Race condition, 竞争冒险
// 2. 什么情况下是绝对线程安全的? 第一种情况,线程独享内存数据。 // 第二种情况,所有的线程只读一个共享的内存。 // 什么情况下一定会出现问题?共享内存,有读有写。 // 3. 线程同步:
代码块同步
对于一段代码,我们可以要求任何一个时刻,只有一个线程可以进去执行,其他线程在上个线程执行完代码块以前,必须等待。
因为所有的线程都是用的同一个TradingAccount 类,就可以在这样。多个线程拿到同一个成员变量(lock),就会排队
public class TradingAccount { private String name;//商品名 private int count; //数量 private Lock lock; public TradingAccount(String name, int count) { this.name = name; this.count = count; this.lock = new MyLock();} public int buyOne() { synchronized(this.lock) { int theOne = this.count;//当前商品是第几号。先返回当前商品是第几号,再减 if(this.count > 0) {//商品数量大于0 才买 this.count--; return theOne;//返回商品号 }else{ return -1; } } }
方法同步:把整个方法作为一个同步代码块,把this作为同步用的同步对象(这种形式把this作为锁)(基于同一个实例产生的多个线程会同步,不同实例不受影响)。《一个方法声明时有synchronized,这种情况对象的锁保护整个方法,Java1.0开始,Java中每个对象都有一个么内部锁。要调用这个方法,线程必须获得内部对象锁。》
public synchronized int buyOne() {//方法同步,关键词修饰方法等价于上面代码块 int theOne = this.count; if(this.count > 0) { this.count--; return theOne; }else { return -1; } }
然后:
增加一个也访问count的方法,能不能做到同步?
与buyOne()相同的方法,buyOne2():
TradingAccount 增加一个属性private Object lock2;
buyOne2(): public int buyOne2() { synchronized(this.lock2) { //剩下的与buyOne()相同。
然后Channel人为做一下改动:
do { if(Math.random() > 0.5) { id = account.buyOne(); } else { id = account.buyOne2(); }
随机的调用one或one2中的一个来买。
运行后线程安全吗,答案是不安全。
当方法只有一个的时候,确实同步了,一次只能有一个线程进入
两个方法时问题出在两个不同的synchronized(this.lock) ,相当于有两个门卫。
如何改?两个synchronized参数改为一个对象都为lock
public int buyOne() { synchronized(this.lock){}
如果两buyOne()与buyOne2()都使用方法同步呢?(synchronized 修饰)不会有问题,因为同一个类中this是同一个this(内部对象锁)。
介绍锁,可以认为代码块同步与方法同步都用了锁。
不出错误的情况下,被同步的代码越多越好还是越少越好?越少越好,性能。大幅降低运行效率。所以线程同步好一些,至少没整个方法都同步。但是也差不多。这时候就要用锁,锁可以管理的更精细灵活小巧。比如仅需要同步的一两行上锁。
TradingAccount类新增:
import java.util.concurrent.locks.Lock;//锁lock,unlock比同步效率高
private Lock lock;//
构造方法中:this.lock = new ReentrantLock();//初始化
lock与lock2就可以注释掉了
改动仅需要同步的代码上锁:为什么不用synchronized(xxx){}?因为大括号总不能一个在循环外,一个在里面吧
public int buyOne() { this.lock.lock();//类自带方法,上锁 int theOne = this.count; if(this.count > 0) { this.count--; this.lock.unlock;//想解锁时使用这个方法 return theOne; }else{ this.lock.unlock;//每个分支都要有,包异常的finally分支 return -1; } } }
运行时没错误,那增加buyOne2()呢,只需上锁位置完全一样,就没问题。
Lock锁:lock(), unlock()(想尽量少的代码被同步就使用)。unlock必须包括在finally中,否则其它线程将永远阻塞。
上锁
锁更多知识:
1 互斥(mutex):对于某个多线程共享的资源,在访问的时候需要一个一个访问。 同步:当多线程访问共享资源的时候,如何协调,使得访问是安全的。
2 死锁:多线程需要两个或者两个以上的共享资源,每个线程的策略都是试图得到所有需要的资源,如果有资源没有拿到,就等待,当两 个或者两个以上的线程都在等待其他线程已经占用的资源,而且自己在运行完以前不打算放弃资源,那么这些线程就会陷入等待状态,这种情况:死锁
写一个死锁类:
package TestThreadSafety; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; //写一个死锁。都需要AB。争抢至死锁。 public class DeadLock { private Lock LockA; private Lock LockB; public DeadLock() { this.LockA = new ReentrantLock();//初始化 this.LockB = new ReentrantLock(); } public void f1() { // 先拿A,再拿B,干活,退出 this.LockA.lock(); System.out.println("got A in f1()"); this.LockB.lock();//f2先上这个锁了,当一个Lock实例被调用了.lock()时必须unlock,这边才能继续运行,否则只能在这一句等待,运行不下去,那边也需要locka解锁,也运行不下去(互相),死锁 System.out.println("print in f1()");//这句话就是打印不出来 this.LockB.unlock(); this.LockA.unlock(); } public void f2() { // 先拿B,再拿A,干活,退出 this.LockB.lock(); System.out.println("got B in f2()"); this.LockA.lock(); System.out.println("print in f2()");//这句话打印不出来 this.LockA.unlock(); this.LockB.unlock(); } }
测试一下:
package TestThreadSafety; public class Tester2 { public static void main(String[] args) { // TODO Auto-generated method stub DeadLock l = new DeadLock(); Runnable mr1 = new Runnable() { @Override public void run() { while(true) {//无限制的重复运行 l.f1(); } } }; Runnable mr2 = new Runnable() { @Override public void run() { while(true) { l.f2(); } } }; Thread t1 = new Thread(mr1); Thread t2 = new Thread(mr2); t1.start(); try { Thread.sleep(50);{//让t2间隔一段时间再运行,先能正常跑一会儿,然后死锁 }catch(Exception e) { e.printStackTrace(); } t2.start(); } }
可重入锁 vs 不可重入锁 可重入锁,如果一个线程已经调用了某个锁的lock,在本次调用中,再次调用该锁的lock,不会被阻塞,这样的锁称为可重入锁
package TestThreadSafety; //写一个不可重入锁 public class MyLock { private boolean isLocked = false;//记录锁的状态 public synchronized void lock() { while(this.isLocked) {//死锁false时上锁不运行这里,但当上过锁时再运行,会被卡住 try { this.wait();//所有的object,全都有,sleep是休眠一段时间,wait是等唤醒(抢不到lock就休眠) }catch(Exception e) { } } this.isLocked = true; } public synchronized void unlock() { this.isLocked = false; this.notify();//通知(唤醒) } }
然后TradingAccount 增加:private MyLock lock;this.lock = new MyLock();其他的注释掉,测试一下买商品,么有线程安全问题
测试是否可重入,TradingAccount 增加:
public void f1() { this.lock.lock(); f2(); this.lock.unlock(); } public void f2() { this.lock.lock(); System.out.println("run in f2()"); this.lock.unlock(); }
Tester测试:
account.f1(); System.out.println("end");//自己写的不可重入锁,这一行打印不出来,原来的可重入锁就可以
7 乐观锁和悲观锁: 悲观锁,锁会阻止没有获得锁的线程访问,将之阻塞,因为默认认为对某个公共资源的访问是不安全的 ReentrantLock,Synchronized都是悲观锁 乐观锁,CAS(Compare and Swap)
CAS(Compare and Swap):门卫不管,谁都可以进,当很多人进入大门后,当有第一个进入教学楼的,开始上锁,剩下的人出去大门重走一遍。再重复一遍以上过程,又有一个人进入教学楼了,整下的人再出大门,再走一遍。this.count--可以分为三部:读,减一,写。假设现在有两个线程做这个步骤,并不上锁,都可以开始。他们读到的数一样。但当进入写这一步的时候,有CAS。每个将要写的都判断一下现在的数与刚才读到寄存器里的还是一个数吗?所以第一个线程会写成功,第二个线程发现数据不对(第一个线程提前写完了),就不会写,再重新从头开始。 乐观,效率高,比如多人混检,效率高。乐观锁能极大提高系统吞吐率 (并行率)更多见数据结构04课程 。
TradingAccount 增加:【import java.util.concurrent.atomic.AtomicInteger;//里面封装了cas乐观锁的方法
private AtomicInteger count2;
this.count2 = new AtomicInteger(count);】
用count2代替count。并且重新写购买方法:
public int buyOne() { int theOne = this.count2.getAndAdd(-1);//这个类的方法之一,方法对count2进行加法运算,加的数是参数,并返回加之前的count2的值,返回值正确时,才算结束(乐观锁,xiangdangyu最后检查对不对。) // theOne > 0 合理的商品号(当前数量) // theOne =< 0 不合理的商品号,加回来,并且返回-1(theOne是减之前的数据,如果theOne =0,那么它现在已经被减为-1了) if(theOne > 0) { return theOne; } else { this.count2.addAndGet(1);//返回的是加完以后的值,都是CAS方法 return -1; } }
public int buyOne2() { int theOne = this.count2.getAndAdd(-1); // theOne > 0 合理的商品号 // theOne =< 0 不合理的商品号,加回来,并且返回-1 if(theOne > 0) { return theOne; } else { this.count2.addAndGet(1); return -1; } }
然后tester中运行十个数组的线程。
AtomicInteger源码
代码清单13-5 incrementAndGet()方法的JDK源码 /** * Atomically increment by one the current value. * @return the updated value */ public final int incrementAndGet() { for (;;) { int current = get(); int next = current + 1; if (compareAndSet(current, next)) return next; } }
incrementAndGet()方法在一个无限循环中,不断尝试将一个比当前值大一的新值赋值给自己。如果失败了,那说明在执行CAS操作的时候,旧值已经发生改变,于是再次循环进行下一次操作,直到设置成功为止。
volatile,这个关键字是提醒JVM,变量有可能被多个线程访问,所以当一个线程改动它时,要更新内存,同时通知其他线程,缓存失效。(内存的可见性) 一个volatile修饰的变量,是不是线程安全的?不是,例子:针对“读,+1,写”的例子(多个线程针对一块内存进行这三个步骤,每个步骤都有可能停掉出去再回来,如果还在读,通知失效重新读,如果没在读这个步骤,或者已经加完了,失效对线程无用,还会写一个错误的值),不是线程安全的
volatile: 可见性,线程不安全的 只有AtomicInteger是线程安全的。 volatile: 会禁止代码重排 三性:可见性,原子性,顺序性
0012两小时三十分钟,到两小时四十分钟。面试
volatile: 可见性,线程不安全的,如何线程不安全?:tester中演示
public static int count1 = 0; public static volatile int count2 = 0; public static AtomicInteger count3 = new AtomicInteger();
public static void increase(String s) { Tester.count1++; Tester.count2++; Tester.count3.incrementAndGet();//方法作用也是+1 System.out.printf("count%s: 1: %d, 2: %d, 3: %d\n", s, Tester.count1, Tester.count2, Tester.count3.get()); }
写个线程跑这个方法
Runnable mr = new Runnable() { @Override public void run() { for(int i = 0; i < 20; i++) { Tester.increase(Thread.currentThread().getName()); } } };
Thread[] threads = new Thread[5000]; for(int i = 0; i < 5000; i++) { threads[i] = new Thread(mr, ""+i); threads[i].start(); } }
测试结果: 只有AtomicInteger是线程安全的
禁止代码重排什么意思
double pi = 3.14; // 1 double r = 3; // 2 double s = pi * r * r; // 3
1,2句代码看起来运行顺序谁在前或这谁在后不影响,两个语句无依赖关系。
正是因为没有依赖关系,处理器可能会将两个独立的操作重新排序,以便同时执行它们
在运行时
所指的重排序优化是机器级的优 化操作,提前执行是指这条语句对应的汇编代码被提前执行
举例,伪代码
a读取配置文件 a读取完了 ---- 其他线程 if(a读取完了){ xxxx }
假如定义a读取完这个变量时没有使用volatile修饰,就可能会由于指令重排序的优化,导致其被提前执行.
这样在线程B中使用配置信息的代码
就可能出现错误,而volatile关键字则可以避免此类情况的发生。
这个关键字修饰的变量不会重排。
独享锁和共享锁 独享锁,只有一个线程能够使用的锁,其他线程都被阻塞:ReentrantLock,Synchronized 独享锁 共享锁,就是多个线程都可以获得的锁。ReadWriteLock,读,写 多个线程的只读是可以共享的。
条件对象:
条件对象管理获得了锁不能做有效工作的线程。条件对象管理那些已经进入被保护代码但还不能运行的线程,Lock类的newCondition(),返回一个与这个锁相关联的条件对象。一个锁对象可以有一个或多个相关联的条件对象。
条件对象的初始化,然后用bank.Lock锁住一个方法,比如转账(这个方法检查余额,余额不够不转),解决了什么问题呢,账户中没有足够资金时要等待,直到另一个线程向账户中增加了资金,但是这个线程刚刚获得了对bankLock的排他性访问权,别的线程没有存款机会,这里要引入条件对象如上图。这时transfer方法判断余额不足时会调用sufficientFunds.await();
应该使用锁与条件对象还是synchronized关键字呢,首先最好都不用,可以用线程安全的方法比如cocurrent包中线程安全的集合。其次考虑synchronized关键字,能减少代码量减少出错概率,最后特别需要条件对像/Lock提供的额外能力,才使用。
线程状态:
新建状态: new操作符新创建线程,new Thread(r),还没开始运行,此状态程序还没有开始运行线程中代码。
可运行状态: 一旦调用start方法进入此状态,可运行的线程可能正在运行也可能没有运行,由操作系统为提供运行时间。再次强调,可运行线程可能正在运行,可能没有运行。
阻塞、等待状态:阻塞或等待状态的线程,不运行代码。当一个线程试图获取一个内部对象锁(而不是java.consurrent库中的Lock)而这个锁被其他线程占有,该线程就会被阻塞。所有其他线程都释放了这个锁,并且线程调度器,允许该线程持有这个锁时,他将变成非阻塞状态。
终止状态:run正常退出(执行方法体中最后一条语句再执行return语句返回时)或者出现异常终止。
除了废弃的stop没有方法可以终止,不过interrupt方法可以用来请求终止一个线程。
被阻塞的线程无法检查中断状态,被阻塞的线程上调用interrupt方法时,抛InterruptedException。
设置中断的状态调用sleep不会休眠反而会清除中断状态,再抛异常。循环调用sleep时注意捕获异常。
守护线程:唯一用途是为其他线程提供服务,只剩守护线程时,虚拟机会退出,因为每必要继续运行程序了。
阻塞锁(要交时间片,就会有状态切换除非有很耽误时间的工作要把时间片让给别人,否则状态切换会很耽误时间)和非阻塞锁(CAS, 要求等很短的时间就可以成功)又叫自旋锁
公平锁和非公平锁 非公平锁,一线程运行了lock,其它线程排队(blocked状态),这时在跑unlock后,谁来的巧会上锁lock,而不是看谁是排队等待的第 一个。 new ReentrantLock() 默认非公平锁,非公平锁的效率更高 new ReentrantLock(true)//公平锁
0013五十分钟左右讲ReentrantLock()代码
著名问题生产者消费者:
生产者,多个线程队列,写。消费者。多个线程,拿。
队列,先用linkedlist做:
Thread[] producers = new Thread[10]; for(int i = 0; i < 10; i++) { producers[i] = new Thread(new Runnable() {//初始化十个生产者 @Override public void run() { for(int j = 0; j < 100; j++) { StringBuffer bfr = new StringBuffer(Thread.currentThread().getName()); bfr.append("-"); bfr.append( j); list.addLast(bfr.toString()); System.out.printf("addLast %s, ", bfr.toString()); } } }, "producer-" + i); producers[i].start();
消费者:
Thread[] consumers = new Thread[10]; for(int i = 0; i < 10; i++) { consumers[i] = new Thread(new Runnable() { @Override public void run() { String item; while(true) { item = list.removeFirst();//线程不安全,上面要同步 System.out.printf("removeFirst %s in Thread %s\n", item, Thread.currentThread().getName()); } } }, "consumer-" + i); consumers[i].start(); } }
显然上面代码有些问题,要不断改动,加了些同步代码,然后学习了信号量。然后使用线程安全的容器,无需同步,同步代码注释掉:
package TestProducersAndConsumers; import java.util.LinkedList;//线程不安全 import java.util.concurrent.Semaphore; // 信号量(权限) import java.util.concurrent.ArrayBlockingQueue;//线程安全的容器 public class Tester { public static void main(String[] args) { // LinkedList<String> list = new LinkedList<String>(); ArrayBlockingQueue<String> list = new ArrayBlockingQueue<String>(5);//用了线程安全的容器,不用自己同步,信号量也不用,同步代码全注释 // Semaphore listCapacity = new Semaphore(5);//信号量,相当于五把锁,比如相当于五把钥匙(权限),抢钥匙,抢到才能往队列写,写一个就要交回。 // 写完生产者消费者代码新需求:List最大的大小是5(假如) // Producer -> addLast // Consumer -> removeFirst // Producer -> offer //与上面方法完全一样 // Consumer -> poll // 生产者 Thread[] producers = new Thread[10]; for(int i = 0; i < 10; i++) { producers[i] = new Thread(new Runnable() {//初始化十个生产者 @Override public void run() { for(int j = 0; j < 100; j++) { StringBuffer bfr = new StringBuffer(Thread.currentThread().getName()); bfr.append("-"); bfr.append( j); // try { // listCapacity.acquire(); // 拿锁(权限),一个线程只有拿到锁才能往下走,第六个会被卡住,直到一人写完还锁 // }catch(Exception e) { // e.printStackTrace(); // } // synchronized(list) { // list.addLast(bfr.toString()); list.offer(bfr.toString());//与上一句功能相同,都是类里边的方法 // list.notify();//唤醒其中一个,与下面消费者中wait()组成线程间通信,必须同一个实例,必须在同步块中 System.out.printf("addLast %s, %d\n", bfr.toString(), list.size()); // } } } }, "producer-" + i); producers[i].start(); } // 消费者 Thread[] consumers = new Thread[10]; for(int i = 0; i < 10; i++) { consumers[i] = new Thread(new Runnable() { @Override public void run() { String item; while(true) { // synchronized(list) { // while(list.isEmpty()) { // try { // list.wait();//空了,时间片交出,效率更高 // }catch(Exception e) { // e.printStackTrace(); // } // } if(!list.isEmpty()) { // item = list.removeFirst();//线程不安全,上面要同步 item = list.poll(); // listCapacity.release();//释放权限 System.out.printf("removeFirst %s in Thread %s\n", item, Thread.currentThread().getName()); } // } } } }, "consumer-" + i); consumers[i].start(); } } }
// 3. 介绍过的容器,哪些是线程安全的?哪些不是 ArrayList, LinkedList, HashMap, HashSet, TreeMap, TreeSet, StringBuilder都是不安全 Vector(与ArrayList几乎一样,区别就是线程安全)HashTable(同HashMap), StringBuffer 都是安全的 。
线程安全的集合
阻塞队列
添加元素队列满,移除元素队列空,阻塞队列将导致线程阻塞。
有上面三类方法:管理线程时使用put与take;另外还有两类,多线程中队列随时变满或变空,应当使用offer、poll、peek。完不成任务这些方法给出错误提示而不是抛出异常。
java.util.concurrent包
提供了映射,有序集,队列的高效实现,通过允许并发访问数据结构的不同部分尽可能减少竞争。确定这些集合大小通常需要遍历size方法不能在常数时间完成工作。迭代器不一定能反映出他们构造之后的所有更改。
了解)批处理:
线程池
ExecutorService service = Executors.newFixedThreadPool(3);//初始化线程池,里面放的线程数 Runnable r = new Runnable() { @Override public void run() { System.out.println("in run()"); try { Thread.sleep(1000); }catch(Exception e) { e.printStackTrace(); } System.out.printf("Thread name: %s\n", Thread.currentThread().getName()); try { Thread.sleep(1000); }catch(Exception e) { e.printStackTrace(); } System.out.println("end"); } }; service.submit(r);//提交任务 service.submit(r); service.submit(r); service.submit(r);
[ɪɡˈzekjətə(r)] 执行器,执行者
Lambda [ˈlæmdə]
public class Student implements Comparable<Student> {}
public interface MyInterface { // functional interface public abstract void f(String s); }
public class Tester { public static void invokeF(MyInterface itf) { itf.f("abc"); } public static void main(String[] args) { // TODO Auto-generated method stub // Thread t = new Thread(new Runnable() { // @Override // public void run() { // for(int i = 0; i < 10; i++) { // System.out.println(i); // } // } // }); // Thread t = new Thread(() -> { // for(int i = 0; i < 10; i++) { // System.out.println(i); // } // });//用表达式替代上面 // // t.start();
// 1. Lambda表达式本质是个语法糖:针对这种只有一个方法的接口,以及实现了这样结构的匿名内部类实例,我们只写方法就好了。 // Lambda表达式的语法: // () -> { // for(int i = 0; i < 10; i++) { // System.out.println(i); // } // }//替代上面的run()方法 // 2. () -> expression // T f(){ // return expression; // } // () -> 5 // int f(){ // return 5; // } // 3. 参数:当只有一个参数的时候,可以没有(),只有参数;如果没参数或者有两个或者两个以上参数,()不可省略 // (x, y) -> x + y // int f(int x, int y) {//类型可写可不写 // return x + y; // } ArrayList<Student> students = new ArrayList<Student>(); students.add(new Student("100", "小红", 10)); students.add(new Student("9", "小兰", 12)); students.add(new Student("50", "小明", 11));
// Collections.sort(students, new Comparator<Student>() { // @Override // public int compare(Student s1, Student s2) { // if(s1 != null && s2 != null) { // return s1.getAge() > s2.getAge() ? 1 : -1; // }else { // return 0; // } // } // });
Collections.sort(students, (s1, s2) -> {//代替上段注释内容 if(s1 != null && s2 != null) { return s1.getAge() > s2.getAge() ? 1 : -1; }else { return 0; } }); System.out.println(students); ArrayList<Integer> list = new ArrayList<Integer>(); list.add(5); list.add(3); list.add(8); Collections.sort(list, (x, y) -> y - x);//函数不复杂,只写表达式的例子 System.out.println(list);
// MyInterface itf = new MyInterface() { // @Override // public void f(String s) { // System.out.println("aaa"); // } // };
// invokeF(s -> System.out.printf("aaa %s\n", s ));//只有一个参数的例子 int i = 5; int ii = 10;
MyInterface itf = s -> { System.out.printf("aaa %s, %d\n", s, i);
};
// i = 10; 不能改 // int ii = 20;//不得这样声明 }; invokeF(itf);
// 4. Lambda表达式的注意事项: // 接口必须只有一个函数: 即该接口是“函数式接口”, functional interface // Lambda表达式,返回的是一个实例。 // Lambda表达式是否可以使用其外部定义的局部变量?可以,但是,有限制:只可以用,不可以改;函数内不得声明和外部局部变量一样的变量(包括Lambda表达式内部的变量和形参) }
File
1 三种File的初始化方法:参数双引号字符串 绝对路径 window系统: 从盘符开始;用反斜杠(捺杠)区分不同级别的文件夹 linux系统、Mac系统:从根/开始;用斜杠(撇杠)区分不同级别的文件夹
注意字符串转义,两个反斜杠代表一个反斜杠。
File file1 = new File("F:\\git-test\\我的文档\\0.埃及计划\\后端零基础就业班\\release\\Code\\0013_JavaSE\\bin\\aaa.txt");
D:\后端零基础就业班\0013_JavaSE\bin.--------Windows中只有单反斜杠,但字符串中要输入多一个反斜杠来表示反斜杠。 File file2 = new File("abc");
System.out.println(file1.exists());//判断否存在 // 相对路径: 路径 + 文件 File file2 = new File("F:\\git-test\\我的文档\\0.埃及计划\\后端零基础就业班\\release\\Code\\0013_JavaSE", "bin\\aaa.txt"); System.out.println(file2.exists()); // 相对路径的特例,java程序启动的时候是有一个缺省目录的,如果你的相对路径和java程序的缺省目录一样,可以不写 System.out.println(System.getProperty("user.dir"));//打印缺省目录 File file3 = new File("bin\\aaa.txt");//相对路径 System.out.println(file3.exists()); // 相对路径,文件夹file实例 + 文件 File dir = new File("F:\\git-test\\我的文档\\0.埃及计划\\后端零基础就业班\\release\\Code\\0013_JavaSE");//文件夹 System.out.println(dir.exists());
File file4 = new File(dir, "bin\aaa.txt"); System.out.println(file4.exists()); // 2. 成员方法: // getAbsolutePath()//绝对路径 // getPath()//取决于初始化时路径 // getName() // length()//文件长度。 File[] files = new File[] {file1,file2,file3,file4,dir}; for(File f: files) { System.out.println("-----------------------------"); System.out.println(f.getAbsolutePath()); System.out.println(f.getPath()); System.out.println(f.getName()); System.out.println(f.length()); }
3 用于判断的成员函数 exists() isDirectory() //是否为文件夹 isFile() //是否为文件
System.out.println(file1.exists()); File file5 = new File("abcde"); System.out.println(file5.exists()); System.out.println(file1.isDirectory());//false System.out.println(dir.isDirectory());//true System.out.println(file1.isFile());//T System.out.println(dir.isFile());//F
4 方法:文件\文件夹的创建和删除 createNewFile() 创建文件,创建成功或失败会返回布尔型值 mkdir() 创建文件夹 mkdirs() 要创建的文件夹上层目录不存在时一并创建 delete()
File file6 = new File("F:\\git-test\\我的文档\\0.埃及计划\\后端零基础就业班\\release\\Code\\0013_JavaSE\\bin\\aaa1.txt"); System.out.println(file6.exists());//F不存在 try { file6.createNewFile(); }catch(Exception e) { e.printStackTrace(); } System.out.println(file6.exists());//T新建完成,名字就是初始化给的名字aaa1.txt
File dir2 = new File("F:\\git-test\\我的文档\\0.埃及计划\\后端零基础就业班\\release\\Code\\0013_JavaSE\\bin\\bin1"); System.out.println(dir2.exists()); System.out.println(dir2.mkdir());//注意创建成功会返回Boolean类型 System.out.println(dir2.exists()); File dir3 = new File("F:\\git-test\\我的文档\\0.埃及计划\\后端零基础就业班\\release\\Code\\0013_JavaSE\\bin1\\bin1"); System.out.println(dir3.exists()); System.out.println(dir3.mkdirs()); System.out.println(dir3.exists()); System.out.println(file6.delete());//也会返回Boolean类型 System.out.println(dir3.delete());//只删除目录最后一级别
-
文件夹递归(遍历,返回最后一个目录下的所有文件夹和文件) // String[] list()
// File[] listFiles() //两种方法,类型不同 File dir4 = new File("F:\git-test\我的文档\0.埃及计划\后端零基础就业班\release\Code\0013_JavaSE");
for(String s: dir4.list()) { System.out.println(s); }
for(File f: dir4.listFiles()) { System.out.println(f); System.out.println(f.isDirectory() ? "D": "F");//判断是文件夹还是文件 }
}
6 递归:一个方法,自己调用自己 F() -> F () 多个方法,互相循环调用,A B C: A -> B -> C -> A 练习:把文件夹下的内容全部打印出来 这下面写的递归方法并没有无限循环,如果空会返回,是文件就打印,只有是文件夹才递归.
public static void printDir(File dir, String tab) {//的第二个参数是为了输入空格(缩进)便于观看 if(!dir.exists()) { return; } File[] files = dir.listFiles(); for(File f: files) { if(f.isFile()) { System.out.printf("%sFile:%s\n", tab, f.getAbsolutePath()); } else { System.out.printf("%sDir:%s\n", tab, f.getAbsolutePath()); printDir(f, tab + " ");//递归 } } }
public static void main(String[] args) { File dir = new File("F:\\git-test\\我的文档\\0.埃及计划\\后端零基础就业班\\release\\Code\\0013_JavaSE"); printDir(dir, ""); }
写一个方法能根据名字找到里面是否有此文件,实现:参数能输入一个文件夹,再输入字符串,找到的文件们返回。 0014Teser3,有空复、练习。
IO流
1 I/O的概念 冯 诺依曼结构:运算器、控制器、存储器 Input输入设备,Output输出设备 内存 -> 硬盘,网卡 Output,硬盘,网卡 -> 内存 Input
2 流Stream 字节流 InputStream OutputStream(相对于内存) 字符流 Reader Writer
某些对象使用了内存之外的其他资源,在这种情况下,当资源不再需要时,将其回收和再利用显得十分重要。
如果一个资源一旦使用完就应立即关闭,应当提供一个close方法来完成必要的清理工作。
3 OutputStream的初始化
try { // 第一种初始化的办法 // File txtFile = new File("F:\\git-test\\我的文档\\0.埃及计划\\后端零基础就业班\\release\\Code\\0014_JavaSE\\bin\\txt\\a.txt"); // FileOutputStream foStream = new FileOutputStream(txtFile); // 第二种初始化的办法 FileOutputStream foStream = new FileOutputStream("F:\\git-test\\我的文档\\0.埃及计划\\后端零基础就业班\\release\\Code\\0014_JavaSE\\bin\\txt\\a.txt"); 4. 方法: write(int/byte[]) // 方法: byte[]、 offset、 length foStream.write(98);//码,打开文件显示小写字母a foStream.write(99); foStream.write(100); byte[] bytes = "ABCDE".getBytes();//字符串类型转换成byte数组类型 foStream.write(bytes); bytes = "0123456789".getBytes(); foStream.write(bytes, 3, 3);//可以只输入数组,也可以跟两个参数。从3开始写,写三个 // 5. flush, write的数据是缓存在内存的缓冲区中,直到调用flush,close,数据才会output到硬盘、网卡等设备。 // 6. 什么是回车? // CR Carriage Return 回车 \r // LF Line Feed 换行 \n // windows:\r\n // Unix:\n // Mac\Linux:\r foStream.write("\r\naaaaa".getBytes()); foStream.flush(); foStream.close(); }catch(Exception e) { e.printStackT race(); }
InputStream
try { // 1. InputStream 两种初始化方法 // File txtFile = new File("F:\\git-test\\我的文档\\0.埃及计划\\后端零基础就业班\\release\\Code\\0014_JavaSE\\bin\\txt\\a.txt"); // FileInputStream fiStream = new FileInputStream(txtFile); FileInputStream fiStream = new FileInputStream("F:\\git-test\\我的文档\\0.埃及计划\\后端零基础就业班\\release\\Code\\0014_JavaSE\\bin\\txt\\a.txt"); // 2. int read(byte[] b)返回读入缓冲区的总字节数,或如果由于到达文件末尾而没有更多数据,则返回-1。参数b读入数据的缓冲区 int read() 返回读取到的下一个字节,或读到末尾时返回-1 // int b; // while((b = fiStream.read()) != -1) {//如果读出-1是读到尾了,赋值优先于比较 // System.out.printf("%c", (char)b); // } int length; byte[] buf = new byte[4]; while((length = fiStream.read(buf)) != -1) {//这里的参数buf是缓冲区,将读取到的东西放到里面。赋值优先级高于比较运算符。设计巧妙 System.out.printf("%s", new String(buf, 0, length))//读多少,打印多少,读到最后时循环就结束了。这是String的构造方法之一,将字节型数组解码成字符串。三个参数:要解码谁,要解码的第一个字节的索引,要解码的长度(字节数) } fiStream.close(); }catch(Exception e) { e.printStackTrace(); }
从一个文件读取,写到另一个文件
try { FileInputStream fis = new FileInputStream("F:\\git-test\\我的文档\\0.埃及计划\\后端零基础就业班\\release\\Code\\0014_JavaSE\\bin\\txt\\aaa.docx"); FileOutputStream fos = new FileOutputStream("F:\\git-test\\我的文档\\0.埃及计划\\后端零基础就业班\\release\\Code\\0014_JavaSE\\bin\\txt\\aaa1.docx"); byte[] buf = new byte[1024]; int length; while((length = fis.read(buf)) != -1) { fos.write(buf, 0, length);//从指定数组中写入输出流。 } fos.close();//后开的先关,先开的后关 fis.close(); }catch(Exception e) { e.printStackTrace(); } }
FileReader、InputStreamReader
// 1. FileReader的初始化
try { // 第一种初始化的方法 // File txtFile = new File("F:\\git-test\\我的文档\\0.埃及计划\\后端零基础就业班\\release\\Code\\0014_JavaSE\\bin\\txt\\b.txt"); // FileReader fr = new FileReader(txtFile); // 第二种初始化的方法 // FileReader fr = new FileReader("F:\\git-test\\我的文档\\0.埃及计划\\后端零基础就业班\\release\\Code\\0014_JavaSE\\bin\\txt\\b.txt"); // 出现乱码,使用InputStreamReader //FileReader extends InputStreamReader FileInputStream fs = new FileInputStream("F:\\git-test\\我的文档\\0.埃及计划\\后端零基础就业班\\release\\Code\\0014_JavaSE\\bin\\txt\\b.txt"); // @@@ InputStreamReader fr = new InputStreamReader(fs, "GBK"); InputStreamReader fr = new InputStreamReader(fs, "UTF-8");//解决字符集不对的情况,直接使用父类(字符流的父类 ,参数是字节流) // 2. 字符集的编码: GB2312,GBK,GB18030,UTF-8,UTF-16,UTF-32,Unicode // 乱码问题的实质:文本文件是UTF-8,但是FileReader使用Window默认的字符集GBK(跟随系统),切不可更改 // int b; // StringBuffer bfr = new StringBuffer(); // // while((b = fr.read()) != -1) { System.out.println((char)b); // bfr.append((char)b); // } // // // 3. 第二种方法,String中的方法来转化字符集 // @@@ System.out.println(new String(bfr.toString().getBytes("GBK"), "UTF-8"));//与上一个@@@注释配套使用 // 4. length and char[] buf int length; char[] buf = new char[4]; while((length = fr.read(buf)) != -1) { System.out.println(new String(buf, 0, length)); } fr.close(); fs.close(); }catch(Exception e) { e.printStackTrace(); }
FileWriter、OutputStreamWriter
try { // 1. 第一种初始化的方法 // File txtFile = new File("F:\\git-test\\我的文档\\0.埃及计划\\后端零基础就业班\\release\\Code\\0014_JavaSE\\bin\\txt \\c.txt"); // FileWriter fw = new FileWriter(txtFile); // 2. 第二种初始化的方法 // FileWriter fw = new FileWriter("F:\\git-test\\我的文档\\0.埃及计划\\后端零基础就业班\\release\\Code\\0014_JavaSE\\bin\\txt\\c.txt"); // 3. 使用OutputStreamWriter FileOutputStream fs = new FileOutputStream("F:\\git-test\\我的文档\\0.埃及计划\\后端零基础就业班\\release\\Code\\0014_JavaSE\\bin\\txt\\c.txt"); OutputStreamWriter fw = new OutputStreamWriter(fs, "UTF-8"); fw.write(97); fw.write('A'); fw.write(25105); fw.write("立芳教育"); char[] buf = new char[] {'一', '二', '三', '四'}; fw.write(buf); fw.write("\r\nBBBB"); fw.close(); fs.close(); }catch(Exception e) { e.printStackTrace(); }
流的close地点
finally作用:清理资源,保证能能够运行
保证所有的文件在退出时能够close,无论是正常还是非正常
1 JDK7以前,把close放在finally
FileOutputStream fs = null; OutputStreamWriter fw = null;//放到try外定义,否则是局部变量finally中不认识 try { fs = new FileOutputStream("F:\\git-test\\我的文档\\0.埃及计划\\后端零基础就业班\\release\\Code\\0014_JavaSE\\bin\\txt\\c.txt"); fw = new OutputStreamWriter(fs, "UTF-8");//写的文件按照utf-8格式的 fw.write(97); fw.write('A'); fw.write("\r\nBBBB"); }catch(Exception e) { e.printStackTrace(); }finally { try { if(fw != null) { fw.close(); } }catch(IOException e) { e.printStackTrace(); } try { if(fs != null) {//都要分开,否则一个出问题就跳到catch中另一个又没关闭 fs.close(); } }catch(IOException e) { e.printStackTrace(); }
2 JDK7, try(声明),小括号中的内容会自动close,如果try()套在一层try{}里面,里面的这个是不用写catch的 3 JDK9, try(引用),也是在里面,不用catch try-with-Resources语句
try { File file = new File("F:\\git-test\\我的文档\\0.埃及计划\\后端零基础就业班\\release\\Code\\0014_JavaSE\\bin\\txt\\c.txt"); // try(FileOutputStream fs = new FileOutputStream(file); // OutputStreamWriter fw = new OutputStreamWriter(fs, "UTF-8");) { // FileOutputStream fs = new FileOutputStream(file); OutputStreamWriter fw = new OutputStreamWriter(fs, "UTF-8"); try(fs; fw){//引用 fw.write(97); fw.write('A'); fw.write(25105); fw.write("立芳教育"); char[] buf = new char[] {'一', '二', '三', '四'}; fw.write(buf); fw.write("\r\nBBBB"); } }catch(Exception e) { e.printStackTrace(); }
Properties
Properties properties = new Properties(); // set properties.setProperty("config_file", "x.config");//(key,value) properties.setProperty("start_point", "10000"); properties.setProperty("start_count", "20");
System.out.println(properties); // get System.out.println(properties.getProperty("config_file")); System.out.println(properties.getProperty("start_point")); System.out.println(properties.getProperty("start_count")); // 遍历 Set<String> keys = properties.stringPropertyNames(); for(String key: keys) { System.out.println(properties.getProperty(key)); } // properties实例 <-> 文件 try { File file = new File("F:\\git-test\\我的文档\\0.埃及计划\\后端零基础就业班\\release\\Code\\0014_JavaSE\\bin\\txt\\p.config"); FileOutputStream fos = new FileOutputStream(file); try(fos){//自动关闭 properties.store(fos, "My comments");//存储到相应文件 } Properties properties2 = new Properties(); FileInputStream fis = new FileInputStream(file); try(fis){ properties2.load(fis);//读 System.out.println(properties2); } }catch(Exception e) { e.printStackTrace(); }
long start = System.currentTimeMillis(); // try(FileInputStream fis = new FileInputStream("F:\\git-test\\我的文档\\0.埃及计划\\后端零基础就业班\\release\\Code\\0015_JavaSE\\bin\\7.mp4"); // FileOutputStream fos = new FileOutputStream("F:\\git-test\\我的文档\\0.埃及计划\\后端零基础就业班\\release\\Code\\0015_JavaSE\\bin\\71.mp4")){ try(BufferedInputStream fis = new BufferedInputStream(new FileInputStream("F:\\git-test\\我的文档\\0.埃及计划\\后端零基础就业班\\release\\Code\\0015_JavaSE\\bin\\7.mp4")); BufferedOutputStream fos = new BufferedOutputStream(new FileOutputStream("F:\\git-test\\我的文档\\0.埃及计划\\后端零基础就业班\\release\\Code\\0015_JavaSE\\bin\\71.mp4"))){ // int b; // while((b = fis.read()) != -1) { // fos.write(b); // }一次性读一个int int length; byte[] bytes = new byte[4*1024]; while((length = fis.read(bytes)) != -1) { fos.write(bytes, 0, length); }一次性读4k更快(替换掉上一段) long end = System.currentTimeMillis();//两个毫秒数相减对比运行时间快慢,带buffer明显快。普通字节流不停地io是不经济,耗时间的,所以才有了缓冲字节流,先放到缓冲区一次性IO. //后端常识,传输很多小文件,打包压缩一次性传会更快 System.out.printf("Time taken: %d\n", end - start); }catch(Exception e) { e.printStackTrace(); } }
缓冲字符流
//01 缺省gbk try(BufferedReader br = new BufferedReader(new FileReader("F:\\git-test\\我的文档\\0.埃及计划\\后端零基础就业班\\release\\Code\\0015_JavaSE\\bin\\a.txt")); // BufferedWriter bw = new BufferedWriter(new FileWriter("F:\\git-test\\我的文档\\0.埃及计划\\后端零基础就业班\\release\\Code\\0015_JavaSE\\bin\\b.txt"))){ try(BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("F:\\git-test\\我的文档\\0.埃及计划\\后端零基础就业班\\release\\Code\\0015_JavaSE\\bin\\a.txt"), "GBK"));//02可以设定字符集 BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("F:\\git-test\\我的文档\\0.埃及计划\\后端零基础就业班\\release\\Code\\0015_JavaSE\\bin\\b.txt"), "UTF-8"))){//用GBK读出来,用UTF-8写进去 String line = null; while((line = br.readLine()) != null) {//按行,循环操作一次读一行 System.out.println(line); bw.write(line);//写操作 bw.newLine();//上面,这里就写个回车 } }catch(Exception e) { e.printStackTrace(); }
序列化与PrintStream
public class Student implements Serializable { private static final long serialVersionUID = 1L; // private String name; private int age; private int id; private int id2; private transient String ageAndId;
try(PrintStream ps = new PrintStream("F:\git-test\我的文档\0.埃及计划\后端零基础就业班\release\Code\0015_JavaSE\bin\a.log")){ System.setOut(ps);//设置输出位置, 所有System.out.println等输出内容都输出到这个文件里面去了,控制台上不会显示
Student s = new Student("隔壁王校长", 39, 1); System.out.println(s); // try(FileOutputStream fo = new FileOutputStream("F:\\git-test\\我的文档\\0.埃及计划\\后端零基础就业班\\release\\Code\\0015_JavaSE\\bin\\student.txt"); // ObjectOutputStream oo = new ObjectOutputStream(fo)) { // // oo.writeObject(s);序列化操作,将类放到文件 // // } try(FileInputStream fi = new FileInputStream("F:\\git-test\\我的文档\\0.埃及计划\\后端零基础就业班\\release\\Code\\0015_JavaSE\\bin\\student.txt"); ObjectInputStream oi = new ObjectInputStream(fi)){ Student s2 = null; s2 = (Student)oi.readObject();//反序列化,返回Object类型,强制类型转换。从流中读取一个对象 System.out.println(s2); } // 1. 可以序列化的对象必须是实现了Serializable接口的类,所有的成员数据会被存储,除了被transient修饰的,static也不会被序列化。(这个接口是空的,但是仍然需要声明实现) // 2. 反序列化 // 3. 数据文件 <-> 类的版本的对应关系 // 只有数据文件,没有类的定义,无法使用 // 数据文件的版本很老,类更新了:java.io.InvalidClassException // private static final long serialVersionUID, 如果值是一样的,就可以读写 // 如果数据格式升级,怎么办?向前兼容(历史包袱会越来越重),数据订正(读出来,改写,写回去) // 4. PrintStream }catch(Exception e) { e.printStackTrace(); } }
Socket
import java.net.Socket; 实现这一套东西,使系统能使用网络
public class Server {//服务器 public static void main(String[] args) { System.out.println("Server is on..."); // 1. ServerSocket对象,绑定一个端口 5555 try { ServerSocket ss = new ServerSocket(5555); try(Socket server = ss.accept();//需要close,也放在try里 InputStream is = server.getInputStream()){ byte[] b = new byte[1024]; int len; while(true) {//死循环,server永远都能接受到client发来的消息 len = is.read(b); String msg = new String(b, 0, len); System.out.println(msg); if(msg.equals("bye")) { break; } } } }catch(Exception e) { e.printStackTrace(); } }
public class Client { 客户端 public static void main(String[] args) { // TODO Auto-generated method stub System.out.println("Client is on..."); // 创建Socket 需要对方IP,端口 try(Socket client = new Socket("127.0.0.1", 5555);//对方的IP地址与端口 //任何一个server的IP地址都是自己127.0.0.1,可以替换成localhost都是自己的IP OutputStream os = client.getOutputStream();){ Scanner sc = new Scanner(System.in); while(true) { System.out.println("please input a word:"); String msg = sc.next(); os.write(msg.getBytes()); if(msg.equals("bye")) { break; } } }catch(Exception e) { e.printStackTrace(); } }
再改成可以传输文件的客户端与服务器:可作为练习,常看。
package TestNetworkFile; import java.net.Socket; import java.io.OutputStream; import java.util.Scanner; import java.io.File; import java.io.FileInputStream; import java.io.BufferedInputStream; import java.io.BufferedOutputStream;
public class Client {
public static void main(String[] args) { // TODO Auto-generated method stub System.out.println("Client is on..."); // 创建Socket 需要对方IP,端口 try(Socket client = new Socket("127.0.0.1", 5555); BufferedOutputStream bos = new BufferedOutputStream(client.getOutputStream());){//上一个包复制过来的,改造成Buffer Scanner sc = new Scanner(System.in); while(true) { System.out.println("please input a path:"); String msg = sc.next(); if(msg.equals("bye")) { bos.write(msg.getBytes()); bos.flush();//所有的Buffer都要flush,否则不会发送 break; } // 是个File Path // 1. 判断File是否存在 File file = new File(msg); if(file.exists()) { // 2. 组装copy命令,并发送 StringBuffer cmd = new StringBuffer("copy;"); cmd.append(file.getAbsolutePath()); cmd.append(";"); cmd.append(file.length()); bos.write(cmd.toString().getBytes());//以bytes数组形式写入 bos.flush(); // 调试时判断是否是竞争冒险 Thread.sleep(30000); // 3. 输入流读取本地文件,循环传输文件 byte[] b = new byte[1024]; int len; try(FileInputStream fis = new FileInputStream(file); BufferedInputStream bfis = new BufferedInputStream(fis);) { while((len = bfis.read(b)) != -1) { bos.write(b, 0, len);//发给服务器 bos.flush(); } } System.out.printf("File %s is sent!\n", file.getAbsoluteFile()); } } }catch(Exception e) { e.printStackTrace(); } }
public class Server {省略头文件
public static void main(String[] args) { System.out.println("Server is on..."); // 1. ServerSocket对象,绑定一个端口 5555 try { ServerSocket ss = new ServerSocket(5555); try(Socket server = ss.accept(); BufferedInputStream bis = new BufferedInputStream(server.getInputStream()); BufferedOutputStream bos = new BufferedOutputStream(server.getOutputStream());){//添加,双工,可写 byte[] b = new byte[1024]; int len; while(true) { len = bis.read(b); String msg = new String(b, 0, len); //处理消息 if(msg.equals("bye")) { break; } if(msg.startsWith("copy;")) {//这三行,新加 bos.write("copyStart".getBytes()); bos.flush(); // 1. 把copy命令用分号“;”分解为三个字符串 String[] words = msg.split(";"); if(words.length == 3) { // 2. 从(0开始)1个字符串中找到文件的扩展名 // 2.1 用“\”把所有的文件夹名或者文件名分离出来,最后一个是文件名 String path = words[1]; // 反斜杠“\”在正则表达式中也是特殊转义字符,如果真的需要用“\”,需要两个\\,所以字符串中需要四个 String[] dirs = path.split("\\\\"); String fileName = dirs[dirs.length - 1]; // 2.2 用“.”把文件名和扩展名分开 String[] fileNames = fileName.split("\\."); if(fileNames.length == 2) { String fileExt = fileNames[1]; StringBuffer serverFile = new StringBuffer("F:\\git-test\\我的文档\\0.埃及计划\\后端零基础就业班\\release\\Code\\0015_JavaSE\\bin\\"); serverFile.append(System.currentTimeMillis()); serverFile.append("."); serverFile.append(fileExt); // 3. 从(0开始)2个字符串,parse成文件的长度,Long型 String fileLengthString = words[2]; long fileLength = Long.parseLong(fileLengthString); // 4. 循环的读取文件 // 初始化写入文件 try(FileOutputStream fos = new FileOutputStream(serverFile.toString()); BufferedOutputStream bfos = new BufferedOutputStream(fos);){ long currentLength = 0; while(true) { len = bis.read(b); bfos.write(b, 0, len); bfos.flush(); currentLength += len; if(currentLength >= fileLength) { break; } } } } } } } } }catch(Exception e) { e.printStackTrace(); } }
函数式编程
什么是函数式编程,
函数式编程本质。上图
纯函数是确定的,返回的东西确定。无状态,所以没有线程安全问题。
大厂没有用的,十年后才有可能。
Haskell是函数式编程语言,未来如果成为第一梯队语言,就可以去学习。
群论粗略知识在一小时十五分钟,一小时十五分钟。
package Functional; @FunctionalInterface //函数式接口,加了这个注解的接口中只能有一个方法,否则报错 public interface MyInterface { public void f(); }
// MyInterface inter3 = c.getInner3(); // inter3.f(); 上图i3变量,是一个方法中的变量按理说运行完方法就没了,那么调用f还怎么获得, 实际上,运行时能解析出外部还要访问,所以内存不放在方法栈中,放到finall域中,方法推出,不释放,外部才能访问到。 这就是Java闭包。
而且如下图,i3必须是只读的,内部类中要修改就会报错:
所谓的不可改动只是不能替换,加入i3是个实例,指的是不能替换变量本身,实例内部的实例字段(或数组内部值)是可以改变的。
闭包的一种用法:log
输入一个T类型,返回一个R类型
如果不太懂,也不需要看课程了,直接看Function类等的源码.
package Functional; import java.util.function.Function; // 一入一出 import java.util.function.Supplier; // 无入一出 import java.util.function.Consumer; // 一入无出 import java.util.function.Predicate; //相当于 Function<T, Boolean> import java.util.ArrayList; public class Tester { public static void f2(MyInterface inter) { inter.f(); } public static void addToArray(Supplier<String> builder, ArrayList<String> list) { list.add(builder.get()); } public static void printArray(Consumer<String> builder, ArrayList<String> list) { System.out.printf("["); for(int i = 0; i < list.size(); i++) { builder.accept(list.get(i)); if(i != list.size() - 1) { System.out.printf(","); } } System.out.printf("]\n"); } public static void printArray2(Predicate<String> filter, Predicate<String> filter2, ArrayList<String> list) { System.out.printf("["); for(int i = 0; i < list.size(); i++) { // if(filter.test(list.get(i)) && filter2.test(list.get(i))) { if(filter.and(filter2).test(list.get(i))) { System.out.print(list.get(i)); if(i != list.size() - 1) { System.out.printf(","); } } } System.out.printf("]\n"); }
public static void main(String[] args) { // Java闭包:内部定义的类,可以访问外部的变量 //所有每部定义的类,以及他其能访问的变量: // 1. 内部类, 外部类实例的成员变量 // 2. 局部内部类, 外部类实例的成员变量 + 函数内的局域变量(final) // 3. 匿名内部类, 外部类实例的成员变量 + 函数内的局域变量(final) // 4. Lambda表达式, 外部类实例的成员变量 + 函数内的局域变量(final) // MyClass c = new MyClass(); // // MyClass.InnerClass i = c.getInner(); // c.setI(20); // // i.f(); // // MyClass.InnerClass i2 = c.new InnerClass(); // c.setI(30); // i2.f(); // // MyInterface inter2 = c.getInner2(); // inter2.f(); // // MyInterface inter3 = c.getInner3(); // inter3.f(); // // MyInterface inter4 = c.getInner4(); // inter4.f(); // // c.fff(); ----------------------------------------------Java中专用来实现函数式编程的函数式接口,直接用 Function<Integer, Integer> f1 = e -> e * 2;//直接用,写表达式 int i1 = f1.apply(5); System.out.println(i1); Function<Integer, Integer> f2 = e -> e + 3; // 1. Function apply int i2 = f2.apply(5); System.out.println(i2); // i1 = f1.apply(5); // i2 = f2.apply(i1); // i1 = f1(5) 数学中上一行应该改这么表达 // i2 = f2(i1); // i2 = f2(f1(5));//数学中f1结果又作为f2从参数 // i2 = f2.f1(5);
源码,返回的是function 类型实例
// 2. Function compose andThen i2 = f2.compose(f1).apply(5);//f1结果又作为f2从参数的实现 System.out.println(i2);//13 Function<Integer, Integer> f3 = e -> e + 15; int i3 = f3.compose(f2).compose(f1).apply(5);//调用时从左向右调用 System.out.println(i3);//28 int i4 = f3.andThen(f2).andThen(f1).apply(5);//这个方法调用结果相反5先作为参数给f3 System.out.println(i4);//46 // 3. Supplier, Consumer ArrayList<String> array = new ArrayList<String>(); addToArray(() -> "" + i4, array);//方法在最上面,传进的参数是没有输入参数,只有输出的表达式 addToArray(() -> "(" + i4 + ")", array);
System.out.println(array); printArray(s -> { System.out.printf("==%s--", s);//同样,演示consumer方法在最上面,,只输入参数 }, array); printArray(s -> { System.out.printf("\'%s\'", s); }, array);
array.add("47"); array.add("407"); array.add("732"); printArray2(s -> s.indexOf('4') >= 0, s-> s.indexOf('7') >= 0, array);//演示Predicate,方法在最上面. } }