(实时更新)
一.Collection(可以用集合工具类Collections)
基本方法
查询快,增删慢,底层数组
jdk7类似于饿汉,jdk8类似于懒汉式
linkedList查询慢,增删快,底层链表
作为List使用时,一般采用add / get方法来 加入/获取对象
作为Queue使用时,才会采用 offer/poll/peek等方法
stack 使用时候,push/pop
实现stack
public class Stackvector{ private linkedList storage = new linkedList (); public void push(T v){ storage.addFirst(v); } public T peek(){ return storage.getFirst(); } public T pop(){ return storage.removeFirst(); } public boolean empty(){ return storage.isEmpty(); } public String toString(){ return storage.toString(); }
同步单线程,没有collection快
2.set不包含重复的元素,无序的元素
TreeSet有序,要求同类型的,通过实现Comparable接口compareTo()比较内部,或者Comparator外部,采用红黑树结构
public static void main(String[] args) {
Set sse=new TreeSet(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof phone && o2 instanceof phone) {
phone p1 = (phone) o1;
phone p2 = (phone) o2;
return p1.age-p2.age;
}
return 0;
}
});
sse.add(new phone(20,"zjw"));
sse.add(new phone(8,"z"));
sse.add(new phone(0,"z"));
Iterator i=sse.iterator();
while (i.hasNext()){
Object next = i.next();
phone sb=(phone)next;
System.out.println(sb);
}
}
Hashset
底层也是HashMap哈希表jdk1.8之前数组+链表jdk1.8之后数组+红黑树(查询快)
无序
set集合调用add时候调用hashcode和equals方法判断是否重复
hashcode没有相同的就放入数组中,再判断equals是否相等,不等面值就会挂到数组下面的链表上(超过8个才会变成红黑树)
set判断相等的方法,hashcode返回相等并且equals返回也相等才是相同对象
Object hashCode()就会随机计算
它就要调用1000次equals方法。这显然会大大降低效率。
如果不重写hashcode方法set里面存引用对象就可能因为hash会随机计算,导致返回flase然后直接存不同区域,set以为不是一个对象,导致set重复。重写了hashcode,根据属性散列,如果引用对象里面属性一样,那么hash也一样,所以是相同对象。
如果不重写equals方法,即使hashcode返回false,但是值还是有可能相同的,还是会导致set重复
linkedhashset底层哈希表+链表(用来记录元素的存储顺序)
hashset是随机的
不允许重复,但是加了指针链表,就知道存储的顺序,所以遍历返回的是按照添加顺序
https://www.bilibili.com/video/BV1x741117jq?p=7
key-value 通过键找到值 key不允许重复
当map集合一创建,那么map集合创建一个entry对象,=表示键值关系
entryset获取key和value
hashtablejdk1.0的被代替
一些代码
import java.util.*;
public class Main {
public static void main(String[] args) {
//Arraylist
List list =new ArrayList<>();
list.add("123");
list.add("456");
list.add("5645");
list.add(2,"a");
list.remove(1);
list.set(2,"b");
System.out.println(list);
//linkedList
List linkedList=new linkedList<>();
linkedList.add("A");
linkedList.add("b");
linkedList.add("c");
((linkedList) linkedList).addFirst("123");
((linkedList) linkedList).push("456");
System.out.println(((linkedList) linkedList).getFirst());
((linkedList) linkedList).pop();
System.out.println(linkedList);
//set
Set set=new HashSet<>();
set.add(1);
set.add(2);
set.add(8);
set.add(4);
for(Integer i:set){
System.out.println(i);
}
//linkedHashSet
Set linkedHashSet=new linkedHashSet<>();
linkedHashSet.add(3);
linkedHashSet.add(8);
linkedHashSet.add(2);
System.out.println(linkedHashSet);
//HashMap
Map map=new HashMap();
map.put(1,2);
map.put(2,3);
System.out.println(map);
System.out.println(map.remove(1));
System.out.println(map.get(2));
System.out.println("==============");
Set>set1 = map.entrySet();
Iterator> it=set1.iterator();
while(it.hasNext()){
Map.Entry entry=it.next();
System.out.print(entry.getKey());
System.out.print(entry.getValue());
}
}
}
jdk1.7
- jdk1.7组成是数组+链表,默认16数组0.75的扩容因子
- 数组下标就是(hashcode值)%(数组大小),相同下标的就往下加链表,使用的是头插法
- 扩容因子默认0.75,链表太长了就会尽量减少链表长度就会扩容,重新创建一个更长的数组提高查找效率
- jdk1.7里面多线程时由于扩容会导致循环链表,所以可以改扩容因子避免自动扩容
- 扩容长度为原来的两倍
//无参构造方法
public HashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
//带一个参数的构造方法
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
//传入初始容量以及扩容因子的方法
public HashMap(int initialCapacity, float loadFactor) {
//参数校验
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
//刚创建HashMap时将初始化容量记录到threshold中
threshold = initialCapacity;
//空方法,linkedHashMap中使用到
init();
}
public HashMap(Map extends K, ? extends V> m) {
this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
inflateTable(threshold);
putAllForCreate(m);
}
//向HashMap中添加元素的方法
public V put(K key, V value) {
//当第一次调用put方法时才对table进行初始化
if (table == EMPTY_TABLE) {
//创建table
inflateTable(threshold);
}
//如果要put元素的key为null,则直接将该元素存储到table[0]链表中
if (key == null)
return putForNullKey(value);
//根据key散列出hash值
int hash = hash(key);
//根据hash值和table的长度计算出该元素应插入的链表在table中的下标i
//return h & (length-1);
int i = indexFor(hash, table.length);
//在table[i]中寻找与插入元素key相同的元素
for (Entry e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
//linkedhashmap的操作不用看
e.recordAccess(this);
return oldValue;
}
}
//方法执行到此处时,说明原链表中不存在与插入元素key相同的元素,那么,就需要创建一个Entry并插入
//向HashMap添加一个元素时,modCount需要自增
modCount++;
//添加Entry
addEntry(hash, key, value, i);
return null;
}
//putForNullKey方法是进行key为null的情况下的插入操作
private V putForNullKey(V value) {
//没有求hash,也没有求i,直接从table[0]中查找是否有Key相同的元素
for (Entry e = table[0]; e != null; e = e.next) {
if (e.key == null) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
//修改次数
modCount++;
addEntry(0, null, value, 0);
return null;
}
void addEntry(int hash, K key, V value, int bucketIndex) {
//jdk1.7版本HashMap的扩容条件:(size >= threshold) && (null != table[bucketIndex])
//扩容条件:1、当前HashMap中Entry个数 >= threshold 2、要插入位置的链表不为空
//jdk1.7和1.8中HashMap的扩容条件有一些差异,需要注意!!!
if ((size >= threshold) && (null != table[bucketIndex])) {
//扩容,新数组的长度为原数组的2倍
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
//扩容后需要重新计算index
bucketIndex = indexFor(hash, table.length);
}
createEntry(hash, key, value, bucketIndex);
}
头插法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mtpU6NEk-1633279305515)(C:Userszjw21AppDataRoamingTyporatypora-user-imagesimage-20210807200509746.png)]
初始化数组//方法参数toSize就是HashMap初始容量
private void inflateTable(int toSize) {
// roundUpToPowerOf2是根据初始容量计算出一个值capacity,作为table的长度
// 该值满足:capacity >= toSize,并且capacity为2的整数次幂
//比如是10找到16
int capacity = roundUpToPowerOf2(toSize);
// 重新计算扩容阈值:threshold = capacity * loadFactor
threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
//创建数组
table = new Entry[capacity];
initHashSeedAsNeeded(capacity);
}
private static int roundUpToPowerOf2(int number) {
// assert number >= 0 : "number must be non-negative";
return number >= MAXIMUM_CAPACITY
? MAXIMUM_CAPACITY
: (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;
}
//该方法是求出i的最高位,比如9对应的2进制为:1001,经过该运算后,求出结果为1000
public static int highestOneBit(int i) {
//该方法是通过多次或运算,将i的低位全都变成1,最后再进行右移再相减,就只保留了最高位的1
//如:1001,经过五次或运算,变成1111,最后一步为1111 - 0111 = 1000
i |= (i >> 1);
i |= (i >> 2);
i |= (i >> 4);
i |= (i >> 8);
i |= (i >> 16);
return i - (i >>> 1);
}
扩容
void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
//创建新数组
Entry[] newTable = new Entry[newCapacity];
//将原table中的元素转移到新table中
transfer(newTable, initHashSeedAsNeeded(newCapacity));
table = newTable;
//重新计算扩容阈值
threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}
//转移元素
void transfer(Entry[] newTable, boolean rehash) {
int newCapacity = newTable.length;
for (Entry e : table) {
while(null != e) {
//这样扩容转移链表,导致链表顺序反过来了
Entry next = e.next;
if (rehash) {
e.hash = null == e.key ? 0 : hash(e.key);
}
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
}
}
}
反正就是浅拷贝,最后多线程导致死链,再get就卡住了
fast-fail机制 每次修改都会modCount++
通过iterator进行修改的时候,modCount不一致的时候默认报错
作用就是,当多个线程同时增加和删除的时候modCount不一致导致直接报错。
ConcurrentHashMap底层原理- 原本一个个的数组每n个格子分为一个Segment数组
- Segment数组是由一个小的HashMap里面有多个Entry数组组成
- 参数concurrencyLevel是几那个Segment就是几个,shif和mask组合是为了存储segement数组的某一个位置(相当于(hashcode值)%(数组大小))
- ssize找一个大于等于concurrencyLevel的2的n次方,cap是Entry数组的2的n次方
- 大致流程就是最开始计算出 concurrencyLevel的2的幂次方数生成sgement数组,然后根据cap计算出每个segment有几个entry数组,创建一个s0=segment[0],其他segment为null,当put的时候,如果hash%数组个数之后发现segment[i]这个位置为空就new Segment
,然后在对Segment中的entry数组中进行hash%数组个数,找到了再put进去。 - 扩容的时候是创建一个小的entry数组而不是创建整个segement数组,扩容的是segment中一个里面的entry数组
- 通过ReentrantLock.trylock去加锁
- 对segment对象内部进行扩容,在扩容中idx新数组下标,创建新数组扩容,将连接在一起并且相同数组位置的链表先放入数组上,然后循环单个的放在新数组中
//entry数组初始为initialCapacity, segment数组个数concurrencyLevel
//每个segement有initialCapacity/concurrencyLevel个Entry数组(最小为2 )
public ConcurrentHashMap(int initialCapacity,
float loadFactor, int concurrencyLevel) {
if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)
throw new IllegalArgumentException();
if (concurrencyLevel > MAX_SEGMENTS)
concurrencyLevel = MAX_SEGMENTS;
// Find power-of-two sizes best matching arguments
int sshift = 0;
int ssize = 1;
//找到大于等于 concurrencyLevel的2的幂次方数
while (ssize < concurrencyLevel) {
++sshift;
ssize <<= 1;
}
segmentShift = 32 - sshift;
segmentMask = ssize - 1;
this.segments = Segment.newArray(ssize);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
//计算每个Segment中,table数组的初始大小
int c = initialCapacity / ssize;
//向上取整
if (c * ssize < initialCapacity)
++c;
//所以每个segment中entry数组最小为2个
int cap = MIN_SEGMENT_TABLE_CAPACITY;
while (cap < c)
cap <<= 1;
// 创建segments和第一个segment
Segment s0 =
new Segment(loadFactor, (int)(cap * loadFactor),
(HashEntry[])new HashEntry[cap]);
Segment[] ss = (Segment[])new Segment[ssize];
//相当于一个模板,先初始化第一个segments[0],当put进入其他segemnt里面的entry存的时候,直接拿这个模板clone一个Segment对象
//这样就不需要重写计算一个segment有几个entry数组了
UNSAFE.putOrderedObject(ss, Sbase, s0); //原子按顺序写入segments[0]
this.segments = ss;
}
put方法
找segment的时候是hash高位进行与操作找到对应的位置
public V put(K key, V value) {
Segment s;
if (value == null) throw new NullPointerException();
//1,一次hash运算
int hash = hash(key);
//2,根据hash值得到对应的Segment的位移
//右移动 segmentShift位留下高位与segmentMask进行与操作,相当于%
int j = (hash >>> segmentShift) & segmentMask;
//3,确定Segment
if ((s = (Segment)UNSAFE.getObject(segments, (j << SSHIFT) + Sbase)) == null)
//如果不存在segment就cas来new一个
s = ensureSegment(j);
//4,对Segment做put操作
return s.put(key, hash, value, false);
}
//对Segment做put操作
final V put(K key, int hash, V value, boolean onlyIfAbsent) {
//5,获取可重入锁,只有在调用时它是空闲的才能获取锁;如果锁被其他线程占用,该线程会尝试自旋获取锁,最大次数是64,如果达到最大次数,则改为阻塞获取锁,lock()操作,保证能获取成功。
HashEntry node = tryLock() ? null : scanAndLockForPut(key, hash, value);
V oldValue;
try {
HashEntry[] tab = table;
//6,根据hash值定位到Segment当中数组的具体位置
//这里低位进行与hash,上面segment进行与hash,保证hash的离散性
int index = (tab.length - 1) & hash;
HashEntry first = entryAt(tab, index);
//7,遍历该链表
for (HashEntry e = first;;) {
if (e != null) {
K k;
if ((k = e.key) == key || (e.hash == hash && key.equals(k))) {
oldValue = e.value;
//如果onlyIfAbsent为true,存在任何事情都不做
if (!onlyIfAbsent) {
e.value = value;
++modCount;
}
break;
}
e = e.next;
}
else {
//8,该数组位置的元素为null,创建HashEntry对象
if (node != null) { node.setNext(first); }
else { node = new HashEntry(hash, key, value, first); }
int c = count + 1;
//9,元素个数达到阀值,考虑扩容,注意扩容扩的是某个Segment对应的HashEntry数组
if (c > threshold && tab.length < MAXIMUM_CAPACITY) {
rehash(node);
}
//10,不扩容,则put操作
//相当于头插法
else { setEntryAt(tab, index, node); }
++modCount;
count = c;
oldValue = null;
break;
}
}
} finally {
//11,释放锁
unlock();
}
//12,返回旧值
return oldValue;
}
//保证并发安全的生成一个segment private Segmentjdk1.8ensureSegment(int k) { final Segment [] ss = this.segments; long u = (k << SSHIFT) + Sbase; // raw offset Segment seg; //可能其他并发的线程已经创建了这个对象,那么就直接返回就好了,后面几个判断也是这个意思 if ((seg = (Segment )UNSAFE.getObjectVolatile(ss, u)) == null) { //原型模式,直接获取s0构造好的Segment属性,直接创建segment Segment proto = ss[0]; // use segment 0 as prototype int cap = proto.table.length; float lf = proto.loadFactor; int threshold = (int)(cap * lf); HashEntry [] tab = (HashEntry [])new HashEntry[cap]; if ((seg = (Segment )UNSAFE.getObjectVolatile(ss, u)) == null) { // recheck Segment s = new Segment (lf, threshold, tab); //自旋+CAS,如果是空那么就走进去进行CAS,CAS过程中要是发现期望值变了那么就返回false,接着自旋。如果没变就CAS执行成功返回true那么就break。 while ((seg = (Segment )UNSAFE.getObjectVolatile(ss, u)) == null) { if (UNSAFE.compareAndSwapObject(ss, u, null, seg = s)) break; } } } return seg; }
-
**红黑树:**红黑树->平衡树,时间复杂度log(n)
- 每个结点是红的或者黑的
- 根节点是黑色的
- 每个叶子结点是黑色的
- 如果一个结点是红色的那么它的两个儿子都是黑的
- 对每个结点,从该结点到其子孙结点的所有路径上包含相同数量的黑结点
-
新结点 代码逻辑
- 父结点是黑色的不需要调整
- 父结点是红色的:
- 叔叔是空的,旋转+旋转的结点都变色变色
- 叔叔是红色,父节点+叔叔结点变黑色,祖父结点变红色
- 叔叔是黑色的,旋转+旋转的结点都变色
例如第一种情况先旋转,P,G变色
-
链表大于8个就会树化,相当于有9个就树化,只有数组长度大于64才会吧链表真正的转为红黑树
-
链表变红黑树:就是先把Node变成TreeNode然后变成双向链表,,然后判断hash值再查看key对象的类型是否实现了comparable接口,最后插入结点,再根据红4黑树规则生成红黑树,双向链表根结点放到第一个位置(红黑树的根节点)。
-
链表,扩容跟1.7差不多,转移的时候先分组然后再把链表转移
-
红黑树,扩容把原来的TreeNode看是否能转为两个比较小的链表,小于6就是链表大于6就会树化,如果不能就重新拆了再平衡树即可
- 没有分段锁了,多个线程同时扩容
- put放入对链表进行synchronized,对数组就是自旋锁,如果超过8个就把链表转成树(TreeBin代替TreeNode对象包括一个红黑树,原因:红黑树的头结点会变化,下一个线程拿头结点就变了)
- 自旋的方式进行初始化,sc==sizeCtl(volatile)默认为0,初始化cas减一,如果小于0其他线程Thread.yield(),最后sizeCtl就会赋值为扩容阈值。(*加载因子0.75)
- addCount()->扩容,同时竞争baseCount(数组长度cas加1),如果cas失败,生成一个counterCells数组,hash散列,线程对自己的counterCells(小计数器记录元素个数)进行cas加1,最后调用size就会把baseCount和counterCells加起来,如果再失败了就进入fullAddCount(x,uncontended)
- fullAddCount()->循环两次counterCells都没有成功collide为true就会扩容,但是collide会被(数组不能超过cpu个数)限制
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Vs4le429-1633279305530)(C:Userszjw21AppDataRoamingTyporatypora-user-imagesimage-20210902225417455.png)]
- 扩容
- 步长stride单个线程从右往左调数组转移,多线程,A通过步长数转移从右往左数组个数
- transferindex为数组长度,nextn新数组长度
- 转移了之后就放一个ForwardinNode对象在这个数组上,put时其他线程就知道数组正在扩容
nextIndex=transferindex=4,
nextBound=nextIndex-stride=2,
Bound=nextBound=2,
i=nextBoun-1=3
0,1,2,3
-
i和bound就是线程转移的范围
-
然后加锁synchronized转移链表或红黑树或数组某一个,链表转移(跟1.8转移一样找出链表最后面同一相同位置的转移。。。)树的转移(跟1.8hashmap转移一样)
- 有帮助线程sc会++,最后会判断sc和原来是否相等,若不相等表示扩容还没结束
-
最后全部转移完了才会赋值tabe=nextTab,sizeCtl变成新值
String:适用于少量的字符串操作的情况(只有string不可变)
String->char[] toCharArray()
char[]->String new String(char [])
String->byte[] getBytes()
byte[]->String Arrays.toSrting(bytes)
StringBuilder:适用于单线程下在字符缓冲区进行大量操作的情况(速度最快)
StringBuffer:适用多线程下在字符缓冲区进行大量操作的情况
初始化默认创建一个长度为16的数组,
如果new StringBuffer(‘abc’)相当于初始化的时候额外在char数组加3个位置为19的char 数组
如果append数组,位置不够就扩容原来2倍+2
建议使用new StringBuffer(capacity)设置char数组大小
但是length返回的是使用的个数synchronized count
三.树-
二叉树:分支不超过两个
-
排序树: 左子树小右子树大
-
平衡树: 左孩子和右孩子相等,高度可以相差一
-
红黑树:接近于平衡树,查询速度快,查找叶子节点最大次数和最小次数不超过2倍
-
堆:完全二叉树结点必叶子都大或者结点必比叶子都小(完全二叉树就是除了最后一层右叶子没有其他都填满)
三个点表示函数可以传多个参数(参数个数不确定)
public static void add(int ...arr){
int sum=0;
for(int i:arr){
sum=i+sum;
}
System.out.println(sum);
}
五.反射
将类的各个组成部分封装成其他对象,这就是反射机制
反射的功能:
在运行时判定任意一个对象所属的类;
在运行时构造任意一个类的对象;
在运行时判定任意一个类所具有的成员变量和方法;
在运行时调用任意一个对象的方法;
生成动态代理。
好处
可以再运行过程中操作这些对象
可以解耦提高扩展性
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5TUIME1S-1633279305544)(C:Userszjw21AppDataRoamingTyporatypora-user-imagesimage-20210301213947025.png)]
-
获取成员变量
get set
get方法:
字段不是静态字段的话,要传入反射类的对象.如果传null是会报
java.lang.NullPointerException但是如果字段是静态字段的话,传入任何对象都是可以的,包括null
对于static -》get(null)也能获取到值
//反射
Class personClass = Person.class;
//fields返回public的成员变量
Field[] fields = personClass.getFields();
for(Field field:fields){
System.out.println(field);
}
Field a = personClass.getField("a");
Person p=new Person();
Object o = a.get(p);
System.out.println(o);
a.set(p,"zjw");
System.out.println(p);
//获取全部的成员变量
Field[] declaredFields = personClass.getDeclaredFields();
for(Field field:declaredFields){
System.out.println(field);
}
Field a1 = personClass.getDeclaredField("age");
a1.setAccessible(true);//设置暴力反射否则报错
Object o1 = a1.get(p);
System.out.println(o1);
-
获取构造器
newInstance调用构造器
//获取构造器
Class personClass = Person.class;
Constructor constructor = personClass.getConstructor(String.class, Integer.class);
System.out.println(constructor);
Object zs=constructor.newInstance("张三",23);
System.out.println(zs);
//返回空参
Object o2 = personClass.newInstance();
System.out.println(o2);
- 获取方法
invoke调用方法
Class personClass = Person.class;
Method eat = personClass.getMethod("eat");
Person p1=new Person();
eat.invoke(p1);
Method eat1 = personClass.getMethod("eat", String.class);
eat1.invoke(p1,"饭");
Method[] methods = personClass.getMethods();
for(Method method:methods){
System.out.println(method);
System.out.println(method.getName());
//method.setAccessible(true);
}
-
读取配置文件(使用反射实现简单框架)
//加载配置文件 //1.创建properties对象 Properties properties=new Properties(); //2.加载配置文件,转换为集合 //获取配置文件 ClassLoader classLoader = Main.class.getClassLoader(); InputStream resourceAsStream = classLoader.getResourceAsStream("pro.properties"); properties.load(resourceAsStream); //获取文件中定义的数据 String className = properties.getProperty("className"); String methodName = properties.getProperty("methodName"); //3.加载进内存 Class cls = Class.forName(className); //4.获取对象 Object o3 = cls.newInstance(); //5.获取方法 Method method = cls.getMethod(methodName); method.invoke(o3);获取class对象方式:- class.forName
用于配置文件
2. 类名.class
用于参数传递
3.对象.getclass()
用以对象获取字节码的方式
//获取类
Class person = Class.forName("Person");
System.out.println(person);
Class personClass1 = Person.class;
System.out.println(personClass1);
Person person1=new Person();
Class aClass = person1.getClass();
System.out.println(aClass);
六.注解
定义:Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。Java 语言中的类、方法、变量、参数和包等都可以被标注。和 Javadoc 不同,Java 标注可以通过反射获取标注内容。在编译器生成类文件时,标注可以被嵌入到字节码中。Java 虚拟机可以保留标注内容,在运行时可以获取到标注内容 。 当然它也支持自定义 Java 标注。
作用生成文档doc 使用反射 override
JDK中预定义的一些注解:
@Override:检测是否继承父类接口
@Deprecated注解标识内容,表示已经过时
@SuppresWarnings压制警告
@SuppressWarnings("all")
自定义注解:
本质上就是一个接口,默认继承Annotation
返回值类型 int String 枚举 注解 以上类型数组
当使用注解,仅给value属性赋值时,此时value属性可以省略,只写属性值。
//定义
public @interface MyAnno {
int show1();
String show2();
String[] strs();
}
//两个文件
========================================================
//使用
@MyAnno(show1 =1,show2 = "123",strs ={"2","3"})
public void show2(){
Date d=new Date();
}
元注解
用于描述注解的注解
@Target作用的位置、
@Target(ElementType.TYPE)//表示只作用在类上
@Target(ElementType.METHOD)//表示只作用在方法
@Target(ElementType.FIELD)//表示只作用成员变量上
@Target({ElementType.METHOD,ElementType.FIELD,ElementType.TYPE})
@Retention被保留阶段
@Retention(RetentionPolicy.RUNTIME)
@documented是否抽取到api文档中
@Inherited注解是否被子类继承
使用注解代替配置文件
@MyAnno(classname = "Person",methodname = "eat")
public class aaaaaa {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchMethodException {
Class aaaaaaClass = aaaaaa.class;
MyAnno annotation = aaaaaaClass.getAnnotation(MyAnno.class);
String classname = annotation.classname();
String methodname = annotation.methodname();
//3.加载进内存
Class cls = Class.forName(classname);
//4.获取对象
Object o3 = cls.newInstance();
//5.获取方法
Method method = cls.getMethod(methodname);
method.invoke(o3);
}
}
七.内部类
一般情况下,类和类之间是互相独立的,内部类就是打破这种独立,让一个类成为另外一个类的内部信息,和成员变量,成员方法同等级别。
1.非静态内部类内部类不能定义静态变量和静态方法,因为非静态内部类不会被加载
public class Testssss {
//成员变量
private String outerName;
//成员方法
public void eat(){
System.out.println("eat");
System.out.println(outerName);
}
//内部类
public class InnerClass{
private String innerName;
public void eat(){
System.out.println("inner eat");
System.out.println(innerName);
}
public InnerClass() {
this.innerName = "123";
}
}
public static void main(String[] args) {
Testssss a=new Testssss();
a.eat();
//这样调用
Testssss.InnerClass s=a.new InnerClass();
s.eat();
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4hEdteiH-1633279305546)(C:Userszjw21AppDataRoamingTyporatypora-user-imagesimage-20210528154328994.png)]
非静态内部类的使用就是把内部类当做外部类的一个成员变量成员方法来使用
与成员变量/成员方法使用一致
由于是非静态的必须依赖于外部类的对象才能创建
Testssss.InnerClass s=a.new InnerClass();
为什么使用内部类
采用内部类这种技术,可以隐藏细节和内部结构,封装性更好,让程序结构更加合理
2.局部内部类(方法内部类)方法中也可以定义内部类(局部内部类(方法内部类))
public class Testssss {
//成员变量
private String outerName;
//成员方法
public void eat(){
//这里class必须为默认不能标注public
class InnerClass {
public void innereat(){
System.out.println("inner eat");
}
}
InnerClass c=new InnerClass();
c.innereat();
}
public static void main(String[] args) {
Testssss a=new Testssss();
a.eat();
}
}
3.静态内部类
静态内部类的创建和构造不需要依赖外部类的对象,类中的所有静态组件都不需要依赖于任何对象,可以通过类本身构造。
public class Testssss {
//成员变量
private String outerName;
//成员方法
public void eat(){
System.out.println(" eat");
}
public static class InnerClass{
private String innerName;
public InnerClass() {
this.innerName = "inner";
}
public void eat(){
System.out.println("inner eat"+innerName);
}
}
public static void main(String[] args) {
Testssss a=new Testssss();
a.eat();
InnerClass s=new InnerClass();
s.eat();
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xMf9r85u-1633279305547)(C:Userszjw21AppDataRoamingTyporatypora-user-imagesimage-20210528160206757.png)]
4.匿名内部类https://blog.csdn.net/qq_34944851/article/details/51449420
简单来说就是为了简化代码
public class Testssss {
public static void main(String[] args) {
a a = new a() {
@Override
public void eat() {
System.out.println("111");
}
@Override
public void sb() {
System.out.println("2222");
}
};
a.sb();
a.eat();
}
}
class a{
public void eat(){
System.out.println("1");
}
public void sb(){
System.out.println("2");
}
}
八.动态代理
在开发中,有a类,本来是调用c类的方法,完成某个功能,但是c不让a调用
在a和c之间创建一个b代理,c让b访问
a->b->c
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zbaaT6hd-1633279305549)(C:Userszjw21AppDataRoamingTyporatypora-user-imagesimage-20210528162444406.png)]
代理的作用
- 功能增强:在原有的功能上增加额外的功能,新增加的功能,叫功能增强。
- 控制访问:代理类不让你访问目标,如商家不让用户访问厂家。
代理类自己手工实现,需要创建一个java类,表示代理类,并且代理的目标是确定的
**缺点:**目标类增加,代理类也要增加,接口功能增加,对所有类都有影响
违反开闭原则,可维护性问题
模拟一个用户购买u盘
用户为客户端
商家为代理,代理某个品牌u盘
厂家为目标类
代理对象必须先调用目标方法然后增强功能
package service;
//接口
//表示厂家商家都要完成的功能
public interface UsBSell {
float sell(int amount);
}
package service;
//厂家
public class UsbKingFactory implements UsBSell {
@Override
public float sell(int amount) {
return 85.0f;
}
}
package service;
//商家->代理
public class Taobao implements UsBSell {
private UsBSell usBSell=new UsbKingFactory();
@Override
public float sell(int amount) {
//访问厂家目标方法
float price= usBSell.sell(1);
//增强
price+=25;
return price;
}
}
package service;
//用户
public class UserShop {
public static void main(String[] args) {
Taobao taoBao=new Taobao();
float sell = taoBao.sell(1);
System.out.println("通过淘宝商家购买单价:"+sell);
}
}
2.动态代理
静态代理中目标类很多的时候,使用动态代理
在执行过程中,使用jdk反射机制,创建代理类对象,并动态的指定代理目标类。(创建对象的能力)
**jdk动态代理:**使用java反射包中的类和接口实现动态代理功能(java.lang.reflect里面三个类:InvocationHandler,Method,Proxy)(接口)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tDvjTB6z-1633279305550)(C:Userszjw21AppDataRoamingTyporatypora-user-imagesimage-20210528173324652.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-feXdnqof-1633279305551)(C:Userszjw21AppDataRoamingTyporatypora-user-imagesimage-20210528174017431.png)]
package service;
//表示厂家商家都要完成的功能
public interface UsBSell {
float sell(int amount);
}
package service;
public class UsbKingFactory implements UsBSell {
@Override
public float sell(int amount) {
System.out.println("目标类中,执行sell目标方法");
return 85.0f;
}
}
package service;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class Myhandler implements InvocationHandler {
private Object target=null;
//构造方法传目标对象
public Myhandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//float price= usBSell.sell(1);
Object invoke = method.invoke(target, args);
//price+=25;
System.out.println("增强");
if(invoke!=null){
Float price=(Float)invoke;
price+=25;
invoke=price;
}
//return price;
return invoke;
}
}
package service;
import java.lang.reflect.Proxy;
public class UserShop {
public static void main(String[] args) {
UsBSell u=new UsbKingFactory();
//传入目标方法
Myhandler myhandler = new Myhandler(u);
//代理对象
UsBSell o = (UsBSell)Proxy.newProxyInstance(u.getClass().getClassLoader(), u.getClass().getInterfaces(), myhandler);
//调用
o.sell(1);
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4fLwJ4ZB-1633279305552)(C:Userszjw21AppDataRoamingTyporatypora-user-imagesimage-20210528180149367.png)]
**cglib动态代理:**第三方工具库,创建代理对象,原理是继承目标类,创建它的子类,在子类中重写父类同名方法,实现功能修改。不能为final(类)
jdk动态代理原理就相当于Proxy.newProxyInstance里面写了一个匿名内部类,当再次调用接口中的方法时,就会将method参数传给自己写的**(xxxProxy)** implements InvocationHandler,然后调用**(xxxProxy)**invoke方法。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QznoDmcX-1633279305554)(C:Userszjw21AppDataRoamingTyporatypora-user-imagesimage-20210516154329410.png)]
九.成员变量和局部变量(属性)成员变量定义在类里面(可以用private,public,protect,缺省默认default)非static加载到堆中
默认初始化值:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MLUl6G2d-1633279305556)(C:Userszjw21AppDataRoamingTyporatypora-user-imagesimage-20210706205229239.png)]
局部变量定义在方法里面(不可以用权限修饰符)加载到栈中
没有默认初始化值。必须显式赋值。
十.类和对象类和对象是面向对象的核心概念
类是抽象的,概念上的内容 一般放在常量池中
对象是是实实在在存在的一个个体。 对象就是实例=》放在堆中
对象是类派生出来的。
一个java文件中只有一个public class类
十一.重载只要是同一个类中方法名相同,参数列表不同就是重载(Overload),与方法返回值和访问修饰符无关。
例如这些eat都叫重载
class Ass{
public int a;
public void eat(int a){
}
public void eat(int ... a){
}
private int eat(int a,int b){
return 1;
}
}
十二.面向对象
封装
提高安全性,复用性,隐藏实现细节
高内聚:类的内部数据封装起来,不允许其他类干涉
低耦合:仅对外暴露少量方法用于使用
四种权限[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NFk5l8Mg-1633279305558)(C:Userszjw21AppDataRoamingTyporatypora-user-imagesimage-20210706223907563.png)]
类中只有public和缺省default
构造器一旦构造器使用了默认空构造器就没用了,同样也可以标识四种权限
this super构造器之间可以通过this()调用
class AAA{
int a;
public AAA(){
}
public AAA(int abc){
this();//调用AAA()构造器
this.a=a;
}
}
super()也这样调用,调用父类的构造器
子类的构造器中一定会有一个super()指向父类构造器,不写就是默认构造器指向父类的默认构造器
同时super()this()不能同时出现,并且都在构造器第一行
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YMUIOgXJ-1633279305560)(C:Userszjw21AppDataRoamingTyporatypora-user-imagesimage-20210707113110099.png)]
下面就不报错
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5UWJrgs1-1633279305561)(C:Userszjw21AppDataRoamingTyporatypora-user-imagesimage-20210707113314401.png)]
继承代码复用性,功能扩展,多态基础,
单继承
一旦A继承B之后,A就获得了B所有的属性与方法
即使父类中声明了private的属性或方法,子类继承父类后仍然获取了父类的结构,只是由于封装性的影响,子类不能直接调用。可以通过父类的public方法间接调用属性或者方法。
重写子类继承父类后,可以通过对父类同名同参数的方法进行覆盖操作
规则:
-
重写方法名,参数列表必须相同
-
子类的方法**权限>=**父类方法的权限(子类不能重写父类的private方法)
-
当父类返回值为void,子类也为void,
其他时候返回值可以是父类返回值的子类,例如父类的返回值是Object 子类的返回值可以为任何继承了Object的类
-
父类抛出的异常,子类可以是异常的子类
public class ssssss {
//这里返回值是Object,权限是default
Object eat(){
System.out.println("eat");
return new Object();
}
public void ss(){
System.out.println("ss");
eat();
}
public static void main(String[] args) {
AAA s=new AAA();
s.ss();
}
}
class AAA extends ssssss{
//这里权限是public 返回值是AAA,可以被调用
public AAA eat(){
System.out.println("eat son");
return new AAA();
}
}
输出
ss
eat son
重写重载区别
重写是方法调用的时候才知道确定的方法,动态绑定
重载是方法调用之前就确定了调用的方法,静态绑定
多态一个事物多种形态 =》1.有继承2.有重写
B extends A A a=new B();
父类引用指向子类的对象 运行时行为
向下转型[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IHjHR0yR-1633279305563)(C:Userszjw21AppDataRoamingTyporatypora-user-imagesimage-20210707122514849.png)]
B extends A A a=new B(); B b=(B)a;(可以通过instanceof判断 )
Object obj=new Woman(); Person p=(Person)obj; //这样也行 Person p=new Person(); Man m=(Man)p; //这样就报错抽象
abstract一个抽象方法必须在抽象类里面,继承的子类要实现所有抽象方法,包括爸爸的爸爸的抽象方法
抽象类不能实例化
不能修饰私有方法,静态方法,final方法,final类
泛型上界
只能get不能set
下界
只能写不能读
十三.==和equals==表示变量保存数据相等,不一定要类型相等;例如(double) 1.0= =( int) 1,(int) 1= =(char) 1
比较引用数据类型变量,比较地址是否相等
equals只能用于引用数据类型变量
自定义的类在Object中默认用的就是==,重写equals之后才会改变
像String,Date,File包装好的都自己重写了equals,比较的是值是否相同
**例子,这里name是String类型的,所以用equals比较 **:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HwRLoKmD-1633279305565)(C:Userszjw21AppDataRoamingTyporatypora-user-imagesimage-20210707144326995.png)]
十四.单元测试import org.junit.Test @Test
十五.包装类public static void main(String[] args) {
//装箱
Integer i1=12;
//拆箱
int sb=i1;
//String
Integer i=new Integer("12");
//int
Integer a=new Integer(12);
//Integer->int
int i1 = a.intValue();
Float f1=12.3;
float ff=f1;
//Float
Float f=new Float(12.3);
//float
Float f1=new Float(12.3f);
//String------->Boolean中可以Boolean b=new Boolean("trueaa")返回flase但不会报错
Float f2=new Float("12.3f");
float v = f2.floatValue();
=========>int Integer 转为String
//float
String str=String.valueOf(v);
//Float
String str2=String.valueOf(f2);
//Float
f2.toString();
============String 转为 int Integer
int i2 = Integer.parseInt("123");
Integer i3 = Integer.parseInt("123");
System.out.println(i2);
}
十六.static
修饰属性,方法,代码块,内部类
static放在方法上,跟类一起加载了this,super不能用了
静态属性vs非静态属性静态属性(类变量):创建了类的多个对象,多个对象共享一个静态变量,某一个对象修改静态变量,其他对象调用的就是修改过的对象。
非静态属性(实例变量):创建类的多个对象每个对象分配一组类的非静态属性,一个对象修改一个实例变量不影响其他对象调用非静态属性
十七.final标了final类不能继承
标了final方法不能重写
标了final属性不能修改(只有构造器中可以修改)
十八.接口为了是实现多继承(不能写构造器,不能实例化)
接口接口之间可以继承,多继承
JDK7以前只能定义全局常量和抽象方法
全局常量:public static final
抽象方法:public abstract
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dHjGtmV2-1633279305566)(C:Userszjw21AppDataRoamingTyporatypora-user-imagesimage-20210707163315755.png)]
实现了全部方法才能实例化,否则为抽象类
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T9dOO07Y-1633279305567)(C:Userszjw21AppDataRoamingTyporatypora-user-imagesimage-20210707163534753.png)]
JDK8之后可以定义静态方法,默认方法
并且默认方法可以被重写,默认方法可以通过创建实现类的对象来直接调用
还可以通过在子类方法中调用**(接口.super.默认方法名)**的方式调用
如果子类同时继承了父类与实现了接口,父类和接口有相同默认方法,子类对象优先是父类的方法(类优先原则,如果是变量直接报错)
如果类只实现了多个接口,接口有相同的默认方法,就会冲突,必须重写方法
静态方法直接通过interface名调用
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QAa4d6Zd-1633279305569)(C:Userszjw21AppDataRoamingTyporatypora-user-imagesimage-20210707165346112.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tsLuQaTL-1633279305570)(C:Userszjw21AppDataRoamingTyporatypora-user-imagesimage-20210707165608461.png)]
十九.输入输出流对于内存而言,往内存存就是输入流,内存往外写就是输出流
文件操作 1.创建文件//方式一
String filePath="C:\Users\zjw21\Desktop\1.txt";
File f=new File(filePath);
f.createNewFile();
System.out.println("创建成功");
//方式二
File parentfile = new File("C:\Users\zjw21\Desktop");
String ff="2.txt";
new File(parentfile,ff).createNewFile();
//方式三
File fff=new File("C:\Users\zjw21\Desktop","3.txt");
fff.createNewFile();
2.基本操作
//获取文件名字
System.out.println("获取文件名字"+fff.getName());
//绝对路径
System.out.println("获取文件名字"+fff.getAbsolutePath());
//获取父目录
System.out.println("获取父目录"+fff.getParent());
//获取文件大小,汉字3字节,字母数字1字节
System.out.println("文件大小"+fff.length());
//文件是否存在
System.out.println("文件是否存在"+fff.exists());
//是否为一个目录
System.out.println("是不是一个目录"+fff.isDirectory());
//文件存在就删除
if(fff.exists()){
if(fff.delete()){
System.out.println("删除成功");
}else{
System.out.println("删除失败");
}
}else{
System.out.println("文件不存在");
}
3.目录也当作文件
文件的操作delete,length,getAbsolutePath()一样使用
mkdir一级目录
mkdirs迭代多级目录
String dirct="C:\Users\zjw21\Desktop\a\b\c";
File dir=new File(dirct);
if(dir.exists()){
System.out.println("文件存在");
}else{
if(dir.mkdirs()){
System.out.println("创建成功");
}else{
System.out.println("创建失败");
}
}
四种抽象类
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QopoxOuM-1633279305573)(C:Userszjw21AppDataRoamingTyporatypora-user-imagesimage-20210711160754742.png)]
字节流(8 bit)
字符流(16 bit) 处理txt 不能处理图片
节点流:直接作用文件上的流。
处理流:在节点流基础之上进行处理。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XRuiacic-1633279305576)(C:Userszjw21AppDataRoamingTyporatypora-user-imagesimage-20210711161406689.png)]
相比FileInputStream,FileReader,缓冲流效率更高,快
但是要通过FileInputStream,FileReader,包装起来
字节流String filePath="C:/Users/zjw21/Desktop/1.txt";
byte[]data=new byte[3];
int readLen=0;
File f=new File(filePath);
f.createNewFile();
FileInputStream fileInputStream=new FileInputStream(filePath);
//false表示覆盖
FileOutputStream fileOutputStream = new FileOutputStream(filePath,false);
fileOutputStream.write("zjwz".getBytes());
while((readLen=fileInputStream.read(data))!=-1){
System.out.print(new String(data,0,readLen));
}
fileOutputStream.close();
fileInputStream.close();
字符流差不多用char[] c=new char[1024];接收
复制文件 public static void copy(String src, String dst) {
//提供需要读入和写入的文件
File fileIN = new File(src);
File fileOUT = new File(dst);
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
//创建相应的节点流,将文件对象作为形参传递给节点流的构造器
FileInputStream fis = new FileInputStream(fileIN);
FileOutputStream fos = new FileOutputStream(fileOUT);
//创建相应的缓冲流,将节点流对象作为形参传递给缓冲流的构造器
bis = new BufferedInputStream(fis);
bos = new BufferedOutputStream(fos);
//具体的文件复制操作
byte[] b = new byte[65536]; //把从输入文件读取到的数据存入该数组
int len; //记录每次读取数据并存入数组中后的返回值,代表读取到的字节数,最大值为b.length;当输入文件被读取完后返回-1
while( (len=bis.read(b)) != -1 ) {
bos.write(b, 0, len);
bos.flush();//默认到了8192也会自动flush清空缓冲区
}
} catch(IOException e) {
e.printStackTrace();
} finally {
//关闭流,遵循先开后关原则(这里只需要关闭缓冲流即可)
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
//关闭外层流的同时自动关闭了内层的
// fos.close();
// fis.close();
}
Buffer字符流
String filePath2="C:/Users/zjw21/Desktop/1.txt";
String filePath="C:/Users/zjw21/Desktop/2.txt";
char [] c=new char[1024];
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(filePath));
FileReader f=new FileReader(filePath2);
BufferedReader bufferedReader = new BufferedReader(f);
String s="";
while((s=bufferedReader.readLine())!=null){
bufferedWriter.write(s);
bufferedWriter.newline();
}
bufferedReader.close();
bufferedWriter.close();
buffer字节流处理二进制文件
String filePath="D:\第四期\15 分布式框架专题-分布式技术ELK\07_Zookeeper集群与Watcher监听机制源码(上).mp4";
String filePath2="C:/Users/zjw21/Desktop/1.mp4";
BufferedInputStream bufferedInputStream=new BufferedInputStream(new FileInputStream(filePath));
BufferedOutputStream bufferedOutputStream=new BufferedOutputStream(new FileOutputStream(filePath2));
byte []b=new byte[1024];
int len=0;
while((len=bufferedInputStream.read(b))!=-1){
bufferedOutputStream.write(b,0,len);
}
bufferedOutputStream.close();
bufferedInputStream.close();
转换流
Java IO流中提供了两种用于将字节流转换为字符流的转换流。其中InputStreamReader用于将字节输入流转换为字符输入流,其中OutputStreamWriter用于将字节输出流转换为字符输出流。使用转换流可以在一定程度上避免乱码,还可以指定输入输出所使用的字符集。
可以改字符集utf-8 gbk
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NsLA7ALj-1633279305579)(C:Userszjw21AppDataRoamingTyporatypora-user-imagesimage-20210711164700860.png)]
public class MyIO2 {
public static void main(String[] args) throws IOException {
String src = "C:/software/workspace/workspace/Test/src/test/a.txt";
// BufferedReader br = new BufferedReader(new FileReader(src));
BufferedReader br = new BufferedReader(
new InputStreamReader(
new FileInputStream(src), "gbk"));
String line = null;
while(null != (line=br.readLine())){
System.out.println(line);
}
// BufferedWriter bw = new BufferedWriter(new FileWriter(src, true));
BufferedWriter bw = new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(src,true),"gbk"));
String str = "好好学习,天天向上!!!";
bw.write(str);
bw.newline(); //输入换行符
bw.flush(); //将缓冲区内容写入目标文件
while(null != (line=br.readLine())){
System.out.println(line);
}
}
}
标准输入输出流
System.in -》 输入流inputstream
System.setIn(输入流)
下次输出就根据输入流输入
================================================================================================================================================================================
System.out -》打印流-》输出流
System.setOut(输出流)
下次输出就根据输出流输出
]b=new byte[1024];
int len=0;
while((len=bufferedInputStream.read(b))!=-1){
bufferedOutputStream.write(b,0,len);
}
bufferedOutputStream.close();
bufferedInputStream.close();
#### 转换流
Java IO流中提供了两种用于将字节流转换为字符流的转换流。其中InputStreamReader用于将字节输入流转换为字符输入流,其中OutputStreamWriter用于将字节输出流转换为字符输出流。使用转换流可以在一定程度上避免乱码,还可以指定输入输出所使用的字符集。
**可以改字符集**utf-8 gbk
[外链图片转存中...(img-NsLA7ALj-1633279305579)]
```java
public class MyIO2 {
public static void main(String[] args) throws IOException {
String src = "C:/software/workspace/workspace/Test/src/test/a.txt";
// BufferedReader br = new BufferedReader(new FileReader(src));
BufferedReader br = new BufferedReader(
new InputStreamReader(
new FileInputStream(src), "gbk"));
String line = null;
while(null != (line=br.readLine())){
System.out.println(line);
}
// BufferedWriter bw = new BufferedWriter(new FileWriter(src, true));
BufferedWriter bw = new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(src,true),"gbk"));
String str = "好好学习,天天向上!!!";
bw.write(str);
bw.newline(); //输入换行符
bw.flush(); //将缓冲区内容写入目标文件
while(null != (line=br.readLine())){
System.out.println(line);
}
}
}
标准输入输出流
System.in -》 输入流inputstream
System.setIn(输入流)
下次输出就根据输入流输入
================================================================================================================================================================================
System.out -》打印流-》输出流
System.setOut(输出流)
下次输出就根据输出流输出



