第二周笔记
day01
Date(Demo06)
-
Data: 数据类,装的是二进制数据(和Date区分开)
-
Date: 日期类
-
java.sql 对应的是数据库的日期类型,只包括年月日 java.util 对应的是java的日期类型,包括年月日, 时分秒
-
数组转过来的集合长度是固定的,所以不能执行增加,删除,但是可以执行修改,更改,遍历
-
二分查找: Arrays.binarySearch(arr,a);
-
日历类:
//日历类 Calendar calendar = Calendar.getInstance(); System.out.println(calendar); //获取当前的Date类型的时间 Date date = calendar.getTime(); System.out.println(date); //获取指定的值 int value = calendar.get(Calendar.DATE); System.out.println(value); //获取属性的最大值,最小值 int value1 = calendar.getMaximum(Calendar.YEAR); int value2 = calendar.getMinimum(Calendar.YEAR); System.out.println(value1+" "+value2);
String
- 字符串的基础
- java将字符串相关的功能面向对象。形成了对象的类–字符串类
- 字符串不是对象(没new ),但是把他当作对象来用,来提高使用效率
- 常量存在常量区里面,堆区里面有个特殊常量区(专门存放字符串)
- String默认将Object中的.equals重写,比较的时候取new出的内存里面的内容,
- -------字符串的比较用.equal方法去比较---------
- s1=1000; s2=1000; s3= new String(“1000”); s4= new String(“1000”); s1=s2; true(地址相同) s1=s3; flase s1.equals(s3) ; true (比较内容) s3=s4; false (地址不同)
-
String的常用方法
String s = “1000phone”;
-
是否包含字符串(s.contains(“1000”));
-
字符串是否相同(s.equals(“1000”));
-
忽略大小写判断字符串的内容是否相同(s.equalsIgnoreCase(“1000”));
-
判断是否以某字符串开头(s.startWith(“1000”));
-
判断以某字符串结尾(s.endsWith(“phone”));
-
将字符串数组转换成字符串
char[] arr = {'p','h','o','n','e'}; String string1 = new String(arr); System.out.println(string1);//phone
-
将字符串转换为字符数组
String s= "1000phone"; char[] arr =s.toCharArray(); System.out.println(arr);
- 将字节数组转成字符串
byte[] bytes = {97,98,99,100}; String string3 = new String(bytes); System.out.println(string3);
-
将字符串转成字节数组
String s="abbcc"; byte[] bytes1 = s.getBytes(); System.out.println(bytes1[0]);//97
-
替换
String s = "1000phone"; //String replace(char oldChar, char newChar) String string5 = s.replace("1000", "haha"); System.out.println(string5+" s:"+s);
-
截取字符串
//String substring(int beginIndex) //String substring(int beginIndex, int endIndex) //包含起始位置,不包含结束位置,到结束位置的前一位 String string6 = "http://www.baidu.com:80/a/b/a/a?name=bing&age=18"; String subString1 = string6.substring(7); String subString2 = string6.substring(7,20); System.out.println(subString2);
-
大小写转换
//String toLowerCase() 转成小写 //String toUpperCase() 转成大写
-
去除空格,只能将字符串俩边的空格去掉
String string7 = " 1000 phone "; System.out.println(string7.trim()+"haha");
-
按字典顺序比较两个字符串
String s = "1000phone"; int value = s.compareTo("1000PHone"); System.out.println(value);
-
切割(//被作为刀的子字符串不会再被作为内容.)
String string8 = "a,b,c,d,e,f"; String[] strings = string8.split(","); for (int i = 0; i < strings.length; i++) { System.out.println(strings[i]); } String string9 = "1.0.0.0.p.h.o.n.e"; String[] strings1 = string9.split("\\.");//默认.是任意字符的意思,\\.将它转义成普通的. for (int i = 0; i < strings1.length; i++) { System.out.println("strings1:"+strings1[i]); }
-
-
不可变字符串(string)的常用方法(前面finfal修饰)
- 字符串本身[例如:(wq11)]不能发生改变,与指向字符串的引用无关 String a1=“wq11”;
- 可变字符串的常用方法
- StringBuilder(线程不安全)jdk1.5开启的 / StringBuffer.(线程安全)jdk1.0开启的 字符串本身可以发生变化, StringBuffer sbuffer=new StringBuffer(“wq11”);
- 在不考虑线程安全问题时,尽量使用StringBuilder,因为速度快,效率高
- 存储:StringBuffer append(boolean b) 从最后插入
- StringBuffer insert(int offset, boolean b) 从指定位置插入
- 删除:
StringBuffer delete(int start, int end) 删除一部分字符串
StringBuffer deleteCharAt(int index) 删除一个字符 - 修改:
StringBuffer replace(int start, int end, String str) 替换指定的子字符串 - 获取:
char charAt(int index)
正则表达式(Demo11)
验证qq号 qq.matches
- :转义字符
- \d:表示数字
- {4,12}表示位数最少4位,最多12位
- []:表示限定某一位
网址(Demo04)
- 网址: 协议+域名(ip地址)+端口+资源在服务器上的路径+查询条件
- 功能:实现客户端和服务器端的通信
- 协议:是制定一个统一的规范
- 服务器:中转站,数据处理,数据存储
[外链图片转存失败(img-W0ioO6d3-1567949517211)(E:\web开发项目文件\Typora\1564385721379.png)]
[外链图片转存失败(img-KC9cSJZP-1567949517213)(C:\Users\12811\AppData\Local\Temp\1564385910017.png)]
day02
一般类中代码执行顺序如下:
- 静态成员变量 > 实例成员变量 > 构造代码块 > 构造方法 ;
- 静态代码块 > 构造代码块 > 构造方法;
- 静态成员变量和静态代码块在类初始化阶段被初始化,且只初始化一次,两者顺 序按照代码从上到下执行;
静态代码块(父) > 静态代码块(子) > 实例成员变量(父) > 构造代码块(父) > 构造方法(父) > 实例成员变量(子) > 构造代码块(子) > 构造方法(子)
lambda表达式
- lambda 是只有一个抽象方法的接口,是简单的匿名内部类,是java8中的新特性
- 使得java可以函数式编程,在并发性能上迈出了实质性的一步
- 基本语法:(参数)->表达式 或 (参数)->{方法体} 1.形参列表:
形参列表允许省略形参类型,若形参列表中只有一个参数,形参列表的圆括号也可以省略代码
2.箭头(->)
必须通过英文的划线号和大于符号组成
3.代码块:
如果代码块只包含一条语句,lambda表达式允许省略代码块的花括号,那么这条语句就不要用花括号表示语句结束
4.返回值:
lambda代码块只有一条return语句,甚至可以省略return关键字
lambda表达式需要返回值,而它的代码块中仅有一条省略了return的语句,lambda表达式会自动返回这条语句的结果
5.lambda表达式可以直接作为函数的参数
当要实现只有一个接口的抽象函数时,使用lambda表达式能够更灵活。
6.lambda表达式类型
Lambda表达式的类型,也被称为"目标类型(target type)",Lambda表达式的目标类型必须是"函数式接口(functional interface)"
Java8新引入的概念,函数接口(functional interface)。它的定义是:一个接口,如果只有一个显式声明的
抽象方法,那么它就是一个函数接口。一般用@FunctionalInterface标注出来 (也可以不标记),函数式接口可以
包含多个default或static方法,但是只能声明一个抽象方法
@FuctionalInterface主要作用就是检查当前接口是不是函数接口
若想使用lambdaname目标必须是一个函数接口
7.lambda表达式中变量的使用
//如果是全局的变量直接用.如果是局部变量会被默认在前面添加final(被作为常量,类似于局部变量在局部内部类中使用的情况) - 比较lambda表达式和匿名内部类:
总结:lambda表达式就是简单的匿名内部类
1.匿名内部类可以为任意接口创建实例,不管接口包含多少个抽象方法,只要匿名内部类实现所有的抽象方法
即可; 但Lambda表达式只能为函数式接口创建实例(即只能有一个抽象方法)
2.匿名内部类可以为抽象类甚至是普通类创建实例;但Lambda表达式只能为函数式接口创建实例
3.匿名内部类实现的抽象方法的方法体允许调用接口中定义的默认(default)方法;但Lambda表达式的代码块
不允许调用接口中的默认(default)方法
集合
- 区分集合和数组
集合:可以存储不同类型的多个数据,但是类型只能是引用数据类型.
缺点:只能存储引用数据类型
优点:存储空间会随着存储数据的增大而增大,所以可以更加合理的利用内存空间,方法很多,方便我们实现功能.
数组:可以存储不同类型的多个数据,数据类型可是简单数据类型,也可是引用数据类型
缺点:创建的是一个定值,只能存储一些固定长度的数据,一旦存满了,就不能存储了
- 集合分类
Collection:—接口
List—接口:有序的,可重复的
ArrayList–类:底层的数据结构是数组,线程不安全的
Vector —类:底层的数据结构是数组,线程安全的
LinkedList–类:底层是链表,线程不安全的
实现去重的方法是list.contains()来实现 ,不包含则添加,包含则不添加
Set—接口 :不可重复的,无序的
HashSet–类:底层是哈希表,线程不安全的,不可排序,可以去重
TreeSet–类:底层是二叉树,线程不安全的,可以***实现***排序和去重
遍历set集合
//Set keySet() 遍历方法一
//原理:先得到所有的key,放入一个Set中,利用Set的迭代器进行遍历得到key,在利用key获取value
public static void main(String[] args) {
Map<String, String> map = new HashMap<>();
//1.增加
map.put("01", "java");
map.put("02", "html");
map.put("05", "iOS");
map.put("03", "BigData");
map.put("04", "iOS");
//方法一
test1(map);
//方法二
test2(map);
}
//方法一
public static void test1(Map<String, String> map) {
//第一步:先得到装着key的set
Set<String> set = map.keySet();
//第二步:遍历set,得到key,再根据key获取value
Iterator<String> iterator = set.iterator();
while (iterator.hasNext()) {
String key = iterator.next();
System.out.println("key:"+key+" value:"+map.get(key));
}
}
//Set<Map.Entry<K,V>> entrySet() 遍历方法二
//原理:先得到所有的entry,放入一个Set中,利用Set的迭代器进行遍历得到entry实体,在利用entry的方法获取key和value
//方法二
public static void test2(Map<String, String> map) {
//第一步:先得到装着Entry实体的set
Set<Map.Entry<String,String>> set = map.entrySet();
//第二步:遍历set,得到entry实体,再调用entry实体对象的方法获取key和value
Iterator<Map.Entry<String,String>> iterator = set.iterator();
while (iterator.hasNext()) {
Map.Entry<String, String> entry = iterator.next();
//通过setValue可以将map的原始值改变,但是一般在使用entrySet的时候,是进行遍历.不进行值的改变.
//entry.setValue("bingbing");
System.out.println("key1:"+entry.getKey()+" value1:"+entry.getValue());
}
Map:–接口
HashMap–类 底层是哈希表,线程不安全
TreeMap–类 底层是二叉树,线程不安全
TreeMap的注意点:
1.什么类型的数据类型可以作为key?
a:实现了Comparable接口的compareTo()方法 b:实现了Comparator接口的compare()方法
可以的代表:String,包装类,自定义的实现了要求的类
不可以的代表:数组,ArrayList,LinkedList(如果给他们建立的比较器也可以比较,但是不建议使用)
-
存储的键值对,key 唯一,value不唯一
-
增加: put
-
遍历方法一:keySet(),
-
遍历方法二:entrySet(),entry是一个接口,先将键值对放入entry对象中,再调用set进行遍历,得到entry实体,再调用entry实体对象获取key和value,
-
entry接口作为map接口的内接口,而且是静态的
-
HashMap去重的时候是对key进行操作的
-
HashMap的底层实现:HashMap里面实现一个静态内部类Entry,其重要的属性有 hash,key,value,next。
-
HashMap与HashTable的区别:HashTable线程安全,key值不能为空,HashMap线程不安全,key值可为空
-
可变参数(demo6):本身是一个数组,参数的个数可以可变 , 构成:数据类型+… 可变参数的特点:
-
增强for循环:for(元素:数组/Collectiom){ 内容 }
-
常用的判断
//boolean isEmpty() //空map!=null //boolean containsKey(K key) 是否包含当前的key //boolean containsValue(V value) 是否包含当前的value
- Collection接口
-
ArrayList的父类是abstractList
-
ArrayList通过迭代器进行遍历,Iterator iterator() //获取集合中的对象
Iterator:叫迭代器
hasnext():判断当前位置是否有值,有返回true,没有false
next():取出当前位置的值,并将指针指向下一个位置
public static void test(Collection collection) {
//4.获取:
//Iterator<E> iterator() //获取集合中的对象
/*
* Iterator:叫迭代器
* hasnext():判断当前位置是否有值,有返回true,没有false
* next():取出当前位置的值,并将指针指向下一个位置
*/
Iterator iterator = collection.iterator();
while (iterator.hasNext()) {
String value = (String) iterator.next();
System.out.println("iterator:"+value);
}
//注意点:
//1.直接再次使用第一次的iterator进行遍历,遍历失败.因为当前的指针已经指向了集合的最后.
//再次使用hasnext会直接返回false.所以如果想再次遍历,要重新获取迭代器对象.
while (iterator.hasNext()) {
String value = (String) iterator.next();
System.out.println("iterator1:"+value);
}
//2.注意:集合可以存储引用数据类型.可以存储不同的数据类型
collection.add(2);
//3.再次遍历--当集合中同时存在不同类型的数据时,需要进行容错处理和向下转型.
Iterator iterator1 = collection.iterator();
while (iterator1.hasNext()) {
Object object = iterator1.next();
if (object instanceof String) {
System.out.println("iterator2:"+object);
}
}
}
}
Collection:
- List:存储的数据是有序的(元素的存储顺序与添加元素的顺序一致),可以重复的.
Arraylist:底层的数据结构是数组,线程不安全的.特点:查找速度快,添加删除速度慢
Vector:底层的数据结构是数组,线程安全的.特点:查找速度快,添加删除速度慢
LinkedList:底层是链表,线程不安全的.特点:查找速度慢,添加删除速度快.
- Set:存储的数据是无序的,不可以重复
- vector底层遍历使用的是枚举器
Vector vector = new Vector<>();
vector.add("java");
vector.add("html");
vector.add("hadoop");
//遍历
//获取一个枚举器
Enumeration enumeration = vector.elements();
while (enumeration.hasMoreElements()) {
Object object = (Object) enumeration.nextElement();
System.out.println(object);
}
}
LinkedList特有的方法:(Demo04)
addFirst()//始终在首位添加
addLast()//始终在末尾添加
getFirst()//获取的对象不存在会发生异常 getLast()
removeFirst()//删除的对象不存在会发生异常 removeLast()
作业:用LinkedList模拟一下栈和队列
/**
* addFirst 堆栈 先进后出
* addLast 队列 先进先出
* @author 12811
*
*/
public class Tools {
LinkedList<String> ll = new LinkedList<String>();
public void add(String str){
ll.addLast(str);
}
public void remove(){
for (int i = 0; i < ll.size(); i++) {
String content = ll.get(i);
System.out.println(content);
}
}
}
public class 堆栈队列 {
/**
* 使用LinkedList模拟一个堆栈或者队列数据结构
* 堆栈:先进后出
* 队列:先进先出
* 用LinkedList中的addFirst(),addLast()方法
*/
public static void main(String[] args) {
Tools tools = new Tools();
tools.add("a");
tools.add("b");
tools.add("c");
tools.add("d");
tools.remove();
}
}
TreeSet的排序去重方法
- TreeSet的add方法实现的排序,去重.通过调用元素的compareTo方法
String类已经实现了Comparable接口 - 使用实现了Comparator接口的compare()方法的比较器对象进行比较
比较器
treeSet里面有比较器这个成员对象
//创建一个比较器类
class ComStrWithLength implements Comparator{
@Override
public int compare(Object o1, Object o2) {
//比较字符串的长度
if (!(o1 instanceof String)) {
throw new ClassCastException("类型转换错误");
}
if (!(o2 instanceof String)) {
throw new ClassCastException("类型转换错误");
}
//向下转型
String s1 = (String)o1;
String s2 = (String)o2;
//先按照长度比
int num = s1.length()-s2.length();
//长度相同,再按照字典顺序比
return num==0?s1.compareTo(s2):num;
}
}
public static void main(String[] args) {
//创建比较器对象
ComStrWithLength comStrWithLength = new ComStrWithLength();
//将比较器对象交给TreeSet
Set set = new TreeSet<>(comStrWithLength);
/*
* TreeSet的add方法实现的排序,去重.通过调用元素的compareTo方法
* String类已经实现了Comparable接口
*/
set.add("java");
set.add("hadoop");
set.add("spark");
set.add("HDFS");
set.add("HDFS");
set.add("Mapreduce");
System.out.println(set);
}
泛型
泛型:通过<数据类型>接收一种数据类型,在编译的时候会使用这种数据类型检测集合中的元素,如果元素不是<>中规定的类型,就不允许添加到当前的集合中(编译失败)
泛型作用:
.使用了泛型不再需要进行容错处理,向下转型,强制类型转换----简化代码
.将运行阶段的问题提前到编译阶段检查,提高了代码的安全性和编程效率
泛型可以修饰的地方: 类, 方法, 接口
泛型应用在方法上
1.方法上的泛型与类上的泛型保持一致
2.方法上独立使用泛型 注意:泛型在使用之前一定要先进行定义
定义的方式:在当前方法的最前面添加<泛型>
作用:让方法与方法内的泛型保持一致
3.静态方法上使用泛型 必须独立使用
方式:在static 后面定义泛型 <泛型>
class Dog<F>{
//1.方法上的泛型与类上的泛型保持一致
public void eat(F f) {
System.out.println(f);
}
//2.方法上独立使用泛型
/*
* 注意:泛型在使用之前一定要先进行定义
* 定义的方式:在当前方法的最前面添加<泛型>
* 作用:让方法与方法内的泛型保持一致
*/
public <E> void song(E e) {
ArrayList<E> arrayList = new ArrayList<>();
System.out.println(e);
}
//3.静态方法上使用泛型
/*
* 必须独立使用
* 方式:在static 后面定义泛型 <泛型>
*/
public static <W> void show(W w) {
}
}
泛型应用在接口上(Demo13)
/*
* 泛型应用在接口上
* 我们主要研究的是实现接口的子类上的泛型是如何使用的
*/
public class Demo13 {
public static void main(String[] args) {
//1.子类上的泛型与接口上的一致
Pig<String> pig = new Pig<>();
pig.show("哈哈");
//2.接口上使用泛型,子类上不用泛型
Bird bird = new Bird();
bird.show("haha");
Class<?> class1 = bird.getClass();
}
}
interface Inte<E>{
public void show(E e);
}
//1.子类上的泛型与接口上的一致
/* 类上的泛型确定了,接口上的泛型就确定了,方法上的泛型就确定了
*/
class Pig<E> implements Inte<E>{
@Override
public void show(E e) {
System.out.println(e);
}
}
//2.接口上使用泛型,子类上不用泛型
/*在实现的接口位置必须指定一个具体的泛型
*
* 方法使用泛型的情况:
* 1.如果是重写的方法,泛型与接口一致
* 2.如果是子类自己的方法,可以与接口一致,也可以有自己的泛型
*/
class Bird implements Inte<String>{
public void show(String e) {};
}
泛型应用在类上
方式:在类的后面直接添加,E代表任意一种数据类型,注意:这里不一定是E,任意字母都可以
- 这就是在类上使用泛型
- 在类上确定的泛型可以直接在方法上使用
通配符
?:通配符,可以表示一种或几种数据类型
- 限制上限:<? extends E>:限制的是整个的<>可以取的泛型类型的上限是E,<>中可以取的类型是E及E的子类
- 限制下限:<? super E>::限制的是整个的<>可以取的泛型类型的下限是E,<>中可以取的类型是E及E的父类
day03
map部分写在day02
数据结构讲解(浅过)
节点:一个节点包含俩部分:数据域和指针域,数据域内部放的是数据,指针域内部放的是下一个节点的地址
1.为什么用HashMap?
HashMap是一个散列桶(数组和链表),它存储的内容是键值对(key-value)映射HashMap采用了数组和链表的数据结构,能在查询和修改方便继承了数组的线性查找和链表的寻址修改HashMap是非synchronized,所以HashMap很快HashMap可以接受null键和值,而Hashtable则不能(原因就是equlas()方法需要对象,因为HashMap是后出的API经过处理才可以)
2.HashMap的工作原理是什么?
HashMap是基于hashing的原理,我们使用put(key, value)存储对象到HashMap中,使用get(key)从HashMap中获取对象。当我们给put()方法传递键和值时,我们先对键调用hashCode()方法,计算并返回的hashCode是用于找到Map数组的bucket位置来储存Node 对象。这里关键点在于指出,HashMap是在bucket中储存键对象和值对象,作为Map.Node 。
[外链图片转存失败(img-soaTVgDr-1567949517214)(E:/%E5%8D%83%E5%B3%B0%E5%9F%B9%E8%AE%AD/week2-03%E8%B5%84%E6%96%99/%E8%B5%84%E6%96%99/%E8%B5%84%E6%96%99/hashMap%E5%92%8CConcurrentMap/HashMap%E5%92%8CConcurrentMap.assets/%E5%9B%BE1.png)]
以下是HashMap初始化 ,简单模拟数据结构Node[] table=new Node[16] 散列桶初始化,tableclass Node {hash;//hash值key;//键 value;//值 node next;//用于指向链表的下一层(产生冲突,用拉链法)}
以下是具体的put过程(JDK1.8版)
1、对Key求Hash值,然后再计算下标
2、如果没有碰撞,直接放入桶中(碰撞的意思是计算得到的Hash值相同,需要放到同一个bucket中)
3、如果碰撞了,以链表的方式链接到后面
4、如果链表长度超过阀值( TREEIFY THRESHOLD==8),就把链表转成红黑树,链表长度低于6,就把红黑树转回链表
5、如果节点已经存在就替换旧值
6、如果桶满了(容量16*加载因子0.75),就需要 resize(扩容2倍后重排)
以下是具体get过程(考虑特殊情况如果两个键的hashcode相同,你如何获取值对象?)当我们调用get()方法,HashMap会使用键对象的hashcode找到bucket位置,找到bucket位置之后,会调用keys.equals()方法去找到链表中正确的节点,最终找到要找的值对象。
[外链图片转存失败(img-hHJgEhcg-1567949517214)(E:/%E5%8D%83%E5%B3%B0%E5%9F%B9%E8%AE%AD/week2-03%E8%B5%84%E6%96%99/%E8%B5%84%E6%96%99/%E8%B5%84%E6%96%99/hashMap%E5%92%8CConcurrentMap/HashMap%E5%92%8CConcurrentMap.assets/%E5%9B%BE2.png)]
3.如果HashMap的大小超过了负载因子(load factor)定义的容量,怎么办?
默认的负载因子大小为0.75,也就是说,当一个map填满了75%的bucket时候,和其它集合类(如ArrayList等)一样,将会创建原来HashMap大小的两倍的bucket数组,来重新调整map的大小,并将原来的对象放入新的bucket数组中。这个过程叫作rehashing,因为它调用hash方法找到新的bucket位置。这个值只可能在两个地方,一个是原下标的位置,另一种是在下标为<原下标+原容量>的位置
4. HashMap和ConcurrentHashMap的区别
首先Map是接口,一般而言concurrentHashMap是线程安全的,具体实现
在1.7采取的segment分段锁,有点类似于16个线程安全的hashtable组合成了一个concurrenthashmap,不同分段操作不需要上锁,同一个分段才需要上锁,读不上锁,写上锁。锁的粒度更加精细,而在1.8中而是直接用Node数组+链表+红黑树的数据结构来实现,并发控制使用Synchronized和CAS来操作,整个看起来就像是优化过且线程安全的HashMap,而原有的Segment的数据结构,但是已经简化了属性,只是为了兼容旧版本,反观HashMap是传统Map接口中所使用的实现了,操作速度快,但是线程是不安全的
5. segment分段锁
1、线程不安全的HashMap
因为多线程环境下,使用Hashmap进行put操作会引起死循环,导致CPU利用率接近100%,所以在并发情况下不能使用HashMap。
2、效率低下的HashTable容器
HashTable容器使用synchronized来保证线程安全,但在线程竞争激烈的情况下HashTable的效率非常低下。因为当一个线程访问HashTable的同步方法时,其他线程访问HashTable的同步方法时,可能会进入阻塞或轮询状态。如线程1使用put进行添加元素,线程2不但不能使用put方法添加元素,并且也不能使用get方法来获取元素,所以竞争越激烈效率越低。
3、ConcurrentHashMap分段锁技术
ConcurrentHashMap是Java 5中支持高并发、高吞吐量的线程安全HashMap实现。
我们以ConcurrentHashMap来说一下分段锁的含义以及设计思想,ConcurrentHashMap中的分段锁称为Segment,类似于HashMap(JDK7与JDK8中HashMap的实现)的结构,即内部拥有一个Entry数组,数组中的每个元素又是一个链表;同时又是一个ReentrantLock(Segment继承了ReentrantLock)。
当需要put元素的时候,并不是对整个hashmap进行加锁,而是先通过hashcode来知道他要放在哪一个分段中,然后对这个分段进行加锁,所以当多线程put的时候,只要不是放在一个分段中,就实现了真正的并行的插入。
但是,在统计size的时候,可就是获取hashmap全局信息的时候,就需要获取所有的分段锁才能统计。
分段锁的设计目的是细化锁的粒度,当操作不需要更新整个数组的时候,就仅仅针对数组中的一项进行加锁操作。
6.红黑树
红黑树是为了解决二叉查找树的缺陷,二叉查找树在特殊情况下会变成一条线性结构(这就跟原来使用链表结构一样了,造成很深的问题),遍历查找会非常慢。而红黑树在插入新数据后可能需要通过左旋,右旋、变色这些操作来保持平衡,引入红黑树就是为了查找数据快,解决链表查询深度的问题,我们知道红黑树属于平衡二叉树,但是为了保持“平衡”是需要付出代价的,但是该代价所损耗的资源要比遍历线性链表要少,所以当长度大于8的时候,会使用红黑树,如果链表长度很短的话,根本不需要引入红黑树,引入反而会慢。
[外链图片转存失败(img-GBmeTbge-1567949517216)(E:/%E5%8D%83%E5%B3%B0%E5%9F%B9%E8%AE%AD/week2-03%E8%B5%84%E6%96%99/%E8%B5%84%E6%96%99/%E8%B5%84%E6%96%99/hashMap%E5%92%8CConcurrentMap/HashMap%E5%92%8CConcurrentMap.assets/%E5%9B%BE3.png)]
[外链图片转存失败(img-fqm8EyHq-1567949517216)(C:\Users\bihai\Desktop\BigData1924\day08\资料\hashMap和ConcurrentMap\HashMap和ConcurrentMap.assets\临时1.png)]
1、每个节点非红即黑
2、根节点总是黑色的
3、如果节点是红色的,则它的子节点必须是黑色的(反之不一定)
4、每个叶子节点都是黑色的空节点(NIL节点)
5、从根节点到叶子节点或空子节点的每条路径,必须包含相同数目的黑色节点(即相同的黑色高度)
7.大 O 表示法
什么是大 O 表示法?
我们常常会听到有人说,“这个算法在最糟情况下的运行时间是 O(n^2) 而且占用了 O(n) 大小的空间”,他的意思是这个算法有点慢,不过没占多大空间。
这里的 O(n^2)
和 O(n)
就是我们通常用来描述算法复杂度的大 O 表示法。
大 O 表示法能让你对一个算法的运行时间和占用空间有个大概概念。
大 O 表示法怎么看?怎么用?
假设一个算法的时间复杂度是 O(n),n 在这里代表的意思就是数据的个数。举个例子,如果你的代码用一个循环遍历 100 个元素,那么这个算法就是 O(n),n 为 100,所以这里的算法在执行时就要做 100 次工作。
大O符号是关于一个算法的最坏情况的。比如说,你要从一个大小为 100 的数组(数组中存的都是数字)中找出一个数值等于 10 的元素,我们可以从头到尾扫描一遍,这个复杂度就是 O(n),这里 n 等于 100,实际上呢,有可能第 1 次就找到了,也有可能是第 100 次才找到,但是大 O 表示法考虑的是最坏的情况,也就是一个算法理论上要执行多久才能覆盖所有的情况。
常见的时间复杂度有:
常数阶O(1),对数阶O(log2n),线性阶O(n),线性对数阶O(nlog2n),平方阶O(n2),立方阶O(n3),…, k次方阶O(nk),指数阶O(2n)。随着问题规模n的不断增大,上述时间复杂度不断增大,算法的执行效率越低。
说明:
大部分情况下你用直觉就可以知道一个算法的大 O 表示法
大 O 表示法只是一种估算,当数据量大的时候才有用
这种东西仅仅在比较两种算法哪种更好的时候才有点用。但归根结底,你还是要实际测试之后才能得出结论。而且如果数据量相对较小,哪怕算法比较慢,在实际使用也不会造成太大的问题。
要知道一个算法的大 O 表示法通常要通过数学分析。在这里我们不会涉及具体的数学,不过知道不同的值意味着什么会很有用。所以这里有一张方便的表。n 在这里代表的意思是数据的个数。举个例子,当对一个有 100 个元素的数组进行排序时,n = 100。
Big-O | 名字 | 描述 |
---|---|---|
O(1) | 常数级 | 最好的。不论输入数据量有多大,这个算法的运行时间总是一样的。例子: 基于索引取出数组中对应的元素。 |
O(log n) | 对数级 | 相当好。这种算法每次循环时会把需要处理的数据量减半。如果你有 100 个元素,则只需要七步就可以找到答案。1000 个元素只要十步。100,0000 元素只要二十步。即便数据量很大这种算法也非常快。例子:二分查找。 |
O(n) | 线性级 | 还不错。如果你有 100 个元素,这种算法就要做 100 次工作。数据量翻倍那么运行时间也翻倍。例子:线性查找。 |
O(n log n) | 线性对数级 | 还可以。比线性级差了一些,不过也没那么差劲。例子:最快的通用排序算法。 |
O(n^2) | 二次方级 | 有点慢。如果你有 100 个元素,这种算法需要做 100^2 = 10000 次工作。数据量 x 2 会导致运行时间 x 4 (因为 2 的 2 次方等于 4)。例子:循环套循环的算法,比如插入排序。 |
O(n^3) | 三次方级 | 特别慢。如果你有 100 个元素,那么这种算法就要做 100^3 = 100,0000 次工作。数据量 x 2 会导致运行时间 x 8。例子:矩阵乘法。 |
O(2^n) | 指数级 | 超级慢。这种算法你要想方设法避免,但有时候你就是没得选。加一点点数据就会把运行时间成倍的加长。例子:旅行商问题。 |
O(n!) | 阶乘级 | 比蜗牛还慢!不管干什么都要跑个 N 年才能得到结果。 |
大部分情况下你用直觉就可以知道一个算法的大 O 表示法。比如说,如果你的代码用一个循环遍历你输入的每个元素,那么这个算法就是 O(n)。如果是循环套循环,那就是 O(n^2)。如果 3 个循环套在一起就是 O(n^3),以此类推。
注意,大 O 表示法只是一种估算,当数据量大的时候才有用。
例如冒泡排序:
假设需要比较的数组中有N个元素,也就意味着外层循环会执行N次即O(n),而内层循环也是要执行N,所以此时冒泡排序的O(n^2)
最好情况下的时间复杂度:如果元素本来就是有序的,那么一趟冒泡排序既可以完成排序工作,比较和移动元素的次数分别是n-1和0,因此最好情况的时间复杂度为O(n)。
最差情况的时间复杂度:如果数据元素本来就是逆序的,进行n-1趟排序,所需比较和移动次数分别为n(n-1)/2和3n(n-1)/2。因此最坏情况子下的时间复杂度为O(n^2)。
day04
多线程
程序:一个可执行的文件
进程 :一个正在运行的程序,也可以理解为在内存中开辟了一块空间
线程 :负责程序的运行,可以看作一条执行的通道或者执行单元,所以我们通常将进程的工作理解为线程的工作
多线程的作用:同一时间干多件事情
为什么调用start(),来开启线程? 因为cpu有随机性(不可控)
通过start()开启线程的时候JVM自动的调用了run()方法,
this只能在非静态方法中使用
wait(demo07)
创建线程对象
- 默认情况下,主线程和垃圾回收线程都是由系统创建的,但是我们需要完成自己的功能----创建自己的线程对象
- java将线程面向对象了,形成的类就是Thread,在Thread类内部执行任务的方法叫run()
- 注意:如果想让run作为任务区,必须让他去被自动调用.我们通过执行start()方法,来开启线程,继而实现run方法的自动调用.
一:直接使用Thread创建线程对象
分析:由于我们实现的实际功能Thread类是决定不了的,所以没有办法将我们的功能放入Thread的run方法里
所以Thread的run 方法是一个空方法.如果我们想实现自己的功能,可以写Thread类的子类,重写run方法
//2.使用子类创建对象
MyThread t0 = new MyThread("t0");
MyThread t1 = new MyThread("t1");
t0.start();//开启线程
t1.start();//开启线程
重写run方法,实现我们的功能.run就是我们的任务区
Thread.currentThread():获取的是当前的线程
Thread.currentThread().getName():线程的名字
二:实现多线程的方式两种:
- 第一种方式:通过创建Thread子类的方式实现功能----线程与任务绑定在了一起,操作不方法
- 第二种:将任务从线程中分离出来,哪个线程需要工作,就将任务交给谁,操作方便
三:线程的停止
- 通过一个标识结束线程 false
- 调用stop方法-----因为有固态的安全问题,所以系统不建议使用
- 调用interrupt方法-------如果目标线程等待很长时间(例如基于一个条件变量),则应该使用interrupt 方法来中断该等待。
thread.interrupt();
四:守护
-
守护线程:相当于后台线程,依赖于前台线程,正常情况下,当前台线程结束的时候,不管守护线程有没有结束,都会立刻结束 t0.setDaemon(true);
-
典型的守护线程:垃圾回收线程
MyTest1 myTest1 = new MyTest1(); Thread t0 = new Thread(myTest1); /* * 当程序调用setDaemon方法时,并且将参数设置成true.当前线程就变成了守护线层. * 注意:这个方法一定要在start方法之前调用 */ t0.setDaemon(true); t0.start();
五:高于主线程的方法 join()方法
- 原理:线程一旦调用了join方法,他的优先级会高于主线程,主线程会等当前的线程执行完后再去执行
- 注意点: 优先级只比main线程高,对其他的线程没有影响
MyTest2 myTest1 = new MyTest2();
Thread t0 = new Thread(myTest1);
Thread t1 = new Thread(myTest1);
t0.start();
t1.start();
/*
* 当线程开始工作后,让t0调用join方法,让他的优先级高于main线程
* 注意:join方法必须在线程开始工作后,执行.
*/
try {
t0.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+" 主线程"+i);
}
六:单例线程
public class Demo13 {
public static void main(String[] args) {
Test1 test = new Test1();
Thread t0 = new Thread(test);
Thread t1 = new Thread(test);
t0.start();
t1.start();
}
}
class Test1 implements Runnable{
public void run() {
SingleInstance2 singleInstance2 = SingleInstance2.getInstance();
}
}
//饿汉式,由于公共方法中只有一行公共的代码,所以不会产生线程安全问题
class SingleInstance1{
private static final SingleInstance1 s = new SingleInstance1();
private SingleInstance1() {
}
public static SingleInstance1 getInstance() {
return s;
}
}
//懒汉式,
class SingleInstance2{
private static SingleInstance2 s = null;
private SingleInstance2() {
}
public static SingleInstance2 getInstance() {
if (s == null) {//尽量减少线程安全代码的判断次数,提高效率
synchronized (SingleInstance2.class) {//使用同步代码块儿实现了线程安全
if (s == null) {
s = new SingleInstance2();
}
}
}
return s;
}
}
七:生产者消费者
-
单生产者单消费者
public class Demo9 { public static void main(String[] args) { //准备数据 Product product = new Product(); //准备任务 Producer producer = new Producer(product); Consumer consumer = new Consumer(product); //准备线程 Thread proThread = new Thread(producer); Thread conThread = new Thread(consumer); //开启线程 proThread.start(); conThread.start(); } } //创建产品 class Product{ String name;//产品的名字 double price;//产品的价格 int count;//生产的产品数量 //标识 boolean flag = false; //准备生产 public synchronized void setProduce(String name,double price){ if (flag == true) { try { wait();//让生产线程等待 } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } this.name = name; this.price = price; System.out.println(Thread.currentThread().getName()+" 生产了:"+this.name+" 产品的数量:"+this.count+" 价格:"+this.price); count++; flag = ! flag; notify();//唤醒消费线程 } //准备消费 public synchronized void getConsume() { if (flag == false) { try { wait();//让消费线程等待 } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } System.out.println(Thread.currentThread().getName()+" 消费了:"+this.name+" 产品的数量:"+this.count+" 价格:"+this.price); //唤醒生产线程 flag = ! flag; notify(); } } //创建生产任务 class Producer implements Runnable{ Product product; public Producer(Product product) { super(); this.product = product; } public void run() { while (true) { product.setProduce("bingbing", 10); } } } //创建消费任务 class Consumer implements Runnable{ Product product; public Consumer(Product product) { super(); this.product = product; } public void run() { while (true) { product.getConsume(); } } }
-
多生产者多消费者(使用synchronized)
public class Demo10 { public static void main(String[] args) { //准备数据 Product1 product = new Product1(); //准备任务 Producer1 producer = new Producer1(product); Consumer1 consumer = new Consumer1(product); //准备线程 Thread proThread1 = new Thread(producer); Thread proThread2 = new Thread(producer); Thread conThread1 = new Thread(consumer); Thread conThread2 = new Thread(consumer); //开启线程 proThread1.start(); conThread1.start(); proThread2.start(); conThread2.start(); } } //创建产品 class Product1{ String name;//产品的名字 double price;//产品的价格 int count;//生产的产品数量 //标识 boolean flag = false; //准备生产 public synchronized void setProduce(String name,double price){ while (flag == true) { try { wait();//让生产线程等待 } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } this.name = name; this.price = price; System.out.println(Thread.currentThread().getName()+" 生产了:"+this.name+" 产品的数量:"+this.count+" 价格:"+this.price); count++; flag = ! flag; //notify();//唤醒消费线程 notifyAll(); } //准备消费 public synchronized void getConsume() { while (flag == false) { try { wait();//让消费线程等待 } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } System.out.println(Thread.currentThread().getName()+" 消费了:"+this.name+" 产品的数量:"+this.count+" 价格:"+this.price); //唤醒生产线程 flag = ! flag; //notify(); notifyAll(); } } //创建生产任务 class Producer1 implements Runnable{ Product1 product; public Producer1(Product1 product) { super(); this.product = product; } public void run() { while (true) { product.setProduce("bingbing", 10); } } } //创建消费任务 class Consumer1 implements Runnable{ Product1 product; public Consumer1(Product1 product) { super(); this.product = product; } public void run() { while (true) { product.getConsume(); } } }
-
多生产者多消费者(同步使用Lock,唤醒等待使用Condition)
Condition
将 Object
监视器方法(wait
、notify
和 notifyAll
)分解成截然不同的对象,其中,Lock
替代了 synchronized
方法和语句的使用,Condition
替代了 Object 监视器方法的使用。
public class Demo11 {
public static void main(String[] args) {
//准备数据
Product2 product = new Product2();
//准备任务
Producer2 producer = new Producer2(product);
Consumer2 consumer = new Consumer2(product);
//准备线程
Thread proThread1 = new Thread(producer);
Thread proThread2 = new Thread(producer);
Thread conThread1 = new Thread(consumer);
Thread conThread2 = new Thread(consumer);
//开启线程
proThread1.start();
conThread1.start();
proThread2.start();
conThread2.start();
}
}
//创建产品
class Product2{
String name;//产品的名字
double price;//产品的价格
int count;//生产的产品数量
//标识
boolean flag = false;
//创建锁对象
Lock lock = new ReentrantLock();
//用于生产任务的Condition
Condition proCon = lock.newCondition();
//用于消费任务的Condition
Condition conCon = lock.newCondition();
//准备生产
public void setProduce(String name,double price){
try {
lock.lock();//获取锁
while (flag == true) {
try {
//wait();//让生产线程等待
proCon.await();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
this.name = name;
this.price = price;
System.out.println(Thread.currentThread().getName()+" 生产了:"+this.name+" 产品的数量:"+this.count+" 价格:"+this.price);
count++;
flag = ! flag;
//notify();//唤醒消费线程
//notifyAll();
conCon.signal();
}finally {
lock.unlock();//释放锁
}
}
//准备消费
public void getConsume() {
try {
lock.lock();
while (flag == false) {
try {
//wait();//让消费线程等待
conCon.await();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+" 消费了:"+this.name+" 产品的数量:"+this.count+" 价格:"+this.price);
//唤醒生产线程
flag = ! flag;
//notify();
//notifyAll();
proCon.signal();
}finally {
lock.unlock();
}
}
}
//创建生产任务
class Producer2 implements Runnable{
Product2 product;
public Producer2(Product2 product) {
super();
this.product = product;
}
public void run() {
while (true) {
product.setProduce("bingbing", 10);
}
}
}
//创建消费任务
class Consumer2 implements Runnable{
Product2 product;
public Consumer2(Product2 product) {
super();
this.product = product;
}
public void run() {
while (true) {
product.getConsume();
}
}
}
-
死锁:出现的情况有两种
1.所有的线程处于等待状态
2.锁之间进行嵌套调用 -
生产任务与消费任务共用一个数据–产品类
要求:最终也要实现一次生产一次消费
错误描述:当有两个生产线程,两个消费线程同时存在的时候,有可能出现生产一次,消费多次或者生产多次消费一次的情况.
原因:当线程被重新唤醒之后,没有判断标记,直接执行了下面的代码
解决办法:将标记处的if改成while
八:线程通信
- 面向对象的精髓:谁的活儿谁干,不是你的活儿不要干,将数据准备的活儿从输入任务输出任务提出来,放入数据类
- 实例:打印机打印----一次输入一次输出 两个线程:输入线程和输出线程 两个任务区:输入任务,输出任务 一份数据
public class Demo8 {
public static void main(String[] args) {
//两个线程:输入线程和输出线程
//1.准备数据
Des2 des = new Des2();
//2.准备两个任务
Input2 input = new Input2(des);
Output2 output = new Output2(des);
//3.准备两个线程
Thread in = new Thread(input);
Thread out = new Thread(output);
//4.开启线程
in.start();
out.start();
}
}
//创建数据类
class Des2{
String name;
String sex;
boolean flag;//控制唤醒和等待状态的切换
//负责输入
public void setData(String name,String sex) {
if (flag == true) {//当flag值为true,就让当前的线程处于等待状态
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}//当执行这行代码的时候,这里对应的是哪个线程,就操作的是哪个线程
}
this.name = name;
this.sex = sex;
flag = !flag;
notify();//唤醒的是通一把锁下的线程,因为现在只有一个输入线程,一个输出线程.所以这里唤醒的是输出线程
//当线程池中没有被当前的锁标记的线程可唤醒时,我们成为空唤醒,空唤醒不影响程序的执行.
}
//负责输出
public void getData() {
if (flag == false) {//让输出线程等待
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println("姓名:"+name+" 性别:"+sex);
flag = ! flag;
notify();//唤醒的是输入线程
}
}
//两个任务区:输入任务,输出任务
class Input2 implements Runnable{
Des2 des = null;
public Input2(Des2 des) {
super();
this.des = des;
}
public void run() {
int i=0;
while (true) {
/*
* 需要给输入任务和输出任务同时加一把锁,保证两个任务之间是同步的
* 给两个任务加一把锁:可以是des或者Object.class
* 分析:
* 不建议使用Object.class:由于Object的使用范围太大,可能造成不必要的错误.
* 使用des最合适,因为他只被当前的两个任务共享.
*
*注意:对于当前的情况只给一个线程加锁,无法实现两个线程的同步.
*/
synchronized (des) {
if (i == 0) {
des.setData("万表哥", "男");
}else {
des.setData("蔡表妹", "女");
}
i=(i+1)%2;
}
}
}
}
class Output2 implements Runnable{
Des2 des = null;
public Output2(Des2 des) {
super();
this.des = des;
}
public void run() {
while (true) {
synchronized (des) {
des.getData();
}
}
}
}
九:线程同步
- 原理:线程同步是指多线程通过特定的设置(如互斥量,事件对象,临界区)来控制线程之间的执行顺序(即所谓的同步)也可以说是在线程之间通过同步建立起执行顺序的关系,如果没有同步,那线程之间是各自运行各自的!
十:比较synchronized和Lock
1.synchronized:从jdk1.0就开始使用的同步方法-称为隐式同步
synchronized(锁对象){//获取锁 我们将锁还可以称为锁旗舰或者监听器
同步的代码
}//释放锁
2.Lock:从jdk1.5开始使用的同步方法-称为显示同步
原理:Lock本身是接口,要通过他的子类创建对象干活儿
使用过程:
首先调用lock()方法获取锁
进行同步的代码块儿
使用unlock()方法释放锁
使用的场景:
当进行多生产者多消费者的功能时,使用Lock,其他的都使用synchronized
使用效率上:Lock高于synchronized
day05
IO流
一:简介
- 作用:实现俩个设备之间数据的传递
- 设备:磁盘,内存,键盘,文件,网络,控制台
- [外链图片转存失败(img-rt0zmwpE-1567949517218)(E:\千锋uml课设\1564798504189.png)]
- 分类: 根据操作的方式:输入流和输出流 根据数据的类型:字节流和字符流
- 字节流:传输的是字节,可以操作任何类型的数据 字符流: 传输的是字节,不同点是在传输过程中加入了编码的操作,让我们的操作更加方便
- 字节输入流:InputStream 字节输出流:OutputStream 字符写出流:Writer 字符读入流:Reader
二:文件读入写出流
- 写入文件----------写出流---------- FileWrite
- 保证路径真实,写write内容完了,刷新,关闭流
- 文件的续写FileWriter(String string, Boolean value),当value的值为true的时候,文件内容不会被覆盖,会在后面续写
- 读出流:返回的 是ASCII码值[外链图片转存失败(img-FDylgZMc-1567949517218)(E:\千锋uml课设\1564801579171.png)]
[外链图片转存失败(img-1zNq37Vm-1567949517220)(E:\千锋uml课设\1564888257512.png)]
三:字符缓冲流(demo07,08)
-
定义:为了提高读写的能力,本身没有读写的能力,借助与字符流来实现
-
换行:newLine 一次读一行 readLine 返回值为null
-
读入写出配合使用
-
字符缓冲流分类: 字符缓冲读入流:BufferedReader 没有读的能力 字符缓冲写出流:BufferedWriter 没有写的能力
public class Demo6 { public static void main(String[] args) throws IOException { //0.创建字符写出流对象 //1.创建字符缓冲写出流对象 BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("text3.txt")); //2.写 bufferedWriter.write("bingbing"); //windows系统 换行符 \r\n linux/unix 换行符 \n //bufferedWriter.write("\r\n");//换行 bufferedWriter.newLine();//换行---支持跨平台 bufferedWriter.write("chenchen"); //3.关闭资源 a.关闭内部的写出流 b.关闭自己 c.刷新 bufferedWriter.close(); } }
-
相对路径:从路径中间的某个部位开始一直到当前的文件 绝对路径:一个文件的完整路径,从根目录开始到当前的文件
-
补充知识:
\代表转义字符 \t:制表符 \n换行符 \:表示普通的\
在代表路径的时候,\与/是一个意思.
-
完成文件的复制(使用读入流和写出流)
public class Demo5 { public static void main(String[] args) throws IOException { //1.创建读入流和写出流 FileReader fileReader = new FileReader("D:\\workspace/BigData1923N19\\src\\com\\qf\\test\\Demo1.java"); FileWriter fileWriter = new FileWriter("Demo1copy.java"); //读写 //使用一次读一个 int num =0 ; // while ((num = fileReader.read()) != -1) { // fileWriter.write(num);//可以实现自动转换 // fileWriter.flush(); // } //使用一次读多行 char[] arr = new char[10]; while ((num = fileReader.read(arr)) != -1) { fileWriter.write(arr,0,num);//可以实现自动转换 fileWriter.flush(); } fileReader.close(); fileWriter.close(); } }
-
完成文件的复制(使用缓冲流实现文件的复制)
public class Demo8 { public static void main(String[] args) throws IOException { //1.创建读入流和写出流 FileReader fileReader = new FileReader("D:\\workspace/BigData1923N19\\src\\com\\qf\\test\\Demo1.java"); FileWriter fileWriter = new FileWriter("Demo1copy.java"); BufferedReader bufferedReader = new BufferedReader(fileReader); BufferedWriter bufferedWriter = new BufferedWriter(fileWriter); //读写 //使用一次读一个 int num =0 ; while ((num = bufferedReader.read()) != -1) { bufferedWriter.write(num);//可以实现自动转换 fileWriter.flush(); } //使用一次读多行 // char[] arr = new char[10]; // while ((num = bufferedReader.read(arr)) != -1) { // bufferedWriter.write(arr,0,num);//可以实现自动转换 // fileWriter.flush(); // } //使用一次读一行 String data = null; while ((data = bufferedReader.readLine()) != null) { bufferedWriter.write(data); bufferedWriter.newLine(); bufferedWriter.flush(); } bufferedReader.close(); bufferedWriter.close(); } }
Writer(“Demo1copy.java”);
//读写
//使用一次读一个
int num =0 ;
// while ((num = fileReader.read()) != -1) {
// fileWriter.write(num);//可以实现自动转换
// fileWriter.flush();
// }
//使用一次读多行
char[] arr = new char[10];
while ((num = fileReader.read(arr)) != -1) {
fileWriter.write(arr,0,num);//可以实现自动转换
fileWriter.flush();
}
fileReader.close();
fileWriter.close();
}
}
8. 完成文件的复制(使用缓冲流实现文件的复制)
public class Demo8 {
public static void main(String[] args) throws IOException {
//1.创建读入流和写出流
FileReader fileReader = new FileReader(“D:\workspace/BigData1923N19\src\com\qf\test\Demo1.java”);
FileWriter fileWriter = new FileWriter(“Demo1copy.java”);
BufferedReader bufferedReader = new BufferedReader(fileReader);
BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);
//读写
//使用一次读一个
int num =0 ;
while ((num = bufferedReader.read()) != -1) {
bufferedWriter.write(num);//可以实现自动转换
fileWriter.flush();
}
//使用一次读多行
// char[] arr = new char[10];
// while ((num = bufferedReader.read(arr)) != -1) {
// bufferedWriter.write(arr,0,num);//可以实现自动转换
// fileWriter.flush();
// }
//使用一次读一行
String data = null;
while ((data = bufferedReader.readLine()) != null) {
bufferedWriter.write(data);
bufferedWriter.newLine();
bufferedWriter.flush();
}
bufferedReader.close();
bufferedWriter.close();
}
}